From fd09ca103fd9fd14f5d0ecd3df6efd839a9df560 Mon Sep 17 00:00:00 2001 From: Deluan Date: Mon, 9 Feb 2026 16:42:05 -0500 Subject: [PATCH] fix(scanner): resolve data race on conf.Server access in getScanner Captured DevExternalScanner config value in the controller struct at construction time instead of reading the global conf.Server pointer in getScanner(). The background goroutine spawned by ScanFolders() was reading conf.Server.DevExternalScanner concurrently with test cleanup reassigning the conf.Server pointer, causing a data race detected by the race detector in the E2E test suite. --- scanner/controller.go | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/scanner/controller.go b/scanner/controller.go index 635011840..db6444fa9 100644 --- a/scanner/controller.go +++ b/scanner/controller.go @@ -29,21 +29,22 @@ var ( func New(rootCtx context.Context, ds model.DataStore, cw artwork.CacheWarmer, broker events.Broker, pls core.Playlists, m metrics.Metrics) model.Scanner { c := &controller{ - rootCtx: rootCtx, - ds: ds, - cw: cw, - broker: broker, - pls: pls, - metrics: m, + rootCtx: rootCtx, + ds: ds, + cw: cw, + broker: broker, + pls: pls, + metrics: m, + devExternalScanner: conf.Server.DevExternalScanner, } - if !conf.Server.DevExternalScanner { + if !c.devExternalScanner { c.limiter = P(rate.Sometimes{Interval: conf.Server.DevActivityPanelUpdateRate}) } return c } func (s *controller) getScanner() scanner { - if conf.Server.DevExternalScanner { + if s.devExternalScanner { return &scannerExternal{} } return &scannerImpl{ds: s.ds, cw: s.cw, pls: s.pls} @@ -92,16 +93,17 @@ type scanner interface { } type controller struct { - rootCtx context.Context - ds model.DataStore - cw artwork.CacheWarmer - broker events.Broker - metrics metrics.Metrics - pls core.Playlists - limiter *rate.Sometimes - count atomic.Uint32 - folderCount atomic.Uint32 - changesDetected bool + rootCtx context.Context + ds model.DataStore + cw artwork.CacheWarmer + broker events.Broker + metrics metrics.Metrics + pls core.Playlists + limiter *rate.Sometimes + devExternalScanner bool + count atomic.Uint32 + folderCount atomic.Uint32 + changesDetected bool } // getLastScanTime returns the most recent scan time across all libraries