From b59c7f0d464134c3c11c2df1c9119aaa64300b80 Mon Sep 17 00:00:00 2001 From: zhangwenjian Date: Sat, 5 Sep 2026 16:44:53 +0800 Subject: [PATCH] =?UTF-8?q?test=E2=9C=85:=20wait=20for=20the=20accept,=20n?= =?UTF-8?q?ot=20just=20the=20dial?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moving the dial to just before the shutdown removed one flake and introduced another: Shutdown only waits for connections the server has already accepted, so calling it in the gap between the dial and the accept finds nothing to wait for and returns cleanly. The test then fails on its own "this proves nothing" guard - which it did, after passing once. A ConnState hook closes both gaps deterministically. The connection is opened late enough not to age past the five seconds net/http stops counting it at, and the child does not proceed until the server has taken it off the listener. Ran five times in a row rather than once, because a single green run is what made the previous version look fixed. Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx --- cmd/api/signal_test.go | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/cmd/api/signal_test.go b/cmd/api/signal_test.go index 09c74c25..3e3c5531 100644 --- a/cmd/api/signal_test.go +++ b/cmd/api/signal_test.go @@ -41,7 +41,22 @@ func TestSignalChild(t *testing.T) { fmt.Println("listen:", err) os.Exit(3) } - srv := &http.Server{Handler: http.NewServeMux()} + // accepted fires once the server has taken a connection off the listener. + // Dialling is not enough: Shutdown only waits for connections the server + // has already accepted, so calling it between the dial and the accept + // finds nothing to wait for and returns immediately. + accepted := make(chan struct{}, 1) + srv := &http.Server{ + Handler: http.NewServeMux(), + ConnState: func(_ net.Conn, state http.ConnState) { + if state == http.StateNew { + select { + case accepted <- struct{}{}: + default: + } + } + }, + } go func() { _ = srv.Serve(ln) }() // Arm before announcing readiness. Doing it the other way round leaves a @@ -79,6 +94,15 @@ func TestSignalChild(t *testing.T) { } defer func() { _ = c.Close() }() + // And wait for the accept, for the opposite reason: an unaccepted + // connection is not one Shutdown waits for either. + select { + case <-accepted: + case <-time.After(10 * time.Second): + fmt.Println("the server never accepted the stalling connection") + os.Exit(6) + } + // A connection that has sent nothing keeps Shutdown busy: net/http // only treats a StateNew connection as idle once it is more than five // seconds old. A short budget makes the timeout deterministic without