Skip to content

Commit 00ebfcd

Browse files
committed
net/http: don't cancel Request.Context on pipelined Server requests
See big comment in code. Fixes #23921 Change-Id: I2dbd1acc2e9da07a71f9e0640aafe0c59a335627 Reviewed-on: https://go-review.googlesource.com/123875 Run-TryBot: Brad Fitzpatrick <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent 6f96cf2 commit 00ebfcd

File tree

2 files changed

+44
-55
lines changed

2 files changed

+44
-55
lines changed

src/net/http/serve_test.go

+19-35
Original file line numberDiff line numberDiff line change
@@ -3175,25 +3175,32 @@ For:
31753175
ts.Close()
31763176
}
31773177

3178-
// Tests that a pipelined request causes the first request's Handler's CloseNotify
3179-
// channel to fire. Previously it deadlocked.
3178+
// Tests that a pipelined request does not cause the first request's
3179+
// Handler's CloseNotify channel to fire.
31803180
//
3181-
// Issue 13165
3181+
// Issue 13165 (where it used to deadlock), but behavior changed in Issue 23921.
31823182
func TestCloseNotifierPipelined(t *testing.T) {
3183+
setParallel(t)
31833184
defer afterTest(t)
31843185
gotReq := make(chan bool, 2)
31853186
sawClose := make(chan bool, 2)
31863187
ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) {
31873188
gotReq <- true
31883189
cc := rw.(CloseNotifier).CloseNotify()
3189-
<-cc
3190+
select {
3191+
case <-cc:
3192+
t.Error("unexpected CloseNotify")
3193+
case <-time.After(100 * time.Millisecond):
3194+
}
31903195
sawClose <- true
31913196
}))
3197+
defer ts.Close()
31923198
conn, err := net.Dial("tcp", ts.Listener.Addr().String())
31933199
if err != nil {
31943200
t.Fatalf("error dialing: %v", err)
31953201
}
31963202
diec := make(chan bool, 1)
3203+
defer close(diec)
31973204
go func() {
31983205
const req = "GET / HTTP/1.1\r\nConnection: keep-alive\r\nHost: foo\r\n\r\n"
31993206
_, err = io.WriteString(conn, req+req) // two requests
@@ -3206,27 +3213,23 @@ func TestCloseNotifierPipelined(t *testing.T) {
32063213
}()
32073214
reqs := 0
32083215
closes := 0
3209-
For:
32103216
for {
32113217
select {
32123218
case <-gotReq:
32133219
reqs++
32143220
if reqs > 2 {
32153221
t.Fatal("too many requests")
3216-
} else if reqs > 1 {
3217-
diec <- true
32183222
}
32193223
case <-sawClose:
32203224
closes++
32213225
if closes > 1 {
3222-
break For
3226+
return
32233227
}
32243228
case <-time.After(5 * time.Second):
32253229
ts.CloseClientConnections()
32263230
t.Fatal("timeout")
32273231
}
32283232
}
3229-
ts.Close()
32303233
}
32313234

32323235
func TestCloseNotifierChanLeak(t *testing.T) {
@@ -5796,31 +5799,23 @@ func TestServerHijackGetsBackgroundByte(t *testing.T) {
57965799
// Tell the client to send more data after the GET request.
57975800
inHandler <- true
57985801

5799-
// Wait until the HTTP server sees the extra data
5800-
// after the GET request. The HTTP server fires the
5801-
// close notifier here, assuming it's a pipelined
5802-
// request, as documented.
5803-
select {
5804-
case <-w.(CloseNotifier).CloseNotify():
5805-
case <-time.After(5 * time.Second):
5806-
t.Error("timeout")
5807-
return
5808-
}
5809-
58105802
conn, buf, err := w.(Hijacker).Hijack()
58115803
if err != nil {
58125804
t.Error(err)
58135805
return
58145806
}
58155807
defer conn.Close()
5816-
n := buf.Reader.Buffered()
5817-
if n != 1 {
5818-
t.Errorf("buffered data = %d; want 1", n)
5819-
}
5808+
58205809
peek, err := buf.Reader.Peek(3)
58215810
if string(peek) != "foo" || err != nil {
58225811
t.Errorf("Peek = %q, %v; want foo, nil", peek, err)
58235812
}
5813+
5814+
select {
5815+
case <-r.Context().Done():
5816+
t.Error("context unexpectedly canceled")
5817+
default:
5818+
}
58245819
}))
58255820
defer ts.Close()
58265821

