From 7d952596671ecbd0fead50a2b5aabb574b010422 Mon Sep 17 00:00:00 2001 From: Gabriel Radureau Date: Tue, 5 May 2026 09:39:46 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(config):=20remove=20racy=20l?= =?UTF-8?q?og.Info=20in=20WatchAndApply=20cancel=20goroutine?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cancel-handler goroutine emitted a "watcher stopped" log AFTER setting the watcherStopped flag. Because viper's internal watcher goroutine (started by viper.WatchConfig) has no public Stop and can outlive the context, this log call would race with subsequent zerolog.SetGlobalLevel from the next test's LoadConfig → SetupLogging. Symptom (under go test -race ./pkg/config/...): WARNING: DATA RACE Write at zerolog.SetGlobalLevel Previous read by zerolog.(*Logger).disabled in log.Info(...) Fix: drop the informational log. The flag is sufficient — the cancel ack does not need to be observable. Test cleanups (defer cancel()) already serialize via the t.Cleanup teardown order. Verified: go test -race ./pkg/config/... passes, full BDD suite green. --- pkg/config/config.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index e22a937..bb9d2ee 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -730,11 +730,14 @@ func (c *Config) WatchAndApply(ctx context.Context) { // Stop the watcher on context cancel — we set a flag that the // OnConfigChange handler checks, avoiding the race with viper's // internal state that would occur if we called OnConfigChange again. + // We deliberately do NOT log here: viper's internal watcher goroutine + // has no public Stop, so it can outlive ctx, and a zerolog call here + // would race with the next test's LoadConfig → SetupLogging → + // zerolog.SetGlobalLevel under -race (observed 2026-05-05). go func() { <-ctx.Done() c.reloadMu.Lock() c.watcherStopped = true c.reloadMu.Unlock() - log.Info().Msg("Config hot-reload watcher stopped") }() }