Skip to content

Commit d0c6ba3

Browse files
committed
http2: close client connections after receiving GOAWAY
Once a connection has received a GOAWAY from the server, close it after the last outstanding request on the connection completes. We're lax about when we call ClientConn.closeConn, frequently closing the underlying net.Conn multiple times. Stop propagating errors on closing the net.Conn up through ClientConn.Close and ClientConn.Shutdown, since these errors are likely to be caused by double-closing the connection rather than a real fault. Fixes golang/go#39752. Change-Id: I06d59e6daa6331c3091e1d49cdbeac313f17e6bd Reviewed-on: https://go-review.googlesource.com/c/net/+/429060 TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> Reviewed-by: Cherry Mui <cherryyz@google.com> Run-TryBot: Damien Neil <dneil@google.com>
1 parent 2e0b12c commit d0c6ba3

File tree

2 files changed

+77
-11
lines changed

2 files changed

+77
-11
lines changed

‎http2/transport.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,8 @@ func (t *Transport) initConnPool() {
258258
// HTTP/2 server.
259259
type ClientConn struct {
260260
t *Transport
261-
tconn net.Conn // usually *tls.Conn, except specialized impls
261+
tconn net.Conn // usually *tls.Conn, except specialized impls
262+
tconnClosed bool
262263
tlsState *tls.ConnectionState // nil only for specialized impls
263264
reused uint32 // whether conn is being reused; atomic
264265
singleUse bool // whether being used for a single http.Request
@@ -921,10 +922,10 @@ func (cc *ClientConn) onIdleTimeout() {
921922
cc.closeIfIdle()
922923
}
923924

924-
func (cc *ClientConn) closeConn() error {
925+
func (cc *ClientConn) closeConn() {
925926
t := time.AfterFunc(250*time.Millisecond, cc.forceCloseConn)
926927
defer t.Stop()
927-
return cc.tconn.Close()
928+
cc.tconn.Close()
928929
}
929930

930931
// A tls.Conn.Close can hang for a long time if the peer is unresponsive.
@@ -990,7 +991,8 @@ func (cc *ClientConn) Shutdown(ctx context.Context) error {
990991
shutdownEnterWaitStateHook()
991992
select {
992993
case <-done:
993-
return cc.closeConn()
994+
cc.closeConn()
995+
return nil
994996
case <-ctx.Done():
995997
cc.mu.Lock()
996998
// Free the goroutine above
@@ -1027,32 +1029,33 @@ func (cc *ClientConn) sendGoAway() error {
10271029

10281030
// closes the client connection immediately. In-flight requests are interrupted.
10291031
// err is sent to streams.
1030-
func (cc *ClientConn) closeForError(err error) error {
1032+
func (cc *ClientConn) closeForError(err error) {
10311033
cc.mu.Lock()
10321034
cc.closed = true
10331035
for _, cs := range cc.streams {
10341036
cs.abortStreamLocked(err)
10351037
}
10361038
cc.cond.Broadcast()
10371039
cc.mu.Unlock()
1038-
return cc.closeConn()
1040+
cc.closeConn()
10391041
}
10401042

10411043
// Close closes the client connection immediately.
10421044
//
10431045
// In-flight requests are interrupted. For a graceful shutdown, use Shutdown instead.
10441046
func (cc *ClientConn) Close() error {
10451047
err := errors.New("http2: client connection force closed via ClientConn.Close")
1046-
return cc.closeForError(err)
1048+
cc.closeForError(err)
1049+
return nil
10471050
}
10481051

10491052
// closes the client connection immediately. In-flight requests are interrupted.
1050-
func (cc *ClientConn) closeForLostPing() error {
1053+
func (cc *ClientConn) closeForLostPing() {
10511054
err := errors.New("http2: client connection lost")
10521055
if f := cc.t.CountError; f != nil {
10531056
f("conn_close_lost_ping")
10541057
}
1055-
return cc.closeForError(err)
1058+
cc.closeForError(err)
10561059
}
10571060

10581061
// errRequestCanceled is a copy of net/http's errRequestCanceled because it's not
@@ -2005,7 +2008,7 @@ func (cc *ClientConn) forgetStreamID(id uint32) {
20052008
// wake up RoundTrip if there is a pending request.
20062009
cc.cond.Broadcast()
20072010

2008-
closeOnIdle := cc.singleUse || cc.doNotReuse || cc.t.disableKeepAlives()
2011+
closeOnIdle := cc.singleUse || cc.doNotReuse || cc.t.disableKeepAlives() || cc.goAway != nil
20092012
if closeOnIdle && cc.streamsReserved == 0 && len(cc.streams) == 0 {
20102013
if VerboseLogs {
20112014
cc.vlogf("http2: Transport closing idle conn %p (forSingleUse=%v, maxStream=%v)", cc, cc.singleUse, cc.nextStreamID-2)
@@ -2674,7 +2677,6 @@ func (rl *clientConnReadLoop) processGoAway(f *GoAwayFrame) error {
26742677
if fn := cc.t.CountError; fn != nil {
26752678
fn("recv_goaway_" + f.ErrCode.stringToken())
26762679
}
2677-
26782680
}
26792681
cc.setGoAway(f)
26802682
return nil

‎http2/transport_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5922,3 +5922,67 @@ func TestTransportSlowWrites(t *testing.T) {
59225922
}
59235923
resp.Body.Close()
59245924
}
5925+
5926+
func TestTransportClosesConnAfterGoAwayNoStreams(t *testing.T) {
5927+
testTransportClosesConnAfterGoAway(t, 0)
5928+
}
5929+
func TestTransportClosesConnAfterGoAwayLastStream(t *testing.T) {
5930+
testTransportClosesConnAfterGoAway(t, 1)
5931+
}
5932+
5933+
// testTransportClosesConnAfterGoAway verifies that the transport
5934+
// closes a connection after reading a GOAWAY from it.
5935+
//
5936+
// lastStream is the last stream ID in the GOAWAY frame.
5937+
// When 0, the transport (unsuccessfully) retries the request (stream 1);
5938+
// when 1, the transport reads the response after receiving the GOAWAY.
5939+
func testTransportClosesConnAfterGoAway(t *testing.T, lastStream uint32) {
5940+
ct := newClientTester(t)
5941+
5942+
var wg sync.WaitGroup
5943+
wg.Add(1)
5944+
ct.client = func() error {
5945+
defer wg.Done()
5946+
req, _ := http.NewRequest("GET", "https://dummy.tld/", nil)
5947+
res, err := ct.tr.RoundTrip(req)
5948+
if err == nil {
5949+
res.Body.Close()
5950+
}
5951+
if gotErr, wantErr := err != nil, lastStream == 0; gotErr != wantErr {
5952+
t.Errorf("RoundTrip got error %v (want error: %v)", err, wantErr)
5953+
}
5954+
if err = ct.cc.Close(); err == nil {
5955+
err = fmt.Errorf("expected error on Close")
5956+
} else if strings.Contains(err.Error(), "use of closed network") {
5957+
err = nil
5958+
}
5959+
return err
5960+
}
5961+
5962+
ct.server = func() error {
5963+
defer wg.Wait()
5964+
ct.greet()
5965+
hf, err := ct.firstHeaders()
5966+
if err != nil {
5967+
return fmt.Errorf("server failed reading HEADERS: %v", err)
5968+
}
5969+
if err := ct.fr.WriteGoAway(lastStream, ErrCodeNo, nil); err != nil {
5970+
return fmt.Errorf("server failed writing GOAWAY: %v", err)
5971+
}
5972+
if lastStream > 0 {
5973+
// Send a valid response to first request.
5974+
var buf bytes.Buffer
5975+
enc := hpack.NewEncoder(&buf)
5976+
enc.WriteField(hpack.HeaderField{Name: ":status", Value: "200"})
5977+
ct.fr.WriteHeaders(HeadersFrameParam{
5978+
StreamID: hf.StreamID,
5979+
EndHeaders: true,
5980+
EndStream: true,
5981+
BlockFragment: buf.Bytes(),
5982+
})
5983+
}
5984+
return nil
5985+
}
5986+
5987+
ct.run()
5988+
}

0 commit comments

Comments
 (0)