Skip to content

Commit

Permalink
[release-branch.go1.14] http2: wait until the request body has been w…
Browse files Browse the repository at this point in the history
…ritten

When the clientConn is done with a request, if there is a request body,
we must wait until the body is written otherwise there can be a race
when attempting to rewind the body.

Updates golang/go#42586

Change-Id: I77606cc19372eea8bbd8995102354f092b8042be
Reviewed-on: https://go-review.googlesource.com/c/net/+/253259
Reviewed-by: Damien Neil <dneil@google.com>
Trust: Emmanuel Odeke <emmanuel@orijtech.com>
Run-TryBot: Emmanuel Odeke <emmanuel@orijtech.com>
TryBot-Result: Go Bot <gobot@golang.org>
(cherry picked from commit ff519b6)
Reviewed-on: https://go-review.googlesource.com/c/net/+/288113
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
Trust: Damien Neil <dneil@google.com>
  • Loading branch information
fraenkel authored and dmitshur committed Jan 29, 2021
1 parent 70d8502 commit 9c81719
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 1 deletion.
8 changes: 7 additions & 1 deletion http2/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -1092,6 +1092,9 @@ func (cc *ClientConn) roundTrip(req *http.Request) (res *http.Response, gotErrAf
// we can keep it.
bodyWriter.cancel()
cs.abortRequestBodyWrite(errStopReqBodyWrite)
if hasBody && !bodyWritten {
<-bodyWriter.resc
}
}
if re.err != nil {
cc.forgetStreamID(cs.ID)
Expand All @@ -1112,6 +1115,7 @@ func (cc *ClientConn) roundTrip(req *http.Request) (res *http.Response, gotErrAf
} else {
bodyWriter.cancel()
cs.abortRequestBodyWrite(errStopReqBodyWriteAndCancel)
<-bodyWriter.resc
}
cc.forgetStreamID(cs.ID)
return nil, cs.getStartedWrite(), errTimeout
Expand All @@ -1121,6 +1125,7 @@ func (cc *ClientConn) roundTrip(req *http.Request) (res *http.Response, gotErrAf
} else {
bodyWriter.cancel()
cs.abortRequestBodyWrite(errStopReqBodyWriteAndCancel)
<-bodyWriter.resc
}
cc.forgetStreamID(cs.ID)
return nil, cs.getStartedWrite(), ctx.Err()
Expand All @@ -1130,6 +1135,7 @@ func (cc *ClientConn) roundTrip(req *http.Request) (res *http.Response, gotErrAf
} else {
bodyWriter.cancel()
cs.abortRequestBodyWrite(errStopReqBodyWriteAndCancel)
<-bodyWriter.resc
}
cc.forgetStreamID(cs.ID)
return nil, cs.getStartedWrite(), errRequestCanceled
Expand All @@ -1139,6 +1145,7 @@ func (cc *ClientConn) roundTrip(req *http.Request) (res *http.Response, gotErrAf
// forgetStreamID.
return nil, cs.getStartedWrite(), cs.resetErr
case err := <-bodyWriter.resc:
bodyWritten = true
// Prefer the read loop's response, if available. Issue 16102.
select {
case re := <-readLoopResCh:
Expand All @@ -1149,7 +1156,6 @@ func (cc *ClientConn) roundTrip(req *http.Request) (res *http.Response, gotErrAf
cc.forgetStreamID(cs.ID)
return nil, cs.getStartedWrite(), err
}
bodyWritten = true
if d := cc.responseHeaderTimeout(); d != 0 {
timer := time.NewTimer(d)
defer timer.Stop()
Expand Down
45 changes: 45 additions & 0 deletions http2/transport_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4583,3 +4583,48 @@ func TestTransportRoundtripCloseOnWriteError(t *testing.T) {
t.Fatal("expected closed")
}
}

// Issue 31192: A failed request may be retried if the body has not been read
// already. If the request body has started to be sent, one must wait until it
// is completed.
func TestTransportBodyRewindRace(t *testing.T) {
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Connection", "close")
w.WriteHeader(http.StatusOK)
return
}, optOnlyServer)
defer st.Close()

tr := &http.Transport{
TLSClientConfig: tlsConfigInsecure,
MaxConnsPerHost: 1,
}
err := ConfigureTransport(tr)
if err != nil {
t.Fatal(err)
}
client := &http.Client{
Transport: tr,
}

const clients = 50

var wg sync.WaitGroup
wg.Add(clients)
for i := 0; i < clients; i++ {
req, err := http.NewRequest("POST", st.ts.URL, bytes.NewBufferString("abcdef"))
if err != nil {
t.Fatalf("unexpect new request error: %v", err)
}

go func() {
defer wg.Done()
res, err := client.Do(req)
if err == nil {
res.Body.Close()
}
}()
}

wg.Wait()
}

0 comments on commit 9c81719

Please sign in to comment.