Skip to content

Commit 9338bdd

Browse files
neildgopherbot
authored andcommitted
http2: speed up TestTransportHandlerBodyClose
Rewrite the slowest test in the http2 package. This test was added in CL 23287 to verify two changes: - A server handler calling req.Body.Close does not kill the request stream. - A Transport does not leak a goroutine if a request body is still being written when the request stream is closed. Split the test into two individual tests, one for the server behavior and one for the transport. Change-Id: I211f458e1001df435d00c2e1ebd7f3072e053c89 Reviewed-on: https://go-review.googlesource.com/c/net/+/700922 Reviewed-by: Nicholas Husin <[email protected]> Reviewed-by: Nicholas Husin <[email protected]> Auto-Submit: Damien Neil <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 6b20036 commit 9338bdd

File tree

2 files changed

+86
-38
lines changed

2 files changed

+86
-38
lines changed

http2/server_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5066,3 +5066,60 @@ func testServerPingResponded(t testing.TB) {
50665066
st.advance(2 * time.Second)
50675067
st.wantIdle()
50685068
}
5069+
5070+
// golang.org/issue/15425: test that a handler closing the request
5071+
// body doesn't terminate the stream to the peer. (It just stops
5072+
// readability from the handler's side, and eventually the client
5073+
// runs out of flow control tokens)
5074+
func TestServerSendDataAfterRequestBodyClose(t *testing.T) {
5075+
synctestTest(t, testServerSendDataAfterRequestBodyClose)
5076+
}
5077+
func testServerSendDataAfterRequestBodyClose(t testing.TB) {
5078+
st := newServerTester(t, nil)
5079+
st.greet()
5080+
5081+
st.writeHeaders(HeadersFrameParam{
5082+
StreamID: 1,
5083+
BlockFragment: st.encodeHeader(),
5084+
EndStream: false,
5085+
EndHeaders: true,
5086+
})
5087+
5088+
// Handler starts writing the response body.
5089+
call := st.nextHandlerCall()
5090+
call.do(func(w http.ResponseWriter, req *http.Request) {
5091+
w.Write([]byte("one"))
5092+
http.NewResponseController(w).Flush()
5093+
})
5094+
st.wantFrameType(FrameHeaders)
5095+
st.wantData(wantData{
5096+
streamID: 1,
5097+
endStream: false,
5098+
data: []byte("one"),
5099+
})
5100+
st.wantIdle()
5101+
5102+
// Handler closes the request body.
5103+
// This is not observable by the client.
5104+
call.do(func(w http.ResponseWriter, req *http.Request) {
5105+
req.Body.Close()
5106+
})
5107+
st.wantIdle()
5108+
5109+
// The client can still send request data, which is discarded.
5110+
st.writeData(1, false, []byte("client-sent data"))
5111+
st.wantIdle()
5112+
5113+
// Handler can still write more response body,
5114+
// which is sent to the client.
5115+
call.do(func(w http.ResponseWriter, req *http.Request) {
5116+
w.Write([]byte("two"))
5117+
http.NewResponseController(w).Flush()
5118+
})
5119+
st.wantData(wantData{
5120+
streamID: 1,
5121+
endStream: false,
5122+
data: []byte("two"),
5123+
})
5124+
st.wantIdle()
5125+
}

http2/transport_test.go

Lines changed: 29 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import (
2828
"net/url"
2929
"os"
3030
"reflect"
31-
"runtime"
3231
"sort"
3332
"strconv"
3433
"strings"
@@ -2341,46 +2340,38 @@ func (b neverEnding) Read(p []byte) (int, error) {
23412340
return len(p), nil
23422341
}
23432342

2344-
// golang.org/issue/15425: test that a handler closing the request
2345-
// body doesn't terminate the stream to the peer. (It just stops
2346-
// readability from the handler's side, and eventually the client
2347-
// runs out of flow control tokens)
2348-
func TestTransportHandlerBodyClose(t *testing.T) {
2349-
const bodySize = 10 << 20
2350-
ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
2351-
r.Body.Close()
2352-
io.Copy(w, io.LimitReader(neverEnding('A'), bodySize))
2353-
})
2354-
2355-
tr := &Transport{TLSClientConfig: tlsConfigInsecure}
2356-
defer tr.CloseIdleConnections()
2343+
// #15425: Transport goroutine leak while the transport is still trying to
2344+
// write its body after the stream has completed.
2345+
func TestTransportStreamEndsWhileBodyIsBeingWritten(t *testing.T) {
2346+
synctestTest(t, testTransportStreamEndsWhileBodyIsBeingWritten)
2347+
}
2348+
func testTransportStreamEndsWhileBodyIsBeingWritten(t testing.TB) {
2349+
body := "this is the client request body"
2350+
const windowSize = 10 // less than len(body)
23572351

2358-
g0 := runtime.NumGoroutine()
2352+
tc := newTestClientConn(t)
2353+
tc.greet(Setting{SettingInitialWindowSize, windowSize})
23592354

2360-
const numReq = 10
2361-
for i := 0; i < numReq; i++ {
2362-
req, err := http.NewRequest("POST", ts.URL, struct{ io.Reader }{io.LimitReader(neverEnding('A'), bodySize)})
2363-
if err != nil {
2364-
t.Fatal(err)
2365-
}
2366-
res, err := tr.RoundTrip(req)
2367-
if err != nil {
2368-
t.Fatal(err)
2369-
}
2370-
n, err := io.Copy(io.Discard, res.Body)
2371-
res.Body.Close()
2372-
if n != bodySize || err != nil {
2373-
t.Fatalf("req#%d: Copy = %d, %v; want %d, nil", i, n, err, bodySize)
2374-
}
2375-
}
2376-
tr.CloseIdleConnections()
2355+
// Client sends a request, and as much body as fits into the stream window.
2356+
req, _ := http.NewRequest("PUT", "https://dummy.tld/", strings.NewReader(body))
2357+
rt := tc.roundTrip(req)
2358+
tc.wantFrameType(FrameHeaders)
2359+
tc.wantData(wantData{
2360+
streamID: rt.streamID(),
2361+
endStream: false,
2362+
size: windowSize,
2363+
})
23772364

2378-
if !waitCondition(5*time.Second, 100*time.Millisecond, func() bool {
2379-
gd := runtime.NumGoroutine() - g0
2380-
return gd < numReq/2
2381-
}) {
2382-
t.Errorf("appeared to leak goroutines")
2383-
}
2365+
// Server responds without permitting the rest of the body to be sent.
2366+
tc.writeHeaders(HeadersFrameParam{
2367+
StreamID: rt.streamID(),
2368+
EndHeaders: true,
2369+
EndStream: true,
2370+
BlockFragment: tc.makeHeaderBlockFragment(
2371+
":status", "413",
2372+
),
2373+
})
2374+
rt.wantStatus(413)
23842375
}
23852376

23862377
// https://golang.org/issue/15930

0 commit comments

Comments
 (0)