fix(client): keep the run's exit signal readable after supervisor Stop

Stop cleared the done channel, so a waiter that raced a teardown got nil
from Done() and selected on a nil channel: an Up racing a Down hung in
waitForUp for its full 50s timeout where it previously failed fast with
"client gave up to connect". The same clearing made Alive() report a
timed-out run dead while its goroutine was still tearing down, letting
Up start an overlapping second run.

Keep the channel in place instead: the exiting run closes it, so late
waiters observe the real exit and Alive() stays true until the run is
actually gone — the lifecycle clientGiveUpChan used to have. Stop still
drops the current client and invalidates the generation.

The stale-running-chan test begins a fresh run before waiting, as Up
does, so the kept exit signal does not race the stale clientRunningChan.
This commit is contained in:
Zoltán Papp
2026-08-27 11:27:25 +02:00
parent 5df35e3e27
commit 3615c01fe3
3 changed files with 26 additions and 3 deletions
+5
View File
@@ -190,6 +190,11 @@ func TestDownThenUp_StaleRunningChan(t *testing.T) {
assert.False(t, s.clientRunning, "clientRunning should be cleared by cleanupConnection (intent = down)")
s.mutex.Unlock()
// A fresh Up begins a new run before waiting; without it the previous
// run's kept exit signal would race the stale clientRunningChan below.
_, nextDone := s.runs.Begin()
defer close(nextDone)
// waitForUp() returns immediately due to stale closed clientRunningChan
waitCtx, ctxCancel := context.WithTimeout(context.Background(), 2*time.Second)
defer ctxCancel()