From 4d2ee8666c4edb09e522de2d040967ea963ee6ca Mon Sep 17 00:00:00 2001 From: "pulse-triage[bot]" <249995291+pulse-triage[bot]@users.noreply.github.com> Date: Mon, 21 Sep 2026 09:25:24 +0100 Subject: [PATCH] test(server): synchronize startup watchdog log capture TestStartupWatchdogLogsPhaseAndStack polls a bytes.Buffer while the watchdog goroutine writes the same buffer through zerolog, so 'go test -race' reports a read/write data race at service_health_test.go:134 and fails the rest-0 backend shard. Capture the log through a mutex-guarded buffer so the poll and the watchdog write are serialized. Change-source: pulse-maintainer --- pkg/server/service_health_test.go | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/pkg/server/service_health_test.go b/pkg/server/service_health_test.go index 5f33e0aaf2..7716ca13bd 100644 --- a/pkg/server/service_health_test.go +++ b/pkg/server/service_health_test.go @@ -6,6 +6,7 @@ import ( "net" "net/http" "strings" + "sync" "testing" "time" @@ -118,12 +119,31 @@ func TestFrontendAssetPathsStayLocalAndBounded(t *testing.T) { } } +// syncBuffer is a goroutine-safe bytes.Buffer for capturing watchdog log output +// written from another goroutine while the test polls it. +type syncBuffer struct { + mu sync.Mutex + buf bytes.Buffer +} + +func (b *syncBuffer) Write(p []byte) (int, error) { + b.mu.Lock() + defer b.mu.Unlock() + return b.buf.Write(p) +} + +func (b *syncBuffer) String() string { + b.mu.Lock() + defer b.mu.Unlock() + return b.buf.String() +} + // A bound-but-not-serving listener is otherwise undiagnosable: nothing accepts // connections and no startup log line is emitted while a synchronous step // stalls. The watchdog must name the last completed phase and dump the local // goroutine stacks so a recurrence can be read from the field log. func TestStartupWatchdogLogsPhaseAndStack(t *testing.T) { - var buf bytes.Buffer + var buf syncBuffer phase := &startupPhase{} phase.mark("config watcher started")