From 972f5837ea9550b0fc5cae38b20c49451b7a7a42 Mon Sep 17 00:00:00 2001 From: Josh Siviour Date: Mon, 3 Aug 2026 00:49:50 +1000 Subject: [PATCH] fix: bump goleak past the elided-frames stack-parse panic (#660) * fix: bump goleak past the elided-frames stack-parse panic goleak v1.3.0's stack parser treats a "...N frames elided..." line as a function name and panics when it can't parse one. Go elides frames on deep/recursive stacks, which is rare but load-dependent, so this surfaced as an intermittent panic inside goleak itself (not a real leak) during TestDrainVolume_DispatchedGoroutineDoesNotLeak under a full parallel preflight run. Upstream fixed this in goleak@b62053b, unreleased as a tagged version, so this pins to the latest commit. * test: guard goleak against panicking on elided-frame stacks * style: trim goleak regression test comments to the 3-line max --- go.mod | 2 +- go.sum | 3 ++- spinifex/vm/watchdog_test.go | 41 ++++++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index cdb15b0f6..ad4b49e80 100644 --- a/go.mod +++ b/go.mod @@ -46,7 +46,7 @@ require ( go.opentelemetry.io/otel/sdk/log v0.20.0 go.opentelemetry.io/otel/sdk/metric v1.44.0 go.opentelemetry.io/otel/trace v1.44.0 - go.uber.org/goleak v1.3.0 + go.uber.org/goleak v1.3.1-0.20260602155401-5f791e3ef83b go.yaml.in/yaml/v3 v3.0.4 golang.org/x/crypto v0.54.0 golang.org/x/sync v0.22.0 diff --git a/go.sum b/go.sum index 9319a5eeb..7846b2e22 100644 --- a/go.sum +++ b/go.sum @@ -1805,8 +1805,9 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/goleak v1.3.1-0.20260602155401-5f791e3ef83b h1:FHYZT12VzXnb6P399kd1i9Ai7aLF+CFSUsOFylDt7Lk= +go.uber.org/goleak v1.3.1-0.20260602155401-5f791e3ef83b/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= diff --git a/spinifex/vm/watchdog_test.go b/spinifex/vm/watchdog_test.go index 0ae9e26e0..386c77cee 100644 --- a/spinifex/vm/watchdog_test.go +++ b/spinifex/vm/watchdog_test.go @@ -154,3 +154,44 @@ func assertStuckMarkedFailed(t *testing.T, m *Manager, rt *recordedTransitions, assert.Contains(t, targets, StateTerminated, "terminal transition must land in Terminated") } + +// deepRecurse recurses past the 100 frames runtime.Stack keeps before it +// collapses the rest into a literal "...N frames elided..." line. Kept +// non-inlinable so the recursion reaches the goroutine's stack trace. +// +//go:noinline +func deepRecurse(n int, ready chan<- struct{}, block <-chan struct{}) int { + if n <= 0 { + close(ready) + <-block + return 0 + } + return deepRecurse(n-1, ready, block) + n +} + +// TestGoleakParsesElidedFrameStacks guards against goleak's stack parser +// panicking on the "...N frames elided..." line runtime.Stack emits for +// goroutines parked past 100 frames deep. v1.3.0 reads it as a function name. +func TestGoleakParsesElidedFrameStacks(t *testing.T) { + ready := make(chan struct{}) + block := make(chan struct{}) + done := make(chan struct{}) + + // 200 frames guarantees the elided-frames line regardless of the + // runtime's exact truncation threshold, while the goroutine stays + // parked on <-block so its stack is captured mid-recursion. + go func() { + defer close(done) + deepRecurse(200, ready, block) + }() + <-ready + + assert.NotPanics(t, func() { + // Find legitimately reports the parked goroutine as a leak; + // only a panic escaping the call is a failure here. + _ = goleak.Find() + }) + + close(block) + <-done +}