From d1d5c4fd064746c5672c38d8a16afaa3ae563f69 Mon Sep 17 00:00:00 2001 From: davidfowl <95136+davidfowl@users.noreply.github.com> Date: Fri, 30 Oct 2026 16:02:29 +0000 Subject: [PATCH 1/2] Count live goroutines from a stack dump in the Go cancellation test runtime.NumGoroutine can briefly count exited goroutines as running while the GC frees their stacks, so the delta in TestRegisterCancellationDoesNotParkGoroutinePerToken flaked after the previous test spawned 5000 goroutines. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../AtsGoCodeGeneratorTests.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/tests/Aspire.Hosting.CodeGeneration.Go.Tests/AtsGoCodeGeneratorTests.cs b/tests/Aspire.Hosting.CodeGeneration.Go.Tests/AtsGoCodeGeneratorTests.cs index d5acffaf8ba..59478bafa13 100644 --- a/tests/Aspire.Hosting.CodeGeneration.Go.Tests/AtsGoCodeGeneratorTests.cs +++ b/tests/Aspire.Hosting.CodeGeneration.Go.Tests/AtsGoCodeGeneratorTests.cs @@ -449,13 +449,28 @@ defer wg.Done() } } + // liveGoroutines counts goroutines from a stop-the-world dump. runtime.NumGoroutine + // reads scheduler counters without stopping the world, and during a GC cycle it + // briefly counts exited goroutines (for example the thousands left by a previous + // test) as running, so its delta can be far larger than any real growth. + func liveGoroutines() int { + buf := make([]byte, 1<<20) + for { + n := runtime.Stack(buf, true) + if n < len(buf) { + return bytes.Count(buf[:n], []byte("\ngoroutine ")) + 1 + } + buf = make([]byte, 2*len(buf)) + } + } + func TestRegisterCancellationDoesNotParkGoroutinePerToken(t *testing.T) { c := newClient("") - before := runtime.NumGoroutine() + before := liveGoroutines() for range 1000 { c.registerCancellation(NewCancellationToken()) } - if grown := runtime.NumGoroutine() - before; grown > 50 { + if grown := liveGoroutines() - before; grown > 50 { t.Fatalf("registering 1000 tokens left %d more goroutines running", grown) } } From 249207d7f1531fb2849bf544d8e455891b48b5b3 Mon Sep 17 00:00:00 2001 From: davidfowl <95136+davidfowl@users.noreply.github.com> Date: Sat, 31 Oct 2026 00:32:02 +0000 Subject: [PATCH 2/2] Remove the Go goroutine-count cancellation test Per review feedback, drop TestRegisterCancellationDoesNotParkGoroutinePerToken from the generated Go runtime hardening tests instead of deflaking it. The test measured process-wide goroutine counts, which are noisy on busy runners. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../AtsGoCodeGeneratorTests.cs | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/tests/Aspire.Hosting.CodeGeneration.Go.Tests/AtsGoCodeGeneratorTests.cs b/tests/Aspire.Hosting.CodeGeneration.Go.Tests/AtsGoCodeGeneratorTests.cs index 59478bafa13..a8387ed90b4 100644 --- a/tests/Aspire.Hosting.CodeGeneration.Go.Tests/AtsGoCodeGeneratorTests.cs +++ b/tests/Aspire.Hosting.CodeGeneration.Go.Tests/AtsGoCodeGeneratorTests.cs @@ -449,32 +449,6 @@ defer wg.Done() } } - // liveGoroutines counts goroutines from a stop-the-world dump. runtime.NumGoroutine - // reads scheduler counters without stopping the world, and during a GC cycle it - // briefly counts exited goroutines (for example the thousands left by a previous - // test) as running, so its delta can be far larger than any real growth. - func liveGoroutines() int { - buf := make([]byte, 1<<20) - for { - n := runtime.Stack(buf, true) - if n < len(buf) { - return bytes.Count(buf[:n], []byte("\ngoroutine ")) + 1 - } - buf = make([]byte, 2*len(buf)) - } - } - - func TestRegisterCancellationDoesNotParkGoroutinePerToken(t *testing.T) { - c := newClient("") - before := liveGoroutines() - for range 1000 { - c.registerCancellation(NewCancellationToken()) - } - if grown := liveGoroutines() - before; grown > 50 { - t.Fatalf("registering 1000 tokens left %d more goroutines running", grown) - } - } - func TestCancellationNotifiesHostUntilCallReturns(t *testing.T) { c, host := newFakeHost(t)