mirror of
https://github.com/golang/net.git
synced 2026-03-31 18:37:08 +09:00
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 <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>
This commit is contained in:
committed by
Damien Neil
parent
d65d470038
commit
f5854403a9
@@ -689,6 +689,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
|
||||
}
|
||||
|
||||
@@ -1080,6 +1081,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)
|
||||
|
||||
@@ -4795,3 +4795,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")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user