Skip to content

Commit 16c245f

Browse files
committed
test(stress): fix goroutine-leak baseline to be per-rep
The global baseline (taken once before rep 1) was stale by the time reps 2 and 3 ran: goroutines from other parallel tests that started DURING the 30 s rep inflated the post-rep count and triggered false positive leak reports in CI. Take a fresh baseline immediately before each rep so the delta measures only goroutines created (and not drained) by that specific rep's workload. runRep already guarantees all 1000 worker goroutines drain via wg.Wait, so the tolerance=10 margin is enough for any short-lived daemon-internal cleanup goroutines.
1 parent 4c8599a commit 16c245f

1 file changed

Lines changed: 17 additions & 15 deletions

File tree

tests/zz_concurrent_dial_encrypt_decrypt_stress_test.go

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -127,28 +127,31 @@ func TestConcurrentDialEncryptDecrypt(t *testing.T) {
127127
}(port)
128128
}
129129

130-
// Capture baseline goroutine count AFTER listeners are warm so the
131-
// per-rep delta only reflects the workload goroutines.
130+
// Warm listeners, then allow any ambient parallel-test goroutines to
131+
// stabilise before the first rep.
132132
time.Sleep(200 * time.Millisecond)
133133
runtime.GC()
134-
baselineGoroutines := runtime.NumGoroutine()
135-
t.Logf("baseline goroutine count: %d", baselineGoroutines)
134+
t.Logf("pre-rep goroutine count: %d", runtime.NumGoroutine())
136135

137136
totalStart := time.Now()
138137

139138
for rep := 0; rep < numReps; rep++ {
140-
t.Logf("--- rep %d/%d (deadline %s) ---", rep+1, numReps, repDuration)
139+
// Snapshot baseline immediately before launching the rep's
140+
// workload so the post-rep delta measures only goroutines that
141+
// THIS rep created but did not drain. Using a single global
142+
// baseline (taken before rep 1) was incorrect: over the 30 s
143+
// rep other parallel tests start up and inflate the count,
144+
// producing false-positive leak reports in CI.
145+
runtime.GC()
146+
baselineGoroutines := runtime.NumGoroutine()
147+
t.Logf("--- rep %d/%d (deadline %s, baseline goroutines %d) ---",
148+
rep+1, numReps, repDuration, baselineGoroutines)
141149
runRep(t, a, b, listeners, repDuration, dialGoroutines, decryptGoroutines, rekeyGoroutines, heartbeatGoroutines)
142150

143-
// Goroutine drain check: poll up to 5 s for the count to fall
144-
// back within tolerance of baseline. The doc spec
145-
// (05-VERIFICATION.md §4.8) is "no goroutine deadlock" — i.e.
146-
// goroutines must drain eventually, not within an arbitrary
147-
// short window. Same pattern as T0.1
148-
// (TestDaemonShutdownStopsGoroutines) which gives 5 s grace.
149-
// Under §4.8's stress workload, a handful of conns may still
150-
// be mid-FIN/TimeWait at rep stop; the deadline-polled loop
151-
// gives them room to complete without flapping the gate.
151+
// runRep calls wg.Wait() so all 1000 worker goroutines have
152+
// already exited by the time we get here. The poll below catches
153+
// goroutines NOT tracked by wg (e.g. daemon-internal goroutines
154+
// started as a side-effect of the workload that didn't clean up).
152155
runtime.GC()
153156
drainDeadline := time.Now().Add(5 * time.Second)
154157
var now int
@@ -162,7 +165,6 @@ func TestConcurrentDialEncryptDecrypt(t *testing.T) {
162165
break
163166
}
164167
if time.Now().After(drainDeadline) {
165-
// Dump goroutine stacks so we can identify the leaker.
166168
buf := make([]byte, 1<<20)
167169
n := runtime.Stack(buf, true)
168170
t.Errorf("rep %d: goroutine leak detected: %d goroutines, baseline=%d, delta=%d (tolerance %d, waited 5s)\n%s",

0 commit comments

Comments
 (0)