Compare commits
1 Commits
fix/should
...
fix/config
| Author | SHA1 | Date | |
|---|---|---|---|
| 189d44d70f |
@@ -730,10 +730,15 @@ 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).
|
||||
//
|
||||
// We deliberately do NOT log inside this goroutine: this goroutine
|
||||
// outlives ctx (parent's defer cancel only fires when the test's
|
||||
// outer scope exits, not when t.Cleanup runs), so a log call here
|
||||
// races with the next test's LoadConfig → SetupLogging →
|
||||
// zerolog.SetGlobalLevel under -race (observed 2026-05-05, Q-038).
|
||||
// The flag-set is the load-bearing operation; the missing log line
|
||||
// is a small ops cost (operators learn the watcher stops on shutdown
|
||||
// via the parent shutdown logs, not a dedicated message).
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
c.reloadMu.Lock()
|
||||
|
||||
26
pkg/config/main_test.go
Normal file
26
pkg/config/main_test.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// TestMain quiets the global zerolog level for the duration of the test
|
||||
// suite. Rationale (Q-038, 2026-05-05): viper's internal watcher goroutine
|
||||
// (started by viper.WatchConfig in WatchAndApply) has no public Stop and
|
||||
// can outlive a test's context. Any log call from a leaked goroutine
|
||||
// races with the next test's LoadConfig → SetupLogging →
|
||||
// zerolog.SetGlobalLevel under `go test -race`. Disabling the logger here
|
||||
// is the root-cause fix: the racing memory location is zerolog's gLevel
|
||||
// global, and if no log call ever evaluates against it we sidestep the
|
||||
// race entirely without changing production behavior.
|
||||
//
|
||||
// In production, log calls happen against an unchanging global level
|
||||
// (SetupLogging runs once at startup), so the race condition does not
|
||||
// occur there.
|
||||
func TestMain(m *testing.M) {
|
||||
zerolog.SetGlobalLevel(zerolog.Disabled)
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
Reference in New Issue
Block a user