Skip to content

Commit 0704177

Browse files
fraenkeldmitshur
authored andcommitted
[release-branch.go1.15-bundle] 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. Updates golang/go#39337. For golang/go#42113. Change-Id: I45e1293309e40629531f4cbb69387864f4f71bc2 Reviewed-on: https://go-review.googlesource.com/c/net/+/240337 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Go Bot <gobot@golang.org> Trust: Brad Fitzpatrick <bradfitz@golang.org> Trust: Damien Neil <dneil@google.com> (cherry picked from commit f585440) Reviewed-on: https://go-review.googlesource.com/c/net/+/266158 Trust: Dmitri Shuralyov <dmitshur@golang.org> Run-TryBot: Dmitri Shuralyov <dmitshur@golang.org> Reviewed-by: Emmanuel Odeke <emmanuel@orijtech.com>
1 parent abf26a1 commit 0704177

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
@@ -668,6 +668,7 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
668668
cc.inflow.add(transportDefaultConnFlow + initialWindowSize)
669669
cc.bw.Flush()
670670
if cc.werr != nil {
671+
cc.Close()
671672
return nil, cc.werr
672673
}
673674

@@ -1033,6 +1034,15 @@ func (cc *ClientConn) roundTrip(req *http.Request) (res *http.Response, gotErrAf
10331034
bodyWriter := cc.t.getBodyWriterState(cs, body)
10341035
cs.on100 = bodyWriter.on100
10351036

1037+
defer func() {
1038+
cc.wmu.Lock()
1039+
werr := cc.werr
1040+
cc.wmu.Unlock()
1041+
if werr != nil {
1042+
cc.Close()
1043+
}
1044+
}()
1045+
10361046
cc.wmu.Lock()
10371047
endStream := !hasBody && !hasTrailers
10381048
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
@@ -4568,3 +4568,65 @@ func TestClientConnTooIdle(t *testing.T) {
45684568
}
45694569
}
45704570
}
4571+
4572+
type fakeConnErr struct {
4573+
net.Conn
4574+
writeErr error
4575+
closed bool
4576+
}
4577+
4578+
func (fce *fakeConnErr) Write(b []byte) (n int, err error) {
4579+
return 0, fce.writeErr
4580+
}
4581+
4582+
func (fce *fakeConnErr) Close() error {
4583+
fce.closed = true
4584+
return nil
4585+
}
4586+
4587+
// issue 39337: close the connection on a failed write
4588+
func TestTransportNewClientConnCloseOnWriteError(t *testing.T) {
4589+
tr := &Transport{}
4590+
writeErr := errors.New("write error")
4591+
fakeConn := &fakeConnErr{writeErr: writeErr}
4592+
_, err := tr.NewClientConn(fakeConn)
4593+
if err != writeErr {
4594+
t.Fatalf("expected %v, got %v", writeErr, err)
4595+
}
4596+
if !fakeConn.closed {
4597+
t.Error("expected closed conn")
4598+
}
4599+
}
4600+
4601+
func TestTransportRoundtripCloseOnWriteError(t *testing.T) {
4602+
req, err := http.NewRequest("GET", "https://dummy.tld/", nil)
4603+
if err != nil {
4604+
t.Fatal(err)
4605+
}
4606+
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, optOnlyServer)
4607+
defer st.Close()
4608+
4609+
tr := &Transport{TLSClientConfig: tlsConfigInsecure}
4610+
defer tr.CloseIdleConnections()
4611+
cc, err := tr.dialClientConn(st.ts.Listener.Addr().String(), false)
4612+
if err != nil {
4613+
t.Fatal(err)
4614+
}
4615+
4616+
writeErr := errors.New("write error")
4617+
cc.wmu.Lock()
4618+
cc.werr = writeErr
4619+
cc.wmu.Unlock()
4620+
4621+
_, err = cc.RoundTrip(req)
4622+
if err != writeErr {
4623+
t.Fatalf("expected %v, got %v", writeErr, err)
4624+
}
4625+
4626+
cc.mu.Lock()
4627+
closed := cc.closed
4628+
cc.mu.Unlock()
4629+
if !closed {
4630+
t.Fatal("expected closed")
4631+
}
4632+
}

0 commit comments

Comments
 (0)