Skip to content

Commit 2a30b34

Browse files
Bryan C. Millsgopherbot
Bryan C. Mills
authored andcommitted
net/http: avoid making a request to a closed server in TestServerShutdown
As soon as the test server closes its listener, its port may be reused for another test server. On some platforms port reuse takes a long time, because they cycle through the available ports before reusing an old one. However, other platforms reuse ports much more aggressively. net/http shouldn't know or care which kind of platform it is on — dialing wild connections is risky and can interfere with other tests no matter what platform we do it on. Instead of making the second request after the server has completely shut down, we can start (and finish!) the entire request while we are certain that the listener has been closed but the port is still open serving an existing request. If everything synchronizes as we expect, that should guarantee that the second request fails. Fixes #56421. Change-Id: I56add243bb9f76ee04ead8f643118f9448fd1280 Reviewed-on: https://go-review.googlesource.com/c/go/+/476036 Auto-Submit: Bryan Mills <[email protected]> Run-TryBot: Bryan Mills <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Damien Neil <[email protected]>
1 parent f26bf20 commit 2a30b34

File tree

1 file changed

+50
-27
lines changed

1 file changed

+50
-27
lines changed

src/net/http/serve_test.go

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5454,32 +5454,54 @@ func testServerSetKeepAlivesEnabledClosesConns(t *testing.T, mode testMode) {
54545454

54555455
func TestServerShutdown(t *testing.T) { run(t, testServerShutdown) }
54565456
func testServerShutdown(t *testing.T, mode testMode) {
5457-
var doShutdown func() // set later
5458-
var doStateCount func()
5459-
var shutdownRes = make(chan error, 1)
5460-
var statesRes = make(chan map[ConnState]int, 1)
5461-
var gotOnShutdown = make(chan struct{}, 1)
5457+
var cst *clientServerTest
5458+
5459+
var once sync.Once
5460+
statesRes := make(chan map[ConnState]int, 1)
5461+
shutdownRes := make(chan error, 1)
5462+
gotOnShutdown := make(chan struct{})
54625463
handler := HandlerFunc(func(w ResponseWriter, r *Request) {
5463-
doStateCount()
5464-
go doShutdown()
5465-
// Shutdown is graceful, so it should not interrupt
5466-
// this in-flight response. Add a tiny sleep here to
5467-
// increase the odds of a failure if shutdown has
5468-
// bugs.
5469-
time.Sleep(20 * time.Millisecond)
5464+
first := false
5465+
once.Do(func() {
5466+
statesRes <- cst.ts.Config.ExportAllConnsByState()
5467+
go func() {
5468+
shutdownRes <- cst.ts.Config.Shutdown(context.Background())
5469+
}()
5470+
first = true
5471+
})
5472+
5473+
if first {
5474+
// Shutdown is graceful, so it should not interrupt this in-flight response
5475+
// but should reject new requests. (Since this request is still in flight,
5476+
// the server's port should not be reused for another server yet.)
5477+
<-gotOnShutdown
5478+
// TODO(#59038): The HTTP/2 server empirically does not always reject new
5479+
// requests. As a workaround, loop until we see a failure.
5480+
for !t.Failed() {
5481+
res, err := cst.c.Get(cst.ts.URL)
5482+
if err != nil {
5483+
break
5484+
}
5485+
out, _ := io.ReadAll(res.Body)
5486+
res.Body.Close()
5487+
if mode == http2Mode {
5488+
t.Logf("%v: unexpected success (%q). Listener should be closed before OnShutdown is called.", cst.ts.URL, out)
5489+
t.Logf("Retrying to work around https://go.dev/issue/59038.")
5490+
continue
5491+
}
5492+
t.Errorf("%v: unexpected success (%q). Listener should be closed before OnShutdown is called.", cst.ts.URL, out)
5493+
}
5494+
}
5495+
54705496
io.WriteString(w, r.RemoteAddr)
54715497
})
5472-
cst := newClientServerTest(t, mode, handler, func(srv *httptest.Server) {
5473-
srv.Config.RegisterOnShutdown(func() { gotOnShutdown <- struct{}{} })
5498+
5499+
cst = newClientServerTest(t, mode, handler, func(srv *httptest.Server) {
5500+
srv.Config.RegisterOnShutdown(func() { close(gotOnShutdown) })
54745501
})
54755502

5476-
doShutdown = func() {
5477-
shutdownRes <- cst.ts.Config.Shutdown(context.Background())
5478-
}
5479-
doStateCount = func() {
5480-
statesRes <- cst.ts.Config.ExportAllConnsByState()
5481-
}
5482-
get(t, cst.c, cst.ts.URL) // calls t.Fail on failure
5503+
out := get(t, cst.c, cst.ts.URL) // calls t.Fail on failure
5504+
t.Logf("%v: %q", cst.ts.URL, out)
54835505

54845506
if err := <-shutdownRes; err != nil {
54855507
t.Fatalf("Shutdown: %v", err)
@@ -5489,19 +5511,16 @@ func testServerShutdown(t *testing.T, mode testMode) {
54895511
if states := <-statesRes; states[StateActive] != 1 {
54905512
t.Errorf("connection in wrong state, %v", states)
54915513
}
5492-
5493-
res, err := cst.c.Get(cst.ts.URL)
5494-
if err == nil {
5495-
res.Body.Close()
5496-
t.Fatal("second request should fail. server should be shut down")
5497-
}
54985514
}
54995515

55005516
func TestServerShutdownStateNew(t *testing.T) { run(t, testServerShutdownStateNew) }
55015517
func testServerShutdownStateNew(t *testing.T, mode testMode) {
55025518
if testing.Short() {
55035519
t.Skip("test takes 5-6 seconds; skipping in short mode")
55045520
}
5521+
// The run helper runs the test in parallel only in short mode by default.
5522+
// Since this test has a very long latency, always run it in parallel.
5523+
t.Parallel()
55055524

55065525
var connAccepted sync.WaitGroup
55075526
ts := newClientServerTest(t, mode, HandlerFunc(func(w ResponseWriter, r *Request) {
@@ -5538,7 +5557,11 @@ func testServerShutdownStateNew(t *testing.T, mode testMode) {
55385557
readRes <- err
55395558
}()
55405559

5560+
// TODO(#59037): This timeout is hard-coded in closeIdleConnections.
5561+
// It is undocumented, and some users may find it surprising.
5562+
// Either document it, or switch to a less surprising behavior.
55415563
const expectTimeout = 5 * time.Second
5564+
55425565
t0 := time.Now()
55435566
select {
55445567
case got := <-shutdownRes:

0 commit comments

Comments
 (0)