Go Proposal #75047: Auto-Label Goroutines per Test to Solve Timeout Debugging
Go proposal #75047 aims to automatically attach goroutine labels (test name and iteration) during test execution, enabling developers to quickly identify which test owns a stuck goroutine in timeout dumps, leveraging the existing pprof label mechanism and runtime traceback enhancements.
Test Timeouts and the Missing Goroutine Ownership
Go developers frequently encounter CI test runs that hang until go test -timeout kills the process, emitting a massive goroutine stack dump. Since Go 1.21 the test framework prints which test functions have not yet finished, but it still cannot tell which of the hundreds of goroutines belong to which test. This makes root-cause analysis extremely difficult, especially for tests that spawn many internal goroutines.
Proposal #75047: Add Goroutine Labels to Tests
The proposal #75047 (https://github.com/golang/go/issues/75047) addresses this by automatically labeling every goroutine created during a test, benchmark, or fuzz run. It builds on the existing runtime/pprof label mechanism introduced in Go 1.9 (proposal #17280, https://go.googlesource.com/proposal/+/HEAD/design/17280-profile-labels.md) via pprof.Do and pprof.Labels. Child goroutines inherit their parent's labels, and the labels appear in CPU profiles, goroutine profiles, and — with the related change CL 694119 (https://go.dev/cl/694119, issue #23458 https://github.com/golang/go/issues/23458) — in panic tracebacks.
pprof.Do(ctx, pprof.Labels("subsystem", "admin"), func(ctx context.Context) {
// this function and all derived goroutines carry subsystem=admin
runAdminServer(ctx)
})The straw-man proposal suggests two labels per test invocation: test (or test.name) with value equal to t.Name() (or f.Name() for fuzz tests). test_iter (or test.iter) recording the iteration number under -count (starting at 0).
With CL 694119 (https://go.dev/cl/694119) the runtime would print a goroutine line like:
goroutine 36 gp=0x33789cd803c0 m=0 mp=0x6d37e0 [running] {test: TestWithPanic#0}:Immediately revealing the test name and iteration.
Community Debate: One Label vs Two, Delimiters, and Key Naming
Reviewer neild proposed a single test label encoding both name and iteration, e.g. TestWithPanic#0 or TestFuzz#0#3 for fuzz inputs. The proposal author countered that this fragments profiling aggregation: a test run with -count 100 would produce 100 distinct label values, scattering samples across many buckets. While pprof -taghide can partially merge them, a separate test_iter label keeps aggregation clean.
Delimiter choice matters because t.Run subtests already use # to disambiguate duplicate names (e.g. TestWithPanic/Foo#1). Adding another # for iteration creates confusing double-hash strings. Alternatives like @, +, or * were discussed.
Key naming style: underscore ( test / test_iter) vs dot ( test.name / test.iter). The review committee leaned toward dot notation for consistency with other pprof label conventions.
Review Committee Stance (aclements)
Direction fully accepted — the team "completely supports doing this"; only label shape remains undecided.
Iteration number from the first run — always print test_iter (even for -count 1) to avoid inconsistent UX where the label appears only on the second run.
Benchmark iteration semantics — traditional auto-scaling loops have ambiguous iteration meaning; the new b.Loop style avoids this, so the outer -count iteration number suffices.
Naming preference emerging — test.name / test.iter (dot style) appears favored.
Implementation Progress
CL 694119 (https://go.dev/cl/694119): runtime traceback prints labels in goroutine status line.
CL 696117 (https://go.dev/cl/696117) / CL 696595 (https://go.dev/cl/696595): testing package wraps each test/benchmark call in pprof.Do to apply labels. Author notes they still "need a little work".
Performance consideration: context.Context label updates allocate a new object; acceptable for outer test granularity but avoided in benchmark inner loops.
Impact on Daily Development
Faster CI timeout triage — search test: TestXxx in goroutine dumps instead of guessing call graphs.
Better profiling attribution — aggregate CPU/goroutine profiles by test label to see which test contributes most resources.
Ecosystem convergence — projects like grpc-go already label server goroutines with grpc.method, citing #75047's naming discussions, indicating a cross-project standard for goroutine labeling.
Conclusion
Though seemingly a small change, #75047 exemplifies Go's engineering philosophy: validate a real pain point, then meticulously refine naming, compatibility, and performance trade-offs. This "slow but thorough" review culture underpins Go's long-term API stability. Developers plagued by flaky CI timeouts should watch this proposal — it promises a tangible productivity boost once landed.
References: Proposal: https://github.com/golang/go/issues/75047 Runtime traceback label display: #23458 (https://github.com/golang/go/issues/23458), CL 694119 (https://go.dev/cl/694119) Testing implementation: CL 696117 (https://go.dev/cl/696117), CL 696595 (https://go.dev/cl/696595) Original pprof label design: #17280 (https://go.googlesource.com/proposal/+/HEAD/design/17280-profile-labels.md)
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
TonyBai
Tony Bai's tech world (tonybai.com). Not satisfied with just "knowing how", we strive for mastery. Focused on Go language internals, high-quality engineering practices, and cloud‑native architecture, exploring cutting‑edge intersections of Go and AI. Gophers who pursue technology are welcome—follow me and evolve with Go.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
