Skip to content

Commit

Permalink
[release-branch.go1.15-bundle] http2: close Transport connection on w…
Browse files Browse the repository at this point in the history
…rite 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>
  • Loading branch information
fraenkel authored and dmitshur committed Oct 29, 2020
1 parent abf26a1 commit 0704177
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 0 deletions.
10 changes: 10 additions & 0 deletions http2/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,7 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
cc.inflow.add(transportDefaultConnFlow + initialWindowSize)
cc.bw.Flush()
if cc.werr != nil {
cc.Close()
return nil, cc.werr
}

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

defer func() {
cc.wmu.Lock()
werr := cc.werr
cc.wmu.Unlock()
if werr != nil {
cc.Close()
}
}()

cc.wmu.Lock()
endStream := !hasBody && !hasTrailers
werr := cc.writeHeaders(cs.ID, endStream, int(cc.maxFrameSize), hdrs)
Expand Down
62 changes: 62 additions & 0 deletions http2/transport_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4568,3 +4568,65 @@ func TestClientConnTooIdle(t *testing.T) {
}
}
}

type fakeConnErr struct {
net.Conn
writeErr error
closed bool
}

func (fce *fakeConnErr) Write(b []byte) (n int, err error) {
return 0, fce.writeErr
}

func (fce *fakeConnErr) Close() error {
fce.closed = true
return nil
}

// issue 39337: close the connection on a failed write
func TestTransportNewClientConnCloseOnWriteError(t *testing.T) {
tr := &Transport{}
writeErr := errors.New("write error")
fakeConn := &fakeConnErr{writeErr: writeErr}
_, err := tr.NewClientConn(fakeConn)
if err != writeErr {
t.Fatalf("expected %v, got %v", writeErr, err)
}
if !fakeConn.closed {
t.Error("expected closed conn")
}
}

func TestTransportRoundtripCloseOnWriteError(t *testing.T) {
req, err := http.NewRequest("GET", "https://dummy.tld/", nil)
if err != nil {
t.Fatal(err)
}
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, optOnlyServer)
defer st.Close()

tr := &Transport{TLSClientConfig: tlsConfigInsecure}
defer tr.CloseIdleConnections()
cc, err := tr.dialClientConn(st.ts.Listener.Addr().String(), false)
if err != nil {
t.Fatal(err)
}

writeErr := errors.New("write error")
cc.wmu.Lock()
cc.werr = writeErr
cc.wmu.Unlock()

_, err = cc.RoundTrip(req)
if err != writeErr {
t.Fatalf("expected %v, got %v", writeErr, err)
}

cc.mu.Lock()
closed := cc.closed
cc.mu.Unlock()
if !closed {
t.Fatal("expected closed")
}
}

0 comments on commit 0704177

Please sign in to comment.