Skip to content

Commit 99fb191

Browse files
committed
net/http: rework CloseNotifier implementation, clarify expectations in docs
CloseNotifier wasn't well specified previously. This CL simplifies its implementation, clarifies the public documentation on CloseNotifier, clarifies internal documentation on conn, and fixes two CloseNotifier bugs in the process. The main change, though, is tightening the rules and expectations for using CloseNotifier: * the caller must consume the Request.Body first (old rule, unwritten) * the received value is the "true" value (old rule, unwritten) * no promises for channel sends after Handler returns (old rule, unwritten) * a subsequent pipelined request fires the CloseNotifier (new behavior; previously it never fired and thus effectively deadlocked as in #13165) * advise that it should only be used without HTTP/1.1 pipelining (use HTTP/2 or non-idempotent browsers). Not that browsers actually use pipelining. The main implementation change is that each Handler now gets its own CloseNotifier channel value, rather than sharing one between the whole conn. This means Handlers can't affect subsequent requests. This is how HTTP/2's Server works too. The old docs never clarified a behavior either way. The other side effect of each request getting its own CloseNotifier channel is that one handler can't "poison" the underlying conn preventing subsequent requests on the same connection from using CloseNotifier (this is #9763). In the old implementation, once any request on a connection used ClosedNotifier, the conn's underlying bufio.Reader source was switched from the TCPConn to the read side of the pipe being fed by a never-ending copy. Since it was impossible to abort that never-ending copy, we could never get back to a fresh state where it was possible to return the underlying TCPConn to callers of Hijack. Now, instead of a never-ending Copy, the background goroutine doing a Read from the TCPConn (or *tls.Conn) only reads a single byte. That single byte can be in the request body, a socket timeout error, io.EOF error, or the first byte of the second body. In any case, the new *connReader type stitches sync and async reads together like an io.MultiReader. To clarify the flow of Read data and combat the complexity of too many wrapper Reader types, the *connReader absorbs the io.LimitReader previously used for bounding request header reads. The liveSwitchReader type is removed. (an unused switchWriter type is also removed) Many fields on *conn are also documented more fully. Fixes #9763 (CloseNotify + Hijack together) Fixes #13165 (deadlock with CloseNotify + pipelined requests) Change-Id: I40abc0a1992d05b294d627d1838c33cbccb9dd65 Reviewed-on: https://go-review.googlesource.com/17750 Reviewed-by: Russ Cox <[email protected]> Run-TryBot: Brad Fitzpatrick <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent 01baf13 commit 99fb191

File tree

2 files changed

+361
-161
lines changed

2 files changed

+361
-161
lines changed

src/net/http/serve_test.go

Lines changed: 129 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,16 @@ func (c *rwTestConn) Close() error {
9797
}
9898

9999
type testConn struct {
100+
readMu sync.Mutex // for TestHandlerBodyClose
100101
readBuf bytes.Buffer
101102
writeBuf bytes.Buffer
102103
closec chan bool // if non-nil, send value to it on close
103104
noopConn
104105
}
105106

106107
func (c *testConn) Read(b []byte) (int, error) {
108+
c.readMu.Lock()
109+
defer c.readMu.Unlock()
107110
return c.readBuf.Read(b)
108111
}
109112

@@ -1246,15 +1249,21 @@ func TestServerUnreadRequestBodyLittle(t *testing.T) {
12461249

12471250
done := make(chan bool)
12481251

1252+
readBufLen := func() int {
1253+
conn.readMu.Lock()
1254+
defer conn.readMu.Unlock()
1255+
return conn.readBuf.Len()
1256+
}
1257+
12491258
ls := &oneConnListener{conn}
12501259
go Serve(ls, HandlerFunc(func(rw ResponseWriter, req *Request) {
12511260
defer close(done)
1252-
if conn.readBuf.Len() < len(body)/2 {
1253-
t.Errorf("on request, read buffer length is %d; expected about 100 KB", conn.readBuf.Len())
1261+
if bufLen := readBufLen(); bufLen < len(body)/2 {
1262+
t.Errorf("on request, read buffer length is %d; expected about 100 KB", bufLen)
12541263
}
12551264
rw.WriteHeader(200)
12561265
rw.(Flusher).Flush()
1257-
if g, e := conn.readBuf.Len(), 0; g != e {
1266+
if g, e := readBufLen(), 0; g != e {
12581267
t.Errorf("after WriteHeader, read buffer length is %d; want %d", g, e)
12591268
}
12601269
if c := rw.Header().Get("Connection"); c != "" {
@@ -1430,15 +1439,21 @@ func testHandlerBodyClose(t *testing.T, i int, tt handlerBodyCloseTest) {
14301439
}
14311440
conn.closec = make(chan bool, 1)
14321441

1442+
readBufLen := func() int {
1443+
conn.readMu.Lock()
1444+
defer conn.readMu.Unlock()
1445+
return conn.readBuf.Len()
1446+
}
1447+
14331448
ls := &oneConnListener{conn}
14341449
var numReqs int
14351450
var size0, size1 int
14361451
go Serve(ls, HandlerFunc(func(rw ResponseWriter, req *Request) {
14371452
numReqs++
14381453
if numReqs == 1 {
1439-
size0 = conn.readBuf.Len()
1454+
size0 = readBufLen()
14401455
req.Body.Close()
1441-
size1 = conn.readBuf.Len()
1456+
size1 = readBufLen()
14421457
}
14431458
}))
14441459
<-conn.closec
@@ -1538,7 +1553,9 @@ type slowTestConn struct {
15381553
// over multiple calls to Read, time.Durations are slept, strings are read.
15391554
script []interface{}
15401555
closec chan bool
1541-
rd, wd time.Time // read, write deadline
1556+
1557+
mu sync.Mutex // guards rd/wd
1558+
rd, wd time.Time // read, write deadline
15421559
noopConn
15431560
}
15441561

@@ -1549,16 +1566,22 @@ func (c *slowTestConn) SetDeadline(t time.Time) error {
15491566
}
15501567

15511568
func (c *slowTestConn) SetReadDeadline(t time.Time) error {
1569+
c.mu.Lock()
1570+
defer c.mu.Unlock()
15521571
c.rd = t
15531572
return nil
15541573
}
15551574

15561575
func (c *slowTestConn) SetWriteDeadline(t time.Time) error {
1576+
c.mu.Lock()
1577+
defer c.mu.Unlock()
15571578
c.wd = t
15581579
return nil
15591580
}
15601581

15611582
func (c *slowTestConn) Read(b []byte) (n int, err error) {
1583+
c.mu.Lock()
1584+
defer c.mu.Unlock()
15621585
restart:
15631586
if !c.rd.IsZero() && time.Now().After(c.rd) {
15641587
return 0, syscall.ETIMEDOUT
@@ -2330,6 +2353,49 @@ For:
23302353
ts.Close()
23312354
}
23322355

2356+
// Tests that a pipelined request causes the first request's Handler's CloseNotify
2357+
// channel to fire. Previously it deadlocked.
2358+
//
2359+
// Issue 13165
2360+
func TestCloseNotifierPipelined(t *testing.T) {
2361+
defer afterTest(t)
2362+
gotReq := make(chan bool, 2)
2363+
sawClose := make(chan bool, 2)
2364+
ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) {
2365+
gotReq <- true
2366+
cc := rw.(CloseNotifier).CloseNotify()
2367+
<-cc
2368+
sawClose <- true
2369+
}))
2370+
conn, err := net.Dial("tcp", ts.Listener.Addr().String())
2371+
if err != nil {
2372+
t.Fatalf("error dialing: %v", err)
2373+
}
2374+
diec := make(chan bool, 2)
2375+
go func() {
2376+
const req = "GET / HTTP/1.1\r\nConnection: keep-alive\r\nHost: foo\r\n\r\n"
2377+
_, err = io.WriteString(conn, req+req) // two requests
2378+
if err != nil {
2379+
t.Fatal(err)
2380+
}
2381+
<-diec
2382+
conn.Close()
2383+
}()
2384+
For:
2385+
for {
2386+
select {
2387+
case <-gotReq:
2388+
diec <- true
2389+
case <-sawClose:
2390+
break For
2391+
case <-time.After(5 * time.Second):
2392+
ts.CloseClientConnections()
2393+
t.Fatal("timeout")
2394+
}
2395+
}
2396+
ts.Close()
2397+
}
2398+
23332399
func TestCloseNotifierChanLeak(t *testing.T) {
23342400
defer afterTest(t)
23352401
req := reqBytes("GET / HTTP/1.0\nHost: golang.org")
@@ -2352,6 +2418,61 @@ func TestCloseNotifierChanLeak(t *testing.T) {
23522418
}
23532419
}
23542420

2421+
// Tests that we can use CloseNotifier in one request, and later call Hijack
2422+
// on a second request on the same connection.
2423+
//
2424+
// It also tests that the connReader stitches together its background
2425+
// 1-byte read for CloseNotifier when CloseNotifier doesn't fire with
2426+
// the rest of the second HTTP later.
2427+
//
2428+
// Issue 9763.
2429+
// HTTP/1-only test. (http2 doesn't have Hijack)
2430+
func TestHijackAfterCloseNotifier(t *testing.T) {
2431+
defer afterTest(t)
2432+
script := make(chan string, 2)
2433+
script <- "closenotify"
2434+
script <- "hijack"
2435+
close(script)
2436+
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
2437+
plan := <-script
2438+
switch plan {
2439+
default:
2440+
panic("bogus plan; too many requests")
2441+
case "closenotify":
2442+
w.(CloseNotifier).CloseNotify() // discard result
2443+
w.Header().Set("X-Addr", r.RemoteAddr)
2444+
case "hijack":
2445+
c, _, err := w.(Hijacker).Hijack()
2446+
if err != nil {
2447+
t.Errorf("Hijack in Handler: %v", err)
2448+
return
2449+
}
2450+
if _, ok := c.(*net.TCPConn); !ok {
2451+
// Verify it's not wrapped in some type.
2452+
// Not strictly a go1 compat issue, but in practice it probably is.
2453+
t.Errorf("type of hijacked conn is %T; want *net.TCPConn", c)
2454+
}
2455+
fmt.Fprintf(c, "HTTP/1.0 200 OK\r\nX-Addr: %v\r\nContent-Length: 0\r\n\r\n", r.RemoteAddr)
2456+
c.Close()
2457+
return
2458+
}
2459+
}))
2460+
defer ts.Close()
2461+
res1, err := Get(ts.URL)
2462+
if err != nil {
2463+
log.Fatal(err)
2464+
}
2465+
res2, err := Get(ts.URL)
2466+
if err != nil {
2467+
log.Fatal(err)
2468+
}
2469+
addr1 := res1.Header.Get("X-Addr")
2470+
addr2 := res2.Header.Get("X-Addr")
2471+
if addr1 == "" || addr1 != addr2 {
2472+
t.Errorf("addr1, addr2 = %q, %q; want same", addr1, addr2)
2473+
}
2474+
}
2475+
23552476
func TestOptions(t *testing.T) {
23562477
uric := make(chan string, 2) // only expect 1, but leave space for 2
23572478
mux := NewServeMux()
@@ -2702,7 +2823,7 @@ func TestHTTP10ConnectionHeader(t *testing.T) {
27022823
defer afterTest(t)
27032824

27042825
mux := NewServeMux()
2705-
mux.Handle("/", HandlerFunc(func(resp ResponseWriter, req *Request) {}))
2826+
mux.Handle("/", HandlerFunc(func(ResponseWriter, *Request) {}))
27062827
ts := httptest.NewServer(mux)
27072828
defer ts.Close()
27082829

@@ -3248,10 +3369,7 @@ func (c *closeWriteTestConn) CloseWrite() error {
32483369
func TestCloseWrite(t *testing.T) {
32493370
var srv Server
32503371
var testConn closeWriteTestConn
3251-
c, err := ExportServerNewConn(&srv, &testConn)
3252-
if err != nil {
3253-
t.Fatal(err)
3254-
}
3372+
c := ExportServerNewConn(&srv, &testConn)
32553373
ExportCloseWriteAndWait(c)
32563374
if !testConn.didCloseWrite {
32573375
t.Error("didn't see CloseWrite call")

0 commit comments

Comments
 (0)