@@ -261,6 +261,7 @@ type ClientConn struct {
261
261
goAway * GoAwayFrame // if non-nil, the GoAwayFrame we received
262
262
goAwayDebug string // goAway frame's debug data, retained as a string
263
263
streams map [uint32 ]* clientStream // client-initiated
264
+ streamsReserved int // incr by ReserveNewRequest; decr on RoundTrip
264
265
nextStreamID uint32
265
266
pendingRequests int // requests blocked and waiting to be sent because len(streams) == maxConcurrentStreams
266
267
pings map [[8 ]byte ]chan struct {} // in flight ping data to notification channel
@@ -782,12 +783,28 @@ func (cc *ClientConn) setGoAway(f *GoAwayFrame) {
782
783
783
784
// CanTakeNewRequest reports whether the connection can take a new request,
784
785
// meaning it has not been closed or received or sent a GOAWAY.
786
+ //
787
+ // If the caller is going to immediately make a new request on this
788
+ // connection, use ReserveNewRequest instead.
785
789
func (cc * ClientConn ) CanTakeNewRequest () bool {
786
790
cc .mu .Lock ()
787
791
defer cc .mu .Unlock ()
788
792
return cc .canTakeNewRequestLocked ()
789
793
}
790
794
795
+ // ReserveNewRequest is like CanTakeNewRequest but also reserves a
796
+ // concurrent stream in cc. The reservation is decremented on the
797
+ // next call to RoundTrip.
798
+ func (cc * ClientConn ) ReserveNewRequest () bool {
799
+ cc .mu .Lock ()
800
+ defer cc .mu .Unlock ()
801
+ if st := cc .idleStateLocked (); ! st .canTakeNewRequest {
802
+ return false
803
+ }
804
+ cc .streamsReserved ++
805
+ return true
806
+ }
807
+
791
808
// clientConnIdleState describes the suitability of a client
792
809
// connection to initiate a new RoundTrip request.
793
810
type clientConnIdleState struct {
@@ -813,7 +830,7 @@ func (cc *ClientConn) idleStateLocked() (st clientConnIdleState) {
813
830
// writing it.
814
831
maxConcurrentOkay = true
815
832
} else {
816
- maxConcurrentOkay = int64 (len (cc .streams )+ 1 ) <= int64 (cc .maxConcurrentStreams )
833
+ maxConcurrentOkay = int64 (len (cc .streams )+ cc . streamsReserved + 1 ) <= int64 (cc .maxConcurrentStreams )
817
834
}
818
835
819
836
st .canTakeNewRequest = cc .goAway == nil && ! cc .closed && ! cc .closing && maxConcurrentOkay &&
@@ -1033,6 +1050,18 @@ func actualContentLength(req *http.Request) int64 {
1033
1050
return - 1
1034
1051
}
1035
1052
1053
+ func (cc * ClientConn ) decrStreamReservations () {
1054
+ cc .mu .Lock ()
1055
+ defer cc .mu .Unlock ()
1056
+ cc .decrStreamReservationsLocked ()
1057
+ }
1058
+
1059
+ func (cc * ClientConn ) decrStreamReservationsLocked () {
1060
+ if cc .streamsReserved > 0 {
1061
+ cc .streamsReserved --
1062
+ }
1063
+ }
1064
+
1036
1065
func (cc * ClientConn ) RoundTrip (req * http.Request ) (* http.Response , error ) {
1037
1066
resp , _ , err := cc .roundTrip (req )
1038
1067
return resp , err
@@ -1041,6 +1070,7 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
1041
1070
func (cc * ClientConn ) roundTrip (req * http.Request ) (res * http.Response , gotErrAfterReqBodyWrite bool , err error ) {
1042
1071
ctx := req .Context ()
1043
1072
if err := checkConnHeaders (req ); err != nil {
1073
+ cc .decrStreamReservations ()
1044
1074
return nil , false , err
1045
1075
}
1046
1076
if cc .idleTimer != nil {
@@ -1049,6 +1079,7 @@ func (cc *ClientConn) roundTrip(req *http.Request) (res *http.Response, gotErrAf
1049
1079
1050
1080
trailers , err := commaSeparatedTrailers (req )
1051
1081
if err != nil {
1082
+ cc .decrStreamReservations ()
1052
1083
return nil , false , err
1053
1084
}
1054
1085
hasTrailers := trailers != ""
@@ -1062,8 +1093,10 @@ func (cc *ClientConn) roundTrip(req *http.Request) (res *http.Response, gotErrAf
1062
1093
select {
1063
1094
case cc .reqHeaderMu <- struct {}{}:
1064
1095
case <- req .Cancel :
1096
+ cc .decrStreamReservations ()
1065
1097
return nil , false , errRequestCanceled
1066
1098
case <- ctx .Done ():
1099
+ cc .decrStreamReservations ()
1067
1100
return nil , false , ctx .Err ()
1068
1101
}
1069
1102
reqHeaderMuNeedsUnlock := true
@@ -1074,6 +1107,11 @@ func (cc *ClientConn) roundTrip(req *http.Request) (res *http.Response, gotErrAf
1074
1107
}()
1075
1108
1076
1109
cc .mu .Lock ()
1110
+ cc .decrStreamReservationsLocked ()
1111
+ if req .URL == nil {
1112
+ cc .mu .Unlock ()
1113
+ return nil , false , errNilRequestURL
1114
+ }
1077
1115
if err := cc .awaitOpenSlotForRequest (req ); err != nil {
1078
1116
cc .mu .Unlock ()
1079
1117
return nil , false , err
@@ -1526,9 +1564,14 @@ func (cs *clientStream) awaitFlowControl(maxBytes int) (taken int32, err error)
1526
1564
}
1527
1565
}
1528
1566
1567
+ var errNilRequestURL = errors .New ("http2: Request.URI is nil" )
1568
+
1529
1569
// requires cc.wmu be held.
1530
1570
func (cc * ClientConn ) encodeHeaders (req * http.Request , addGzipHeader bool , trailers string , contentLength int64 ) ([]byte , error ) {
1531
1571
cc .hbuf .Reset ()
1572
+ if req .URL == nil {
1573
+ return nil , errNilRequestURL
1574
+ }
1532
1575
1533
1576
host := req .Host
1534
1577
if host == "" {
0 commit comments