// both [Transport] and [Server].
type HTTP2Config struct {
// MaxConcurrentStreams optionally specifies the number of
- // concurrent streams that a peer may have open at a time.
+ // concurrent streams that a client may have open at a time.
// If zero, MaxConcurrentStreams defaults to at least 100.
+ //
+ // This parameter only applies to Servers.
MaxConcurrentStreams int
+ // StrictMaxConcurrentRequests controls whether an HTTP/2 server's
+ // concurrency limit should be respected across all connections
+ // to that server.
+ // If true, new requests sent when a connection's concurrency limit
+ // has been exceeded will block until an existing request completes.
+ // If false, an additional connection will be opened if all
+ // existing connections are at their limit.
+ //
+ // This parameter only applies to Transports.
+ StrictMaxConcurrentRequests bool
+
// MaxDecoderHeaderTableSize optionally specifies an upper limit for the
// size of the header compression table used for decoding headers sent
// by the peer.
// When an HTTP/2 connection is at its stream limit
// a new request is made on a new connection.
-func TestTransportPoolConnHTTP2OverStreamLimit(t *testing.T) {
+func testTransportPoolConnHTTP2NoStrictMaxConcurrentRequests(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
dt := newTransportDialTester(t, http2Mode, func(srv *http.Server) {
srv.HTTP2 = &http.HTTP2Config{
})
}
+// When an HTTP/2 connection is at its stream limit
+// and StrictMaxConcurrentRequests = true,
+// a new request waits for a slot on the existing connection.
+func TestTransportPoolConnHTTP2StrictMaxConcurrentRequests(t *testing.T) {
+ t.Skip("skipped until h2_bundle.go includes support for StrictMaxConcurrentRequests")
+
+ synctest.Test(t, func(t *testing.T) {
+ dt := newTransportDialTester(t, http2Mode, func(srv *http.Server) {
+ srv.HTTP2.MaxConcurrentStreams = 2
+ }, func(tr *http.Transport) {
+ tr.HTTP2 = &http.HTTP2Config{
+ StrictMaxConcurrentRequests: true,
+ }
+ })
+
+ // First request dials an HTTP/2 connection.
+ rt1 := dt.roundTrip()
+ c1 := dt.wantDial()
+ c1.finish(nil)
+ rt1.wantDone(c1, "HTTP/2.0")
+
+ // Second request uses the existing connection.
+ rt2 := dt.roundTrip()
+ rt2.wantDone(c1, "HTTP/2.0")
+
+ // Third request blocks waiting for a slot on the existing connection.
+ rt3 := dt.roundTrip()
+
+ // First request finishing unblocks the thirrd.
+ rt1.finish()
+ rt3.wantDone(c1, "HTTP/2.0")
+
+ rt2.finish()
+ rt3.finish()
+ })
+}
+
// A new request made while an HTTP/2 dial is in progress will start a second dial.
func TestTransportPoolConnHTTP2Startup(t *testing.T) {
synctest.Test(t, func(t *testing.T) {