@@ -5861,17 +5856,6 @@ func TestServerHijackGetsBackgroundByte_big(t *testing.T) {
58615856
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
58625857
defer close(done)
58635858

5864-
// Wait until the HTTP server sees the extra data
5865-
// after the GET request. The HTTP server fires the
5866-
// close notifier here, assuming it's a pipelined
5867-
// request, as documented.
5868-
select {
5869-
case <-w.(CloseNotifier).CloseNotify():
5870-
case <-time.After(5 * time.Second):
5871-
t.Error("timeout")
5872-
return
5873-
}
5874-
58755859
conn, buf, err := w.(Hijacker).Hijack()
58765860
if err != nil {
58775861
t.Error(err)

src/net/http/server.go

+25-20
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,9 @@ type Hijacker interface {
203203
//
204204
// This mechanism can be used to cancel long operations on the server
205205
// if the client has disconnected before the response is ready.
206+
//
207+
// Deprecated: the CloseNotifier interface predates Go's context package.
208+
// New code should use Request.Context instead.
206209
type CloseNotifier interface {
207210
// CloseNotify returns a channel that receives at most a
208211
// single value (true) when the client connection has gone
@@ -674,10 +677,28 @@ func (cr *connReader) backgroundRead() {
674677
cr.lock()
675678
if n == 1 {
676679
cr.hasByte = true
677-
// We were at EOF already (since we wouldn't be in a
678-
// background read otherwise), so this is a pipelined
679-
// HTTP request.
680-
cr.closeNotifyFromPipelinedRequest()
680+
// We were past the end of the previous request's body already
681+
// (since we wouldn't be in a background read otherwise), so
682+
// this is a pipelined HTTP request. Prior to Go 1.11 we used to
683+
// send on the CloseNotify channel and cancel the context here,
684+
// but the behavior was documented as only "may", and we only
685+
// did that because that's how CloseNotify accidentally behaved
686+
// in very early Go releases prior to context support. Once we
687+
// added context support, people used a Handler's
688+
// Request.Context() and passed it along. Having that context
689+
// cancel on pipelined HTTP requests caused problems.
690+
// Fortunately, almost nothing uses HTTP/1.x pipelining.
691+
// Unfortunately, apt-get does, or sometimes does.
692+
// New Go 1.11 behavior: don't fire CloseNotify or cancel
693+
// contexts on pipelined requests. Shouldn't affect people, but
694+
// fixes cases like Issue 23921. This does mean that a client
695+
// closing their TCP connection after sending a pipelined
696+
// request won't cancel the context, but we'll catch that on any
697+
// write failure (in checkConnErrorWriter.Write).
698+
// If the server never writes, yes, there are still contrived
699+
// server & client behaviors where this fails to ever cancel the
700+
// context, but that's kinda why HTTP/1.x pipelining died
701+
// anyway.
681702
}
682703
if ne, ok := err.(net.Error); ok && cr.aborted && ne.Timeout() {
683704
// Ignore this error. It's the expected error from
@@ -724,19 +745,6 @@ func (cr *connReader) handleReadError(_ error) {
724745
cr.closeNotify()
725746
}
726747

727-
// closeNotifyFromPipelinedRequest simply calls closeNotify.
728-
//
729-
// This method wrapper is here for documentation. The callers are the
730-
// cases where we send on the closenotify channel because of a
731-
// pipelined HTTP request, per the previous Go behavior and
732-
// documentation (that this "MAY" happen).
733-
//
734-
// TODO: consider changing this behavior and making context
735-
// cancelation and closenotify work the same.
736-
func (cr *connReader) closeNotifyFromPipelinedRequest() {
737-
cr.closeNotify()
738-
}
739-
740748
// may be called from multiple goroutines.
741749
func (cr *connReader) closeNotify() {
742750
res, _ := cr.conn.curReq.Load().(*response)
@@ -1834,9 +1842,6 @@ func (c *conn) serve(ctx context.Context) {
18341842
if requestBodyRemains(req.Body) {
18351843
registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead)
18361844
} else {
1837-
if w.conn.bufr.Buffered() > 0 {
1838-
w.conn.r.closeNotifyFromPipelinedRequest()
1839-
}
18401845
w.conn.r.startBackgroundRead()
18411846
}
18421847

0 commit comments

Comments
 (0)