Skip to content

Commit d7eefc9

Browse files
neilddmitshur
authored andcommitted
[internal-branch.go1.16-vendor] http2: avoid blocking while holding ClientConn.mu
Operations which examine the state of a ClientConn--notably, the connection pool's check to see if a conn is available to take a new request--need to acquire mu. Blocking while holding mu, such as when writing to the network, blocks these operations. Remove blocking operations from the mutex. Perform network writes with only ClientConn.wmu held. Clarify that wmu guards the per-conn HPACK encoder and buffer. Add a new mutex guarding request creation, covering the critical section starting with allocating a new stream ID and continuing until the stream is created. Fix a locking issue where trailers were written from the HPACK buffer with only wmu held, but headers were encoded into the buffer with only mu held. (Now both encoding and writes occur with wmu held.) Updates golang/go#49076 Change-Id: Ibb313424ed2f32c1aeac4645b76aedf227b597a3 Reviewed-on: https://go-review.googlesource.com/c/net/+/349594 Trust: Damien Neil <[email protected]> Run-TryBot: Damien Neil <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]> Reviewed-on: https://go-review.googlesource.com/c/net/+/356982 Reviewed-by: Dmitri Shuralyov <[email protected]>
1 parent 78e8d65 commit d7eefc9

File tree

2 files changed

+358
-56
lines changed

2 files changed

+358
-56
lines changed

http2/transport.go

+116-55
Original file line numberDiff line numberDiff line change
@@ -264,22 +264,29 @@ type ClientConn struct {
264264
nextStreamID uint32
265265
pendingRequests int // requests blocked and waiting to be sent because len(streams) == maxConcurrentStreams
266266
pings map[[8]byte]chan struct{} // in flight ping data to notification channel
267-
bw *bufio.Writer
268267
br *bufio.Reader
269-
fr *Framer
270268
lastActive time.Time
271269
lastIdle time.Time // time last idle
272-
// Settings from peer: (also guarded by mu)
270+
// Settings from peer: (also guarded by wmu)
273271
maxFrameSize uint32
274272
maxConcurrentStreams uint32
275273
peerMaxHeaderListSize uint64
276274
initialWindowSize uint32
277275

276+
// reqHeaderMu is a 1-element semaphore channel controlling access to sending new requests.
277+
// Write to reqHeaderMu to lock it, read from it to unlock.
278+
// Lock reqmu BEFORE mu or wmu.
279+
reqHeaderMu chan struct{}
280+
281+
// wmu is held while writing.
282+
// Acquire BEFORE mu when holding both, to avoid blocking mu on network writes.
283+
// Only acquire both at the same time when changing peer settings.
284+
wmu sync.Mutex
285+
bw *bufio.Writer
286+
fr *Framer
287+
werr error // first write error that has occurred
278288
hbuf bytes.Buffer // HPACK encoder writes into this
279289
henc *hpack.Encoder
280-
281-
wmu sync.Mutex // held while writing; acquire AFTER mu if holding both
282-
werr error // first write error that has occurred
283290
}
284291

