1
0
mirror of https://github.com/golang/go synced 2024-11-07 15:36:23 -07:00

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 <bradfitz@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
This commit is contained in:
Brad Fitzpatrick 2018-07-13 19:31:35 +00:00
parent 6f96cf2793
commit 00ebfcde7f
2 changed files with 44 additions and 55 deletions

View File

@ -3175,25 +3175,32 @@ For:
ts.Close() ts.Close()
} }
// Tests that a pipelined request causes the first request's Handler's CloseNotify // Tests that a pipelined request does not cause the first request's
// channel to fire. Previously it deadlocked. // Handler's CloseNotify channel to fire.
// //
// Issue 13165 // Issue 13165 (where it used to deadlock), but behavior changed in Issue 23921.
func TestCloseNotifierPipelined(t *testing.T) { func TestCloseNotifierPipelined(t *testing.T) {
setParallel(t)
defer afterTest(t) defer afterTest(t)
gotReq := make(chan bool, 2) gotReq := make(chan bool, 2)
sawClose := make(chan bool, 2) sawClose := make(chan bool, 2)
ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) { ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) {
gotReq <- true gotReq <- true
cc := rw.(CloseNotifier).CloseNotify() cc := rw.(CloseNotifier).CloseNotify()
<-cc select {
case <-cc:
t.Error("unexpected CloseNotify")
case <-time.After(100 * time.Millisecond):
}
sawClose <- true sawClose <- true
})) }))
defer ts.Close()
conn, err := net.Dial("tcp", ts.Listener.Addr().String()) conn, err := net.Dial("tcp", ts.Listener.Addr().String())
if err != nil { if err != nil {
t.Fatalf("error dialing: %v", err) t.Fatalf("error dialing: %v", err)
} }
diec := make(chan bool, 1) diec := make(chan bool, 1)
defer close(diec)
go func() { go func() {
const req = "GET / HTTP/1.1\r\nConnection: keep-alive\r\nHost: foo\r\n\r\n" const req = "GET / HTTP/1.1\r\nConnection: keep-alive\r\nHost: foo\r\n\r\n"
_, err = io.WriteString(conn, req+req) // two requests _, err = io.WriteString(conn, req+req) // two requests
@ -3206,27 +3213,23 @@ func TestCloseNotifierPipelined(t *testing.T) {
}() }()
reqs := 0 reqs := 0
closes := 0 closes := 0
For:
for { for {
select { select {
case <-gotReq: case <-gotReq:
reqs++ reqs++
if reqs > 2 { if reqs > 2 {
t.Fatal("too many requests") t.Fatal("too many requests")
} else if reqs > 1 {
diec <- true
} }
case <-sawClose: case <-sawClose:
closes++ closes++
if closes > 1 { if closes > 1 {
break For return
} }
case <-time.After(5 * time.Second): case <-time.After(5 * time.Second):
ts.CloseClientConnections() ts.CloseClientConnections()
t.Fatal("timeout") t.Fatal("timeout")
} }
} }
ts.Close()
} }
func TestCloseNotifierChanLeak(t *testing.T) { func TestCloseNotifierChanLeak(t *testing.T) {
@ -5796,31 +5799,23 @@ func TestServerHijackGetsBackgroundByte(t *testing.T) {
// Tell the client to send more data after the GET request. // Tell the client to send more data after the GET request.
inHandler <- true inHandler <- true
// Wait until the HTTP server sees the extra data
// after the GET request. The HTTP server fires the
// close notifier here, assuming it's a pipelined
// request, as documented.
select {
case <-w.(CloseNotifier).CloseNotify():
case <-time.After(5 * time.Second):
t.Error("timeout")
return
}
conn, buf, err := w.(Hijacker).Hijack() conn, buf, err := w.(Hijacker).Hijack()
if err != nil { if err != nil {
t.Error(err) t.Error(err)
return return
} }
defer conn.Close() defer conn.Close()
n := buf.Reader.Buffered()
if n != 1 {
t.Errorf("buffered data = %d; want 1", n)
}
peek, err := buf.Reader.Peek(3) peek, err := buf.Reader.Peek(3)
if string(peek) != "foo" || err != nil { if string(peek) != "foo" || err != nil {
t.Errorf("Peek = %q, %v; want foo, nil", peek, err) t.Errorf("Peek = %q, %v; want foo, nil", peek, err)
} }
select {
case <-r.Context().Done():
t.Error("context unexpectedly canceled")
default:
}
})) }))
defer ts.Close() defer ts.Close()
@ -5861,17 +5856,6 @@ func TestServerHijackGetsBackgroundByte_big(t *testing.T) {
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
defer close(done) defer close(done)
// Wait until the HTTP server sees the extra data
// after the GET request. The HTTP server fires the
// close notifier here, assuming it's a pipelined
// request, as documented.
select {
case <-w.(CloseNotifier).CloseNotify():
case <-time.After(5 * time.Second):
t.Error("timeout")
return
}
conn, buf, err := w.(Hijacker).Hijack() conn, buf, err := w.(Hijacker).Hijack()
if err != nil { if err != nil {
t.Error(err) t.Error(err)

View File

@ -203,6 +203,9 @@ type Hijacker interface {
// //
// This mechanism can be used to cancel long operations on the server // This mechanism can be used to cancel long operations on the server
// if the client has disconnected before the response is ready. // if the client has disconnected before the response is ready.
//
// Deprecated: the CloseNotifier interface predates Go's context package.
// New code should use Request.Context instead.
type CloseNotifier interface { type CloseNotifier interface {
// CloseNotify returns a channel that receives at most a // CloseNotify returns a channel that receives at most a
// single value (true) when the client connection has gone // single value (true) when the client connection has gone
@ -674,10 +677,28 @@ func (cr *connReader) backgroundRead() {
cr.lock() cr.lock()
if n == 1 { if n == 1 {
cr.hasByte = true cr.hasByte = true
// We were at EOF already (since we wouldn't be in a // We were past the end of the previous request's body already
// background read otherwise), so this is a pipelined // (since we wouldn't be in a background read otherwise), so
// HTTP request. // this is a pipelined HTTP request. Prior to Go 1.11 we used to
cr.closeNotifyFromPipelinedRequest() // send on the CloseNotify channel and cancel the context here,
// but the behavior was documented as only "may", and we only
// did that because that's how CloseNotify accidentally behaved
// in very early Go releases prior to context support. Once we
// added context support, people used a Handler's
// Request.Context() and passed it along. Having that context
// cancel on pipelined HTTP requests caused problems.
// Fortunately, almost nothing uses HTTP/1.x pipelining.
// Unfortunately, apt-get does, or sometimes does.
// New Go 1.11 behavior: don't fire CloseNotify or cancel
// contexts on pipelined requests. Shouldn't affect people, but
// fixes cases like Issue 23921. This does mean that a client
// closing their TCP connection after sending a pipelined
// request won't cancel the context, but we'll catch that on any
// write failure (in checkConnErrorWriter.Write).
// If the server never writes, yes, there are still contrived
// server & client behaviors where this fails to ever cancel the
// context, but that's kinda why HTTP/1.x pipelining died
// anyway.
} }
if ne, ok := err.(net.Error); ok && cr.aborted && ne.Timeout() { if ne, ok := err.(net.Error); ok && cr.aborted && ne.Timeout() {
// Ignore this error. It's the expected error from // Ignore this error. It's the expected error from
@ -724,19 +745,6 @@ func (cr *connReader) handleReadError(_ error) {
cr.closeNotify() cr.closeNotify()
} }
// closeNotifyFromPipelinedRequest simply calls closeNotify.
//
// This method wrapper is here for documentation. The callers are the
// cases where we send on the closenotify channel because of a
// pipelined HTTP request, per the previous Go behavior and
// documentation (that this "MAY" happen).
//
// TODO: consider changing this behavior and making context
// cancelation and closenotify work the same.
func (cr *connReader) closeNotifyFromPipelinedRequest() {
cr.closeNotify()
}
// may be called from multiple goroutines. // may be called from multiple goroutines.
func (cr *connReader) closeNotify() { func (cr *connReader) closeNotify() {
res, _ := cr.conn.curReq.Load().(*response) res, _ := cr.conn.curReq.Load().(*response)
@ -1834,9 +1842,6 @@ func (c *conn) serve(ctx context.Context) {
if requestBodyRemains(req.Body) { if requestBodyRemains(req.Body) {
registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead) registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead)
} else { } else {
if w.conn.bufr.Buffered() > 0 {
w.conn.r.closeNotifyFromPipelinedRequest()
}
w.conn.r.startBackgroundRead() w.conn.r.startBackgroundRead()
} }