Skip to content

Commit 9f24bb4

Browse files
committed
http2: properly discard data received after request/response body is closed
A server handler can close an inbound Request.Body to indicate that it is not interested in the remainder of the request body. Equivalently, a client can close a Response.Body indicate that it is not interesed in the remainder of the response body. In both cases, if we receive DATA frames from the peer for the stream, we should return connection-level flow control credit for the discarded data. We do not return stream-level flow control, since we don't want to unblock further sends of data that we're just going to discard. Closing either a Response.Body or an inbound Request.Body results in a pipe.BreakWithError. Reads from a broken pipe fail immediately. Previously, writes to a broken pipe would succeed, discarding the written data and incrementing the pipe's unread count. Silently discarding data written to a broken pipe results in both the Transport and Server failing to detect the condition where data has been discarded. Change pipes to return an error when writing to a broken pipe. Change transportResponseBody.Close to break the response body before returning flow control credit for unread data in the pipe, avoiding a race condition where data is added to the pipe in between the return of flow control credit and the pipe breaking. Change the Server to treat an error writing to the inbound request body as an expected condition (since this only happens when a handler closes the request body), returning connection-level flow control credit for the discarded data. Fixes golang/go#57578 Change-Id: I1ed4ea9865818f9c7d7eb4500edfd7556e3cbcbf Reviewed-on: https://go-review.googlesource.com/c/net/+/475135 Run-TryBot: Damien Neil <dneil@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Roland Shoemaker <roland@golang.org>
1 parent 08dda57 commit 9f24bb4

File tree

5 files changed

+39
-14
lines changed

5 files changed

+39
-14
lines changed

http2/pipe.go

+1-5
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,9 @@ func (p *pipe) Write(d []byte) (n int, err error) {
8888
p.c.L = &p.mu
8989
}
9090
defer p.c.Signal()
91-
if p.err != nil {
91+
if p.err != nil || p.breakErr != nil {
9292
return 0, errClosedPipeWrite
9393
}
94-
if p.breakErr != nil {
95-
p.unread += len(d)
96-
return len(d), nil // discard when there is no reader
97-
}
9894
return p.b.Write(d)
9995
}
10096

http2/pipe_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -125,14 +125,14 @@ func TestPipeBreakWithError(t *testing.T) {
125125
if p.Len() != 3 {
126126
t.Errorf("pipe should have 3 unread bytes")
127127
}
128-
// Write should succeed silently.
129-
if n, err := p.Write([]byte("abc")); err != nil || n != 3 {
130-
t.Errorf("Write(abc) after break\ngot %v, %v\nwant 0, nil", n, err)
128+
// Write should fail.
129+
if n, err := p.Write([]byte("abc")); err != errClosedPipeWrite || n != 0 {
130+
t.Errorf("Write(abc) after break\ngot %v, %v\nwant 0, errClosedPipeWrite", n, err)
131131
}
132132
if p.b != nil {
133133
t.Errorf("buffer should be nil after Write")
134134
}
135-
if p.Len() != 6 {
135+
if p.Len() != 3 {
136136
t.Errorf("pipe should have 6 unread bytes")
137137
}
138138
// Read should fail.

http2/server.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -1822,15 +1822,18 @@ func (sc *serverConn) processData(f *DataFrame) error {
18221822
}
18231823

18241824
if len(data) > 0 {
1825+
st.bodyBytes += int64(len(data))
18251826
wrote, err := st.body.Write(data)
18261827
if err != nil {
1828+
// The handler has closed the request body.
1829+
// Return the connection-level flow control for the discarded data,
1830+
// but not the stream-level flow control.
18271831
sc.sendWindowUpdate(nil, int(f.Length)-wrote)
1828-
return sc.countError("body_write_err", streamError(id, ErrCodeStreamClosed))
1832+
return nil
18291833
}
18301834
if wrote != len(data) {
18311835
panic("internal error: bad Writer")
18321836
}
1833-
st.bodyBytes += int64(len(data))
18341837
}
18351838

18361839
// Return any padded flow control now, since we won't

http2/server_test.go

+26
Original file line numberDiff line numberDiff line change
@@ -3906,6 +3906,32 @@ func TestUnreadFlowControlReturned_Server(t *testing.T) {
39063906
}
39073907
}
39083908

3909+
func TestServerReturnsStreamAndConnFlowControlOnBodyClose(t *testing.T) {
3910+
unblockHandler := make(chan struct{})
3911+
defer close(unblockHandler)
3912+
3913+
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
3914+
r.Body.Close()
3915+
w.WriteHeader(200)
3916+
w.(http.Flusher).Flush()
3917+
<-unblockHandler
3918+
})
3919+
defer st.Close()
3920+
3921+
st.greet()
3922+
st.writeHeaders(HeadersFrameParam{
3923+
StreamID: 1,
3924+
BlockFragment: st.encodeHeader(),
3925+
EndHeaders: true,
3926+
})
3927+
st.wantHeaders()
3928+
const size = inflowMinRefresh // enough to trigger flow control return
3929+
st.writeData(1, false, make([]byte, size))
3930+
st.wantWindowUpdate(0, size) // conn-level flow control is returned
3931+
unblockHandler <- struct{}{}
3932+
st.wantData()
3933+
}
3934+
39093935
func TestServerIdleTimeout(t *testing.T) {
39103936
if testing.Short() {
39113937
t.Skip("skipping in short mode")

http2/transport.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -2555,6 +2555,9 @@ func (b transportResponseBody) Close() error {
25552555
cs := b.cs
25562556
cc := cs.cc
25572557

2558+
cs.bufPipe.BreakWithError(errClosedResponseBody)
2559+
cs.abortStream(errClosedResponseBody)
2560+
25582561
unread := cs.bufPipe.Len()
25592562
if unread > 0 {
25602563
cc.mu.Lock()
@@ -2573,9 +2576,6 @@ func (b transportResponseBody) Close() error {
25732576
cc.wmu.Unlock()
25742577
}
25752578

2576-
cs.bufPipe.BreakWithError(errClosedResponseBody)
2577-
cs.abortStream(errClosedResponseBody)
2578-
25792579
select {
25802580
case <-cs.donec:
25812581
case <-cs.ctx.Done():

0 commit comments

Comments
 (0)