@@ -165,6 +165,7 @@ type ClientConn struct {
165
165
goAwayDebug string // goAway frame's debug data, retained as a string
166
166
streams map [uint32 ]* clientStream // client-initiated
167
167
nextStreamID uint32
168
+ pendingRequests int // requests blocked and waiting to be sent because len(streams) == maxConcurrentStreams
168
169
pings map [[8 ]byte ]chan struct {} // in flight ping data to notification channel
169
170
bw * bufio.Writer
170
171
br * bufio.Reader
@@ -217,35 +218,45 @@ type clientStream struct {
217
218
resTrailer * http.Header // client's Response.Trailer
218
219
}
219
220
220
- // awaitRequestCancel runs in its own goroutine and waits for the user
221
- // to cancel a RoundTrip request, its context to expire, or for the
222
- // request to be done (any way it might be removed from the cc.streams
223
- // map: peer reset, successful completion, TCP connection breakage,
224
- // etc)
225
- func (cs * clientStream ) awaitRequestCancel (req * http.Request ) {
221
+ // awaitRequestCancel waits for the user to cancel a request or for the done
222
+ // channel to be signaled. A non-nil error is returned only if the request was
223
+ // canceled.
224
+ func awaitRequestCancel (req * http.Request , done <- chan struct {}) error {
226
225
ctx := reqContext (req )
227
226
if req .Cancel == nil && ctx .Done () == nil {
228
- return
227
+ return nil
229
228
}
230
229
select {
231
230
case <- req .Cancel :
232
- cs .cancelStream ()
233
- cs .bufPipe .CloseWithError (errRequestCanceled )
231
+ return errRequestCanceled
234
232
case <- ctx .Done ():
233
+ return ctx .Err ()
234
+ case <- done :
235
+ return nil
236
+ }
237
+ }
238
+
239
+ // awaitRequestCancel waits for the user to cancel a request, its context to
240
+ // expire, or for the request to be done (any way it might be removed from the
241
+ // cc.streams map: peer reset, successful completion, TCP connection breakage,
242
+ // etc). If the request is canceled, then cs will be canceled and closed.
243
+ func (cs * clientStream ) awaitRequestCancel (req * http.Request ) {
244
+ if err := awaitRequestCancel (req , cs .done ); err != nil {
235
245
cs .cancelStream ()
236
- cs .bufPipe .CloseWithError (ctx .Err ())
237
- case <- cs .done :
246
+ cs .bufPipe .CloseWithError (err )
238
247
}
239
248
}
240
249
241
250
func (cs * clientStream ) cancelStream () {
242
- cs .cc .mu .Lock ()
251
+ cc := cs .cc
252
+ cc .mu .Lock ()
243
253
didReset := cs .didReset
244
254
cs .didReset = true
245
- cs . cc .mu .Unlock ()
255
+ cc .mu .Unlock ()
246
256
247
257
if ! didReset {
248
- cs .cc .writeStreamReset (cs .ID , ErrCodeCancel , nil )
258
+ cc .writeStreamReset (cs .ID , ErrCodeCancel , nil )
259
+ cc .forgetStreamID (cs .ID )
249
260
}
250
261
}
251
262
@@ -594,6 +605,8 @@ func (cc *ClientConn) setGoAway(f *GoAwayFrame) {
594
605
}
595
606
}
596
607
608
+ // CanTakeNewRequest reports whether the connection can take a new request,
609
+ // meaning it has not been closed or received or sent a GOAWAY.
597
610
func (cc * ClientConn ) CanTakeNewRequest () bool {
598
611
cc .mu .Lock ()
599
612
defer cc .mu .Unlock ()
@@ -605,8 +618,7 @@ func (cc *ClientConn) canTakeNewRequestLocked() bool {
605
618
return false
606
619
}
607
620
return cc .goAway == nil && ! cc .closed &&
608
- int64 (len (cc .streams )+ 1 ) < int64 (cc .maxConcurrentStreams ) &&
609
- cc .nextStreamID < math .MaxInt32
621
+ int64 (cc .nextStreamID )+ int64 (cc .pendingRequests ) < math .MaxInt32
610
622
}
611
623
612
624
// onIdleTimeout is called from a time.AfterFunc goroutine. It will
@@ -752,10 +764,9 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
752
764
hasTrailers := trailers != ""
753
765
754
766
cc .mu .Lock ()
755
- cc .lastActive = time .Now ()
756
- if cc .closed || ! cc .canTakeNewRequestLocked () {
767
+ if err := cc .awaitOpenSlotForRequest (req ); err != nil {
757
768
cc .mu .Unlock ()
758
- return nil , errClientConnUnusable
769
+ return nil , err
759
770
}
760
771
761
772
body := req .Body
@@ -869,31 +880,31 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
869
880
case re := <- readLoopResCh :
870
881
return handleReadLoopResponse (re )
871
882
case <- respHeaderTimer :
872
- cc .forgetStreamID (cs .ID )
873
883
if ! hasBody || bodyWritten {
874
884
cc .writeStreamReset (cs .ID , ErrCodeCancel , nil )
875
885
} else {
876
886
bodyWriter .cancel ()
877
887
cs .abortRequestBodyWrite (errStopReqBodyWriteAndCancel )
878
888
}
889
+ cc .forgetStreamID (cs .ID )
879
890
return nil , errTimeout
880
891
case <- ctx .Done ():
881
- cc .forgetStreamID (cs .ID )
882
892
if ! hasBody || bodyWritten {
883
893
cc .writeStreamReset (cs .ID , ErrCodeCancel , nil )
884
894
} else {
885
895
bodyWriter .cancel ()
886
896
cs .abortRequestBodyWrite (errStopReqBodyWriteAndCancel )
887
897
}
898
+ cc .forgetStreamID (cs .ID )
888
899
return nil , ctx .Err ()
889
900
case <- req .Cancel :
890
- cc .forgetStreamID (cs .ID )
891
901
if ! hasBody || bodyWritten {
892
902
cc .writeStreamReset (cs .ID , ErrCodeCancel , nil )
893
903
} else {
894
904
bodyWriter .cancel ()
895
905
cs .abortRequestBodyWrite (errStopReqBodyWriteAndCancel )
896
906
}
907
+ cc .forgetStreamID (cs .ID )
897
908
return nil , errRequestCanceled
898
909
case <- cs .peerReset :
899
910
// processResetStream already removed the
@@ -920,6 +931,45 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
920
931
}
921
932
}
922
933
934
+ // awaitOpenSlotForRequest waits until len(streams) < maxConcurrentStreams.
935
+ // Must hold cc.mu.
936
+ func (cc * ClientConn ) awaitOpenSlotForRequest (req * http.Request ) error {
937
+ var waitingForConn chan struct {}
938
+ var waitingForConnErr error // guarded by cc.mu
939
+ for {
940
+ cc .lastActive = time .Now ()
941
+ if cc .closed || ! cc .canTakeNewRequestLocked () {
942
+ return errClientConnUnusable
943
+ }
944
+ if int64 (len (cc .streams ))+ 1 <= int64 (cc .maxConcurrentStreams ) {
945
+ if waitingForConn != nil {
946
+ close (waitingForConn )
947
+ }
948
+ return nil
949
+ }
950
+ // Unfortunately, we cannot wait on a condition variable and channel at
951
+ // the same time, so instead, we spin up a goroutine to check if the
952
+ // request is canceled while we wait for a slot to open in the connection.
953
+ if waitingForConn == nil {
954
+ waitingForConn = make (chan struct {})
955
+ go func () {
956
+ if err := awaitRequestCancel (req , waitingForConn ); err != nil {
957
+ cc .mu .Lock ()
958
+ waitingForConnErr = err
959
+ cc .cond .Broadcast ()
960
+ cc .mu .Unlock ()
961
+ }
962
+ }()
963
+ }
964
+ cc .pendingRequests ++
965
+ cc .cond .Wait ()
966
+ cc .pendingRequests --
967
+ if waitingForConnErr != nil {
968
+ return waitingForConnErr
969
+ }
970
+ }
971
+ }
972
+
923
973
// requires cc.wmu be held
924
974
func (cc * ClientConn ) writeHeaders (streamID uint32 , endStream bool , hdrs []byte ) error {
925
975
first := true // first frame written (HEADERS is first, then CONTINUATION)
@@ -1279,7 +1329,9 @@ func (cc *ClientConn) streamByID(id uint32, andRemove bool) *clientStream {
1279
1329
cc .idleTimer .Reset (cc .idleTimeout )
1280
1330
}
1281
1331
close (cs .done )
1282
- cc .cond .Broadcast () // wake up checkResetOrDone via clientStream.awaitFlowControl
1332
+ // Wake up checkResetOrDone via clientStream.awaitFlowControl and
1333
+ // wake up RoundTrip if there is a pending request.
1334
+ cc .cond .Broadcast ()
1283
1335
}
1284
1336
return cs
1285
1337
}
@@ -1378,8 +1430,9 @@ func (rl *clientConnReadLoop) run() error {
1378
1430
cc .vlogf ("http2: Transport readFrame error on conn %p: (%T) %v" , cc , err , err )
1379
1431
}
1380
1432
if se , ok := err .(StreamError ); ok {
1381
- if cs := cc .streamByID (se .StreamID , true /*ended; remove it*/ ); cs != nil {
1433
+ if cs := cc .streamByID (se .StreamID , false ); cs != nil {
1382
1434
cs .cc .writeStreamReset (cs .ID , se .Code , err )
1435
+ cs .cc .forgetStreamID (cs .ID )
1383
1436
if se .Cause == nil {
1384
1437
se .Cause = cc .fr .errDetail
1385
1438
}
@@ -1701,6 +1754,7 @@ func (b transportResponseBody) Close() error {
1701
1754
}
1702
1755
1703
1756
cs .bufPipe .BreakWithError (errClosedResponseBody )
1757
+ cc .forgetStreamID (cs .ID )
1704
1758
return nil
1705
1759
}
1706
1760
0 commit comments