285292
// clientStream is the state for a single HTTP/2 stream. One of these
@@ -398,10 +405,11 @@ func (cs *clientStream) abortRequestBodyWrite(err error) {
398405
cc.mu.Lock()
399406
if cs.stopReqBody == nil {
400407
cs.stopReqBody = err
401-
if cs.req.Body != nil {
402-
cs.req.Body.Close()
403-
}
404408
cc.cond.Broadcast()
409+
// Close the body after releasing the mutex, in case it blocks.
410+
if body := cs.req.Body; body != nil {
411+
defer body.Close()
412+
}
405413
}
406414
cc.mu.Unlock()
407415
}
@@ -670,6 +678,7 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
670678
singleUse: singleUse,
671679
wantSettingsAck: true,
672680
pings: make(map[[8]byte]chan struct{}),
681+
reqHeaderMu: make(chan struct{}, 1),
673682
}
674683
if d := t.idleConnTimeout(); d != 0 {
675684
cc.idleTimeout = d
@@ -898,41 +907,48 @@ func (cc *ClientConn) Shutdown(ctx context.Context) error {
898907

899908
func (cc *ClientConn) sendGoAway() error {
900909
cc.mu.Lock()
901-
defer cc.mu.Unlock()
902-
cc.wmu.Lock()
903-
defer cc.wmu.Unlock()
904-
if cc.closing {
910+
closing := cc.closing
911+
cc.closing = true
912+
maxStreamID := cc.nextStreamID
913+
cc.mu.Unlock()
914+
if closing {
905915
// GOAWAY sent already
906916
return nil
907917
}
918+
919+
cc.wmu.Lock()
920+
defer cc.wmu.Unlock()
908921
// Send a graceful shutdown frame to server
909-
maxStreamID := cc.nextStreamID
910922
if err := cc.fr.WriteGoAway(maxStreamID, ErrCodeNo, nil); err != nil {
911923
return err
912924
}
913925
if err := cc.bw.Flush(); err != nil {
914926
return err
915927
}
916928
// Prevent new requests
917-
cc.closing = true
918929
return nil
919930
}
920931

921932
// closes the client connection immediately. In-flight requests are interrupted.
922933
// err is sent to streams.
923934
func (cc *ClientConn) closeForError(err error) error {
924935
cc.mu.Lock()
925-
defer cc.cond.Broadcast()
926-
defer cc.mu.Unlock()
927-
for id, cs := range cc.streams {
936+
streams := cc.streams
937+
cc.streams = nil
938+
cc.closed = true
939+
cc.mu.Unlock()
940+
941+
for _, cs := range streams {
928942
select {
929943
case cs.resc <- resAndError{err: err}:
930944
default:
931945
}
932946
cs.bufPipe.CloseWithError(err)
933-
delete(cc.streams, id)
934947
}
935-
cc.closed = true
948+
949+
cc.mu.Lock()
950+
defer cc.cond.Broadcast()
951+
defer cc.mu.Unlock()
936952
return cc.tconn.Close()
937953
}
938954

@@ -1017,6 +1033,7 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
10171033
}
10181034

10191035
func (cc *ClientConn) roundTrip(req *http.Request) (res *http.Response, gotErrAfterReqBodyWrite bool, err error) {
1036+
ctx := req.Context()
10201037
if err := checkConnHeaders(req); err != nil {
10211038
return nil, false, err
10221039
}
@@ -1030,6 +1047,26 @@ func (cc *ClientConn) roundTrip(req *http.Request) (res *http.Response, gotErrAf
10301047
}
10311048
hasTrailers := trailers != ""
10321049

1050+
// Acquire the new-request lock by writing to reqHeaderMu.
1051+
// This lock guards the critical section covering allocating a new stream ID
1052+
// (requires mu) and creating the stream (requires wmu).
1053+
if cc.reqHeaderMu == nil {
1054+
panic("RoundTrip on initialized ClientConn") // for tests
1055+
}
1056+
select {
1057+
case cc.reqHeaderMu <- struct{}{}:
1058+
case <-req.Cancel:
1059+
return nil, false, errRequestCanceled
1060+
case <-ctx.Done():
1061+
return nil, false, ctx.Err()
1062+
}
1063+
reqHeaderMuNeedsUnlock := true
1064+
defer func() {
1065+
if reqHeaderMuNeedsUnlock {
1066+
<-cc.reqHeaderMu
1067+
}
1068+
}()
1069+
10331070
cc.mu.Lock()
10341071
if err := cc.awaitOpenSlotForRequest(req); err != nil {
10351072
cc.mu.Unlock()
@@ -1061,22 +1098,24 @@ func (cc *ClientConn) roundTrip(req *http.Request) (res *http.Response, gotErrAf
10611098
requestedGzip = true
10621099
}
10631100

1101+
cs := cc.newStream()
1102+
cs.req = req
1103+
cs.trace = httptrace.ContextClientTrace(req.Context())
1104+
cs.requestedGzip = requestedGzip
1105+
bodyWriter := cc.t.getBodyWriterState(cs, body)
1106+
cs.on100 = bodyWriter.on100
1107+
cc.mu.Unlock()
1108+
10641109
// we send: HEADERS{1}, CONTINUATION{0,} + DATA{0,} (DATA is
10651110
// sent by writeRequestBody below, along with any Trailers,
10661111
// again in form HEADERS{1}, CONTINUATION{0,})
1112+
cc.wmu.Lock()
10671113
hdrs, err := cc.encodeHeaders(req, requestedGzip, trailers, contentLen)
10681114
if err != nil {
1069-
cc.mu.Unlock()
1115+
cc.wmu.Unlock()
10701116
return nil, false, err
10711117
}
10721118

1073-
cs := cc.newStream()
1074-
cs.req = req
1075-
cs.trace = httptrace.ContextClientTrace(req.Context())
1076-
cs.requestedGzip = requestedGzip
1077-
bodyWriter := cc.t.getBodyWriterState(cs, body)
1078-
cs.on100 = bodyWriter.on100
1079-
10801119
defer func() {
10811120
cc.wmu.Lock()
10821121
werr := cc.werr
@@ -1086,24 +1125,24 @@ func (cc *ClientConn) roundTrip(req *http.Request) (res *http.Response, gotErrAf
10861125
}
10871126
}()
10881127

1089-
cc.wmu.Lock()
10901128
endStream := !hasBody && !hasTrailers
1091-
werr := cc.writeHeaders(cs.ID, endStream, int(cc.maxFrameSize), hdrs)
1129+
err = cc.writeHeaders(cs.ID, endStream, int(cc.maxFrameSize), hdrs)
10921130
cc.wmu.Unlock()
1131+
<-cc.reqHeaderMu // release the new-request lock
1132+
reqHeaderMuNeedsUnlock = false
10931133
traceWroteHeaders(cs.trace)
1094-
cc.mu.Unlock()
10951134

1096-
if werr != nil {
1135+
if err != nil {
10971136
if hasBody {
10981137
bodyWriter.cancel()
10991138
}
11001139
cc.forgetStreamID(cs.ID)
11011140
// Don't bother sending a RST_STREAM (our write already failed;
11021141
// no need to keep writing)
1103-
traceWroteRequest(cs.trace, werr)
1142+
traceWroteRequest(cs.trace, err)
11041143
// TODO(dneil): An error occurred while writing the headers.
11051144
// Should we return an error indicating that this request can be retried?
1106-
return nil, false, werr
1145+
return nil, false, err
11071146
}
11081147

11091148
var respHeaderTimer <-chan time.Time
@@ -1120,7 +1159,6 @@ func (cc *ClientConn) roundTrip(req *http.Request) (res *http.Response, gotErrAf
11201159

11211160
readLoopResCh := cs.resc
11221161
bodyWritten := false
1123-
ctx := req.Context()
11241162

11251163
handleReadLoopResponse := func(re resAndError) (*http.Response, bool, error) {
11261164
res := re.res
@@ -1422,19 +1460,17 @@ func (cs *clientStream) writeRequestBody(body io.Reader, bodyCloser io.Closer) (
14221460
return nil
14231461
}
14241462

1463+
cc.wmu.Lock()
14251464
var trls []byte
14261465
if hasTrailers {
1427-
cc.mu.Lock()
14281466
trls, err = cc.encodeTrailers(req)
1429-
cc.mu.Unlock()
14301467
if err != nil {
1468+
cc.wmu.Unlock()
14311469
cc.writeStreamReset(cs.ID, ErrCodeInternal, err)
14321470
cc.forgetStreamID(cs.ID)
14331471
return err
14341472
}
14351473
}
1436-
1437-
cc.wmu.Lock()
14381474
defer cc.wmu.Unlock()
14391475

14401476
// Two ways to send END_STREAM: either with trailers, or
@@ -1484,7 +1520,7 @@ func (cs *clientStream) awaitFlowControl(maxBytes int) (taken int32, err error)
14841520
}
14851521
}
14861522

1487-
// requires cc.mu be held.
1523+
// requires cc.wmu be held.
14881524
func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trailers string, contentLength int64) ([]byte, error) {
14891525
cc.hbuf.Reset()
14901526

@@ -1672,7 +1708,7 @@ func shouldSendReqContentLength(method string, contentLength int64) bool {
16721708
}
16731709
}
16741710

1675-
// requires cc.mu be held.
1711+
// requires cc.wmu be held.
16761712
func (cc *ClientConn) encodeTrailers(req *http.Request) ([]byte, error) {
16771713
cc.hbuf.Reset()
16781714

@@ -1821,15 +1857,19 @@ func (rl *clientConnReadLoop) cleanup() {
18211857
} else if err == io.EOF {
18221858
err = io.ErrUnexpectedEOF
18231859
}
1824-
for _, cs := range cc.streams {
1860+
cc.closed = true
1861+
streams := cc.streams
1862+
cc.streams = nil
1863+
cc.mu.Unlock()
1864+
for _, cs := range streams {
18251865
cs.bufPipe.CloseWithError(err) // no-op if already closed
18261866
select {
18271867
case cs.resc <- resAndError{err: err}:
18281868
default:
18291869
}
18301870
close(cs.done)
18311871
}
1832-
cc.closed = true
1872+
cc.mu.Lock()
18331873
cc.cond.Broadcast()
18341874
cc.mu.Unlock()
18351875
}
@@ -2159,8 +2199,6 @@ func (b transportResponseBody) Read(p []byte) (n int, err error) {
21592199
}
21602200

21612201
cc.mu.Lock()
2162-
defer cc.mu.Unlock()
2163-
21642202
var connAdd, streamAdd int32
21652203
// Check the conn-level first, before the stream-level.
21662204
if v := cc.inflow.available(); v < transportDefaultConnFlow/2 {
@@ -2177,6 +2215,8 @@ func (b transportResponseBody) Read(p []byte) (n int, err error) {
21772215
cs.inflow.add(streamAdd)
21782216
}
21792217
}
2218+
cc.mu.Unlock()
2219+
21802220
if connAdd != 0 || streamAdd != 0 {
21812221
cc.wmu.Lock()
21822222
defer cc.wmu.Unlock()
@@ -2202,19 +2242,25 @@ func (b transportResponseBody) Close() error {
22022242

22032243
if unread > 0 || !serverSentStreamEnd {
22042244
cc.mu.Lock()
2205-
cc.wmu.Lock()
22062245
if !serverSentStreamEnd {
2207-
cc.fr.WriteRSTStream(cs.ID, ErrCodeCancel)
22082246
cs.didReset = true
22092247
}
22102248
// Return connection-level flow control.
22112249
if unread > 0 {
22122250
cc.inflow.add(int32(unread))
2251+
}
2252+
cc.mu.Unlock()
2253+
2254+
cc.wmu.Lock()
2255+
if !serverSentStreamEnd {
2256+
cc.fr.WriteRSTStream(cs.ID, ErrCodeCancel)
2257+
}
2258+
// Return connection-level flow control.
2259+
if unread > 0 {
22132260
cc.fr.WriteWindowUpdate(0, uint32(unread))
22142261
}
22152262
cc.bw.Flush()
22162263
cc.wmu.Unlock()
2217-
cc.mu.Unlock()
22182264
}
22192265

22202266
cs.bufPipe.BreakWithError(errClosedResponseBody)
@@ -2292,6 +2338,10 @@ func (rl *clientConnReadLoop) processData(f *DataFrame) error {
22922338
}
22932339
if refund > 0 {
22942340
cc.inflow.add(int32(refund))
2341+
}
2342+
cc.mu.Unlock()
2343+
2344+
if refund > 0 {
22952345
cc.wmu.Lock()
22962346
cc.fr.WriteWindowUpdate(0, uint32(refund))
22972347
if !didReset {
@@ -2301,7 +2351,6 @@ func (rl *clientConnReadLoop) processData(f *DataFrame) error {
23012351
cc.bw.Flush()
23022352
cc.wmu.Unlock()
23032353
}
2304-
cc.mu.Unlock()
23052354

23062355
if len(data) > 0 && !didReset {
23072356
if _, err := cs.bufPipe.Write(data); err != nil {
@@ -2362,6 +2411,23 @@ func (rl *clientConnReadLoop) processGoAway(f *GoAwayFrame) error {
23622411
}
23632412

23642413
func (rl *clientConnReadLoop) processSettings(f *SettingsFrame) error {
2414+
cc := rl.cc
2415+
// Locking both mu and wmu here allows frame encoding to read settings with only wmu held.
2416+
// Acquiring wmu when f.IsAck() is unnecessary, but convenient and mostly harmless.
2417+
cc.wmu.Lock()
2418+
defer cc.wmu.Unlock()
2419+
2420+
if err := rl.processSettingsNoWrite(f); err != nil {
2421+
return err
2422+
}
2423+
if !f.IsAck() {
2424+
cc.fr.WriteSettingsAck()
2425+
cc.bw.Flush()
2426+
}
2427+
return nil
2428+
}
2429+
2430+
func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error {
23652431
cc := rl.cc
23662432
cc.mu.Lock()
23672433
defer cc.mu.Unlock()
@@ -2424,12 +2490,7 @@ func (rl *clientConnReadLoop) processSettings(f *SettingsFrame) error {
24242490
cc.seenSettings = true
24252491
}
24262492

2427-
cc.wmu.Lock()
2428-
defer cc.wmu.Unlock()
2429-
2430-
cc.fr.WriteSettingsAck()
2431-
cc.bw.Flush()
2432-
return cc.werr
2493+
return nil
24332494
}
24342495

24352496
func (rl *clientConnReadLoop) processWindowUpdate(f *WindowUpdateFrame) error {

0 commit comments

Comments
 (0)