Skip to content

Commit f585440

Browse files
fraenkelneild
authored andcommitted
http2: close Transport connection on write errors
When a new connection is created, and a write error occurs during the initial exchange, the connection must be closed. There is no guarantee that the caller will close the connection. When a connection with an existing write error is used or being used, it will stay in use until its read loop completes. Requests will continue to use this connection and fail when writing its header. These connections should be closed to force the cleanup in its readLoop. Fixes golang/go#39337 Change-Id: I45e1293309e40629531f4cbb69387864f4f71bc2 Reviewed-on: https://go-review.googlesource.com/c/net/+/240337 Reviewed-by: Brad Fitzpatrick <[email protected]> Run-TryBot: Brad Fitzpatrick <[email protected]> TryBot-Result: Go Bot <[email protected]> Trust: Brad Fitzpatrick <[email protected]> Trust: Damien Neil <[email protected]>
1 parent d65d470 commit f585440

File tree

2 files changed

+72
-0
lines changed

2 files changed

+72
-0
lines changed

http2/transport.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,7 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
689689
cc.inflow.add(transportDefaultConnFlow + initialWindowSize)
690690
cc.bw.Flush()
691691
if cc.werr != nil {
692+
cc.Close()
692693
return nil, cc.werr
693694
}
694695

@@ -1080,6 +1081,15 @@ func (cc *ClientConn) roundTrip(req *http.Request) (res *http.Response, gotErrAf
10801081
bodyWriter := cc.t.getBodyWriterState(cs, body)
10811082
cs.on100 = bodyWriter.on100
10821083

1084+
defer func() {
1085+
cc.wmu.Lock()
1086+
werr := cc.werr
1087+
cc.wmu.Unlock()
1088+
if werr != nil {
1089+
cc.Close()
1090+
}
1091+
}()
1092+
10831093
cc.wmu.Lock()
10841094
endStream := !hasBody && !hasTrailers
10851095
werr := cc.writeHeaders(cs.ID, endStream, int(cc.maxFrameSize), hdrs)

http2/transport_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4795,3 +4795,65 @@ func TestClientConnTooIdle(t *testing.T) {
47954795
}
47964796
}
47974797
}
4798+
4799+
type fakeConnErr struct {
4800+
net.Conn
4801+
writeErr error
4802+
closed bool
4803+
}
4804+
4805+
func (fce *fakeConnErr) Write(b []byte) (n int, err error) {
4806+
return 0, fce.writeErr
4807+
}
4808+
4809+
func (fce *fakeConnErr) Close() error {
4810+
fce.closed = true
4811+
return nil
4812+
}
4813+
4814+
// issue 39337: close the connection on a failed write
4815+
func TestTransportNewClientConnCloseOnWriteError(t *testing.T) {
4816+
tr := &Transport{}
4817+
writeErr := errors.New("write error")
4818+
fakeConn := &fakeConnErr{writeErr: writeErr}
4819+
_, err := tr.NewClientConn(fakeConn)
4820+
if err != writeErr {
4821+
t.Fatalf("expected %v, got %v", writeErr, err)
4822+
}
4823+
if !fakeConn.closed {
4824+
t.Error("expected closed conn")
4825+
}
4826+
}
4827+
4828+
func TestTransportRoundtripCloseOnWriteError(t *testing.T) {
4829+
req, err := http.NewRequest("GET", "https://dummy.tld/", nil)
4830+
if err != nil {
4831+
t.Fatal(err)
4832+
}
4833+
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, optOnlyServer)
4834+
defer st.Close()
4835+
4836+
tr := &Transport{TLSClientConfig: tlsConfigInsecure}
4837+
defer tr.CloseIdleConnections()
4838+
cc, err := tr.dialClientConn(st.ts.Listener.Addr().String(), false)
4839+
if err != nil {
4840+
t.Fatal(err)
4841+
}
4842+
4843+
writeErr := errors.New("write error")
4844+
cc.wmu.Lock()
4845+
cc.werr = writeErr
4846+
cc.wmu.Unlock()
4847+
4848+
_, err = cc.RoundTrip(req)
4849+
if err != writeErr {
4850+
t.Fatalf("expected %v, got %v", writeErr, err)
4851+
}
4852+
4853+
cc.mu.Lock()
4854+
closed := cc.closed
4855+
cc.mu.Unlock()
4856+
if !closed {
4857+
t.Fatal("expected closed")
4858+
}
4859+
}

0 commit comments

Comments
 (0)