]> Cypherpunks repositories - gostls13.git/commitdiff
net/http: update bundled x/net/http2 and delete obsolete http2inTests
authorNicholas S. Husin <nsh@golang.org>
Thu, 2 Oct 2025 16:41:25 +0000 (12:41 -0400)
committerNicholas Husin <nsh@golang.org>
Thu, 2 Oct 2025 19:52:26 +0000 (12:52 -0700)
http2inTests is no longer needed after go.dev/cl/708135 and should be
deleted. To prevent errors in future vendored dependency updates,
h2_bundle.go is also updated together in this change.

Change-Id: I7b8c3f6854203fab4ec639a2a268df0cd2b1dee7
Reviewed-on: https://go-review.googlesource.com/c/go/+/708595
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Nicholas Husin <husin@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>

src/go.mod
src/go.sum
src/net/http/h2_bundle.go
src/net/http/http2_test.go [deleted file]
src/net/http/internal/httpcommon/httpcommon.go
src/net/http/socks_bundle.go
src/vendor/golang.org/x/net/nettest/conntest.go
src/vendor/modules.txt

index 4b400fe87189d1095b12a968481c7ebd4520458d..f134f0c7b571da9e33ec213b3e96406309641fef 100644 (file)
@@ -4,7 +4,7 @@ go 1.26
 
 require (
        golang.org/x/crypto v0.42.0
-       golang.org/x/net v0.44.0
+       golang.org/x/net v0.44.1-0.20251002015445-edb764c2296f
 )
 
 require (
index c90970a9cbaaad8540bcf5fb499ddb74dcb55a5a..f24bea029a223937910d50ed8f321c7c73018a3d 100644 (file)
@@ -1,7 +1,7 @@
 golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
 golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
-golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
-golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
+golang.org/x/net v0.44.1-0.20251002015445-edb764c2296f h1:vNklv+oJQSYNGsWXHoCPi2MHMcpj9/Q7aBhvvfnJvGg=
+golang.org/x/net v0.44.1-0.20251002015445-edb764c2296f/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
 golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
 golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
 golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
index 3abd7476f04e739f8469be5c244759d9ac697aa4..f09e102efb7b1ce6649812cb1431c84652985a64 100644 (file)
@@ -1047,6 +1047,7 @@ func http2shouldRetryDial(call *http2dialCall, req *Request) bool {
 //   - If the resulting value is zero or out of range, use a default.
 type http2http2Config struct {
        MaxConcurrentStreams         uint32
+       StrictMaxConcurrentRequests  bool
        MaxDecoderHeaderTableSize    uint32
        MaxEncoderHeaderTableSize    uint32
        MaxReadFrameSize             uint32
@@ -1084,12 +1085,13 @@ func http2configFromServer(h1 *Server, h2 *http2Server) http2http2Config {
 // (the net/http Transport).
 func http2configFromTransport(h2 *http2Transport) http2http2Config {
        conf := http2http2Config{
-               MaxEncoderHeaderTableSize: h2.MaxEncoderHeaderTableSize,
-               MaxDecoderHeaderTableSize: h2.MaxDecoderHeaderTableSize,
-               MaxReadFrameSize:          h2.MaxReadFrameSize,
-               SendPingTimeout:           h2.ReadIdleTimeout,
-               PingTimeout:               h2.PingTimeout,
-               WriteByteTimeout:          h2.WriteByteTimeout,
+               StrictMaxConcurrentRequests: h2.StrictMaxConcurrentStreams,
+               MaxEncoderHeaderTableSize:   h2.MaxEncoderHeaderTableSize,
+               MaxDecoderHeaderTableSize:   h2.MaxDecoderHeaderTableSize,
+               MaxReadFrameSize:            h2.MaxReadFrameSize,
+               SendPingTimeout:             h2.ReadIdleTimeout,
+               PingTimeout:                 h2.PingTimeout,
+               WriteByteTimeout:            h2.WriteByteTimeout,
        }
 
        // Unlike most config fields, where out-of-range values revert to the default,
@@ -1148,6 +1150,9 @@ func http2fillNetHTTPConfig(conf *http2http2Config, h2 *HTTP2Config) {
        if h2.MaxConcurrentStreams != 0 {
                conf.MaxConcurrentStreams = uint32(h2.MaxConcurrentStreams)
        }
+       if http2http2ConfigStrictMaxConcurrentRequests(h2) {
+               conf.StrictMaxConcurrentRequests = true
+       }
        if h2.MaxEncoderHeaderTableSize != 0 {
                conf.MaxEncoderHeaderTableSize = uint32(h2.MaxEncoderHeaderTableSize)
        }
@@ -1183,6 +1188,10 @@ func http2fillNetHTTPConfig(conf *http2http2Config, h2 *HTTP2Config) {
        }
 }
 
+func http2http2ConfigStrictMaxConcurrentRequests(h2 *HTTP2Config) bool {
+       return h2.StrictMaxConcurrentRequests
+}
+
 // Buffer chunks are allocated from a pool to reduce pressure on GC.
 // The maximum wasted space per dataBuffer is 2x the largest size class,
 // which happens when the dataBuffer has multiple chunks and there is
@@ -1900,7 +1909,7 @@ func (fr *http2Framer) maxHeaderListSize() uint32 {
 func (f *http2Framer) startWrite(ftype http2FrameType, flags http2Flags, streamID uint32) {
        // Write the FrameHeader.
        f.wbuf = append(f.wbuf[:0],
-               0, // 3 bytes of length, filled in in endWrite
+               0, // 3 bytes of length, filled in endWrite
                0,
                0,
                byte(ftype),
@@ -2708,6 +2717,15 @@ type http2PriorityFrame struct {
        http2PriorityParam
 }
 
+var http2defaultRFC9218Priority = http2PriorityParam{
+       incremental: 0,
+       urgency:     3,
+}
+
+// Note that HTTP/2 has had two different prioritization schemes, and
+// PriorityParam struct below is a superset of both schemes. The exported
+// symbols are from RFC 7540 and the non-exported ones are from RFC 9218.
+
 // PriorityParam are the stream prioritzation parameters.
 type http2PriorityParam struct {
        // StreamDep is a 31-bit stream identifier for the
@@ -2723,6 +2741,20 @@ type http2PriorityParam struct {
        // the spec, "Add one to the value to obtain a weight between
        // 1 and 256."
        Weight uint8
+
+       // "The urgency (u) parameter value is Integer (see Section 3.3.1 of
+       // [STRUCTURED-FIELDS]), between 0 and 7 inclusive, in descending order of
+       // priority. The default is 3."
+       urgency uint8
+
+       // "The incremental (i) parameter value is Boolean (see Section 3.3.6 of
+       // [STRUCTURED-FIELDS]). It indicates if an HTTP response can be processed
+       // incrementally, i.e., provide some meaningful output as chunks of the
+       // response arrive."
+       //
+       // We use uint8 (i.e. 0 is false, 1 is true) instead of bool so we can
+       // avoid unnecessary type conversions and because either type takes 1 byte.
+       incremental uint8
 }
 
 func (p http2PriorityParam) IsZero() bool {
@@ -3423,7 +3455,6 @@ var (
        http2VerboseLogs    bool
        http2logFrameWrites bool
        http2logFrameReads  bool
-       http2inTests        bool
 
        // Enabling extended CONNECT by causes browsers to attempt to use
        // WebSockets-over-HTTP/2. This results in problems when the server's websocket
@@ -4103,6 +4134,10 @@ type http2Server struct {
 type http2serverInternalState struct {
        mu          sync.Mutex
        activeConns map[*http2serverConn]struct{}
+
+       // Pool of error channels. This is per-Server rather than global
+       // because channels can't be reused across synctest bubbles.
+       errChanPool sync.Pool
 }
 
 func (s *http2serverInternalState) registerConn(sc *http2serverConn) {
@@ -4134,6 +4169,27 @@ func (s *http2serverInternalState) startGracefulShutdown() {
        s.mu.Unlock()
 }
 
+// Global error channel pool used for uninitialized Servers.
+// We use a per-Server pool when possible to avoid using channels across synctest bubbles.
+var http2errChanPool = sync.Pool{
+       New: func() any { return make(chan error, 1) },
+}
+
+func (s *http2serverInternalState) getErrChan() chan error {
+       if s == nil {
+               return http2errChanPool.Get().(chan error) // Server used without calling ConfigureServer
+       }
+       return s.errChanPool.Get().(chan error)
+}
+
+func (s *http2serverInternalState) putErrChan(ch chan error) {
+       if s == nil {
+               http2errChanPool.Put(ch) // Server used without calling ConfigureServer
+               return
+       }
+       s.errChanPool.Put(ch)
+}
+
 // ConfigureServer adds HTTP/2 support to a net/http Server.
 //
 // The configuration conf may be nil.
@@ -4146,7 +4202,10 @@ func http2ConfigureServer(s *Server, conf *http2Server) error {
        if conf == nil {
                conf = new(http2Server)
        }
-       conf.state = &http2serverInternalState{activeConns: make(map[*http2serverConn]struct{})}
+       conf.state = &http2serverInternalState{
+               activeConns: make(map[*http2serverConn]struct{}),
+               errChanPool: sync.Pool{New: func() any { return make(chan error, 1) }},
+       }
        if h1, h2 := s, conf; h2.IdleTimeout == 0 {
                if h1.IdleTimeout != 0 {
                        h2.IdleTimeout = h1.IdleTimeout
@@ -5052,25 +5111,6 @@ func (sc *http2serverConn) readPreface() error {
        }
 }
 
-var http2errChanPool = sync.Pool{
-       New: func() interface{} { return make(chan error, 1) },
-}
-
-func http2getErrChan() chan error {
-       if http2inTests {
-               // Channels cannot be reused across synctest tests.
-               return make(chan error, 1)
-       } else {
-               return http2errChanPool.Get().(chan error)
-       }
-}
-
-func http2putErrChan(ch chan error) {
-       if !http2inTests {
-               http2errChanPool.Put(ch)
-       }
-}
-
 var http2writeDataPool = sync.Pool{
        New: func() interface{} { return new(http2writeData) },
 }
@@ -5078,7 +5118,7 @@ var http2writeDataPool = sync.Pool{
 // writeDataFromHandler writes DATA response frames from a handler on
 // the given stream.
 func (sc *http2serverConn) writeDataFromHandler(stream *http2stream, data []byte, endStream bool) error {
-       ch := http2getErrChan()
+       ch := sc.srv.state.getErrChan()
        writeArg := http2writeDataPool.Get().(*http2writeData)
        *writeArg = http2writeData{stream.id, data, endStream}
        err := sc.writeFrameFromHandler(http2FrameWriteRequest{
@@ -5110,7 +5150,7 @@ func (sc *http2serverConn) writeDataFromHandler(stream *http2stream, data []byte
                        return http2errStreamClosed
                }
        }
-       http2putErrChan(ch)
+       sc.srv.state.putErrChan(ch)
        if frameWriteDone {
                http2writeDataPool.Put(writeArg)
        }
@@ -6364,7 +6404,7 @@ func (sc *http2serverConn) writeHeaders(st *http2stream, headerData *http2writeR
                // waiting for this frame to be written, so an http.Flush mid-handler
                // writes out the correct value of keys, before a handler later potentially
                // mutates it.
-               errc = http2getErrChan()
+               errc = sc.srv.state.getErrChan()
        }
        if err := sc.writeFrameFromHandler(http2FrameWriteRequest{
                write:  headerData,
@@ -6376,7 +6416,7 @@ func (sc *http2serverConn) writeHeaders(st *http2stream, headerData *http2writeR
        if errc != nil {
                select {
                case err := <-errc:
-                       http2putErrChan(errc)
+                       sc.srv.state.putErrChan(errc)
                        return err
                case <-sc.doneServing:
                        return http2errClientDisconnected
@@ -7057,7 +7097,7 @@ func (w *http2responseWriter) Push(target string, opts *PushOptions) error {
                method: opts.Method,
                url:    u,
                header: http2cloneHeader(opts.Header),
-               done:   http2getErrChan(),
+               done:   sc.srv.state.getErrChan(),
        }
 
        select {
@@ -7074,7 +7114,7 @@ func (w *http2responseWriter) Push(target string, opts *PushOptions) error {
        case <-st.cw:
                return http2errStreamClosed
        case err := <-msg.done:
-               http2putErrChan(msg.done)
+               sc.srv.state.putErrChan(msg.done)
                return err
        }
 }
@@ -7577,6 +7617,7 @@ type http2ClientConn struct {
        readIdleTimeout             time.Duration
        pingTimeout                 time.Duration
        extendedConnectAllowed      bool
+       strictMaxConcurrentStreams  bool
 
        // rstStreamPingsBlocked works around an unfortunate gRPC behavior.
        // gRPC strictly limits the number of PING frames that it will receive.
@@ -8007,7 +8048,8 @@ func (t *http2Transport) newClientConn(c net.Conn, singleUse bool) (*http2Client
                initialWindowSize:           65535,    // spec default
                initialStreamRecvWindowSize: conf.MaxUploadBufferPerStream,
                maxConcurrentStreams:        http2initialMaxConcurrentStreams, // "infinite", per spec. Use a smaller value until we have received server settings.
-               peerMaxHeaderListSize:       0xffffffffffffffff,               // "infinite", per spec. Use 2^64-1 instead.
+               strictMaxConcurrentStreams:  conf.StrictMaxConcurrentRequests,
+               peerMaxHeaderListSize:       0xffffffffffffffff, // "infinite", per spec. Use 2^64-1 instead.
                streams:                     make(map[uint32]*http2clientStream),
                singleUse:                   singleUse,
                seenSettingsChan:            make(chan struct{}),
@@ -8241,7 +8283,7 @@ func (cc *http2ClientConn) idleStateLocked() (st http2clientConnIdleState) {
                return
        }
        var maxConcurrentOkay bool
-       if cc.t.StrictMaxConcurrentStreams {
+       if cc.strictMaxConcurrentStreams {
                // We'll tell the caller we can take a new request to
                // prevent the caller from dialing a new TCP
                // connection, but then we'll block later before
@@ -10884,6 +10926,8 @@ type http2OpenStreamOptions struct {
        // PusherID is zero if the stream was initiated by the client. Otherwise,
        // PusherID names the stream that pushed the newly opened stream.
        PusherID uint32
+       // priority is used to set the priority of the newly opened stream.
+       priority http2PriorityParam
 }
 
 // FrameWriteRequest is a request to write a frame.
@@ -11095,7 +11139,7 @@ func (p *http2writeQueuePool) get() *http2writeQueue {
 }
 
 // RFC 7540, Section 5.3.5: the default weight is 16.
-const http2priorityDefaultWeight = 15 // 16 = 15 + 1
+const http2priorityDefaultWeightRFC7540 = 15 // 16 = 15 + 1
 
 // PriorityWriteSchedulerConfig configures a priorityWriteScheduler.
 type http2PriorityWriteSchedulerConfig struct {
@@ -11150,8 +11194,8 @@ func http2NewPriorityWriteScheduler(cfg *http2PriorityWriteSchedulerConfig) http
                }
        }
 
-       ws := &http2priorityWriteScheduler{
-               nodes:                make(map[uint32]*http2priorityNode),
+       ws := &http2priorityWriteSchedulerRFC7540{
+               nodes:                make(map[uint32]*http2priorityNodeRFC7540),
                maxClosedNodesInTree: cfg.MaxClosedNodesInTree,
                maxIdleNodesInTree:   cfg.MaxIdleNodesInTree,
                enableWriteThrottle:  cfg.ThrottleOutOfOrderWrites,
@@ -11165,32 +11209,32 @@ func http2NewPriorityWriteScheduler(cfg *http2PriorityWriteSchedulerConfig) http
        return ws
 }
 
-type http2priorityNodeState int
+type http2priorityNodeStateRFC7540 int
 
 const (
-       http2priorityNodeOpen http2priorityNodeState = iota
-       http2priorityNodeClosed
-       http2priorityNodeIdle
+       http2priorityNodeOpenRFC7540 http2priorityNodeStateRFC7540 = iota
+       http2priorityNodeClosedRFC7540
+       http2priorityNodeIdleRFC7540
 )
 
-// priorityNode is a node in an HTTP/2 priority tree.
+// priorityNodeRFC7540 is a node in an HTTP/2 priority tree.
 // Each node is associated with a single stream ID.
 // See RFC 7540, Section 5.3.
-type http2priorityNode struct {
-       q            http2writeQueue        // queue of pending frames to write
-       id           uint32                 // id of the stream, or 0 for the root of the tree
-       weight       uint8                  // the actual weight is weight+1, so the value is in [1,256]
-       state        http2priorityNodeState // open | closed | idle
-       bytes        int64                  // number of bytes written by this node, or 0 if closed
-       subtreeBytes int64                  // sum(node.bytes) of all nodes in this subtree
+type http2priorityNodeRFC7540 struct {
+       q            http2writeQueue               // queue of pending frames to write
+       id           uint32                        // id of the stream, or 0 for the root of the tree
+       weight       uint8                         // the actual weight is weight+1, so the value is in [1,256]
+       state        http2priorityNodeStateRFC7540 // open | closed | idle
+       bytes        int64                         // number of bytes written by this node, or 0 if closed
+       subtreeBytes int64                         // sum(node.bytes) of all nodes in this subtree
 
        // These links form the priority tree.
-       parent     *http2priorityNode
-       kids       *http2priorityNode // start of the kids list
-       prev, next *http2priorityNode // doubly-linked list of siblings
+       parent     *http2priorityNodeRFC7540
+       kids       *http2priorityNodeRFC7540 // start of the kids list
+       prev, next *http2priorityNodeRFC7540 // doubly-linked list of siblings
 }
 
-func (n *http2priorityNode) setParent(parent *http2priorityNode) {
+func (n *http2priorityNodeRFC7540) setParent(parent *http2priorityNodeRFC7540) {
        if n == parent {
                panic("setParent to self")
        }
@@ -11225,7 +11269,7 @@ func (n *http2priorityNode) setParent(parent *http2priorityNode) {
        }
 }
 
-func (n *http2priorityNode) addBytes(b int64) {
+func (n *http2priorityNodeRFC7540) addBytes(b int64) {
        n.bytes += b
        for ; n != nil; n = n.parent {
                n.subtreeBytes += b
@@ -11238,7 +11282,7 @@ func (n *http2priorityNode) addBytes(b int64) {
 //
 // f(n, openParent) takes two arguments: the node to visit, n, and a bool that is true
 // if any ancestor p of n is still open (ignoring the root node).
-func (n *http2priorityNode) walkReadyInOrder(openParent bool, tmp *[]*http2priorityNode, f func(*http2priorityNode, bool) bool) bool {
+func (n *http2priorityNodeRFC7540) walkReadyInOrder(openParent bool, tmp *[]*http2priorityNodeRFC7540, f func(*http2priorityNodeRFC7540, bool) bool) bool {
        if !n.q.empty() && f(n, openParent) {
                return true
        }
@@ -11249,7 +11293,7 @@ func (n *http2priorityNode) walkReadyInOrder(openParent bool, tmp *[]*http2prior
        // Don't consider the root "open" when updating openParent since
        // we can't send data frames on the root stream (only control frames).
        if n.id != 0 {
-               openParent = openParent || (n.state == http2priorityNodeOpen)
+               openParent = openParent || (n.state == http2priorityNodeOpenRFC7540)
        }
 
        // Common case: only one kid or all kids have the same weight.
@@ -11279,7 +11323,7 @@ func (n *http2priorityNode) walkReadyInOrder(openParent bool, tmp *[]*http2prior
                *tmp = append(*tmp, n.kids)
                n.kids.setParent(nil)
        }
-       sort.Sort(http2sortPriorityNodeSiblings(*tmp))
+       sort.Sort(http2sortPriorityNodeSiblingsRFC7540(*tmp))
        for i := len(*tmp) - 1; i >= 0; i-- {
                (*tmp)[i].setParent(n) // setParent inserts at the head of n.kids
        }
@@ -11291,13 +11335,13 @@ func (n *http2priorityNode) walkReadyInOrder(openParent bool, tmp *[]*http2prior
        return false
 }
 
-type http2sortPriorityNodeSiblings []*http2priorityNode
+type http2sortPriorityNodeSiblingsRFC7540 []*http2priorityNodeRFC7540
 
-func (z http2sortPriorityNodeSiblings) Len() int { return len(z) }
+func (z http2sortPriorityNodeSiblingsRFC7540) Len() int { return len(z) }
 
-func (z http2sortPriorityNodeSiblings) Swap(i, k int) { z[i], z[k] = z[k], z[i] }
+func (z http2sortPriorityNodeSiblingsRFC7540) Swap(i, k int) { z[i], z[k] = z[k], z[i] }
 
-func (z http2sortPriorityNodeSiblings) Less(i, k int) bool {
+func (z http2sortPriorityNodeSiblingsRFC7540) Less(i, k int) bool {
        // Prefer the subtree that has sent fewer bytes relative to its weight.
        // See sections 5.3.2 and 5.3.4.
        wi, bi := float64(z[i].weight+1), float64(z[i].subtreeBytes)
@@ -11311,13 +11355,13 @@ func (z http2sortPriorityNodeSiblings) Less(i, k int) bool {
        return bi/bk <= wi/wk
 }
 
-type http2priorityWriteScheduler struct {
+type http2priorityWriteSchedulerRFC7540 struct {
        // root is the root of the priority tree, where root.id = 0.
        // The root queues control frames that are not associated with any stream.
-       root http2priorityNode
+       root http2priorityNodeRFC7540
 
        // nodes maps stream ids to priority tree nodes.
-       nodes map[uint32]*http2priorityNode
+       nodes map[uint32]*http2priorityNodeRFC7540
 
        // maxID is the maximum stream id in nodes.
        maxID uint32
@@ -11325,7 +11369,7 @@ type http2priorityWriteScheduler struct {
        // lists of nodes that have been closed or are idle, but are kept in
        // the tree for improved prioritization. When the lengths exceed either
        // maxClosedNodesInTree or maxIdleNodesInTree, old nodes are discarded.
-       closedNodes, idleNodes []*http2priorityNode
+       closedNodes, idleNodes []*http2priorityNodeRFC7540
 
        // From the config.
        maxClosedNodesInTree int
@@ -11334,19 +11378,19 @@ type http2priorityWriteScheduler struct {
        enableWriteThrottle  bool
 
        // tmp is scratch space for priorityNode.walkReadyInOrder to reduce allocations.
-       tmp []*http2priorityNode
+       tmp []*http2priorityNodeRFC7540
 
        // pool of empty queues for reuse.
        queuePool http2writeQueuePool
 }
 
-func (ws *http2priorityWriteScheduler) OpenStream(streamID uint32, options http2OpenStreamOptions) {
+func (ws *http2priorityWriteSchedulerRFC7540) OpenStream(streamID uint32, options http2OpenStreamOptions) {
        // The stream may be currently idle but cannot be opened or closed.
        if curr := ws.nodes[streamID]; curr != nil {
-               if curr.state != http2priorityNodeIdle {
+               if curr.state != http2priorityNodeIdleRFC7540 {
                        panic(fmt.Sprintf("stream %d already opened", streamID))
                }
-               curr.state = http2priorityNodeOpen
+               curr.state = http2priorityNodeOpenRFC7540
                return
        }
 
@@ -11358,11 +11402,11 @@ func (ws *http2priorityWriteScheduler) OpenStream(streamID uint32, options http2
        if parent == nil {
                parent = &ws.root
        }
-       n := &http2priorityNode{
+       n := &http2priorityNodeRFC7540{
                q:      *ws.queuePool.get(),
                id:     streamID,
-               weight: http2priorityDefaultWeight,
-               state:  http2priorityNodeOpen,
+               weight: http2priorityDefaultWeightRFC7540,
+               state:  http2priorityNodeOpenRFC7540,
        }
        n.setParent(parent)
        ws.nodes[streamID] = n
@@ -11371,19 +11415,19 @@ func (ws *http2priorityWriteScheduler) OpenStream(streamID uint32, options http2
        }
 }
 
-func (ws *http2priorityWriteScheduler) CloseStream(streamID uint32) {
+func (ws *http2priorityWriteSchedulerRFC7540) CloseStream(streamID uint32) {
        if streamID == 0 {
                panic("violation of WriteScheduler interface: cannot close stream 0")
        }
        if ws.nodes[streamID] == nil {
                panic(fmt.Sprintf("violation of WriteScheduler interface: unknown stream %d", streamID))
        }
-       if ws.nodes[streamID].state != http2priorityNodeOpen {
+       if ws.nodes[streamID].state != http2priorityNodeOpenRFC7540 {
                panic(fmt.Sprintf("violation of WriteScheduler interface: stream %d already closed", streamID))
        }
 
        n := ws.nodes[streamID]
-       n.state = http2priorityNodeClosed
+       n.state = http2priorityNodeClosedRFC7540
        n.addBytes(-n.bytes)
 
        q := n.q
@@ -11396,7 +11440,7 @@ func (ws *http2priorityWriteScheduler) CloseStream(streamID uint32) {
        }
 }
 
-func (ws *http2priorityWriteScheduler) AdjustStream(streamID uint32, priority http2PriorityParam) {
+func (ws *http2priorityWriteSchedulerRFC7540) AdjustStream(streamID uint32, priority http2PriorityParam) {
        if streamID == 0 {
                panic("adjustPriority on root")
        }
@@ -11410,11 +11454,11 @@ func (ws *http2priorityWriteScheduler) AdjustStream(streamID uint32, priority ht
                        return
                }
                ws.maxID = streamID
-               n = &http2priorityNode{
+               n = &http2priorityNodeRFC7540{
                        q:      *ws.queuePool.get(),
                        id:     streamID,
-                       weight: http2priorityDefaultWeight,
-                       state:  http2priorityNodeIdle,
+                       weight: http2priorityDefaultWeightRFC7540,
+                       state:  http2priorityNodeIdleRFC7540,
                }
                n.setParent(&ws.root)
                ws.nodes[streamID] = n
@@ -11426,7 +11470,7 @@ func (ws *http2priorityWriteScheduler) AdjustStream(streamID uint32, priority ht
        parent := ws.nodes[priority.StreamDep]
        if parent == nil {
                n.setParent(&ws.root)
-               n.weight = http2priorityDefaultWeight
+               n.weight = http2priorityDefaultWeightRFC7540
                return
        }
 
@@ -11467,8 +11511,8 @@ func (ws *http2priorityWriteScheduler) AdjustStream(streamID uint32, priority ht
        n.weight = priority.Weight
 }
 
-func (ws *http2priorityWriteScheduler) Push(wr http2FrameWriteRequest) {
-       var n *http2priorityNode
+func (ws *http2priorityWriteSchedulerRFC7540) Push(wr http2FrameWriteRequest) {
+       var n *http2priorityNodeRFC7540
        if wr.isControl() {
                n = &ws.root
        } else {
@@ -11487,8 +11531,8 @@ func (ws *http2priorityWriteScheduler) Push(wr http2FrameWriteRequest) {
        n.q.push(wr)
 }
 
-func (ws *http2priorityWriteScheduler) Pop() (wr http2FrameWriteRequest, ok bool) {
-       ws.root.walkReadyInOrder(false, &ws.tmp, func(n *http2priorityNode, openParent bool) bool {
+func (ws *http2priorityWriteSchedulerRFC7540) Pop() (wr http2FrameWriteRequest, ok bool) {
+       ws.root.walkReadyInOrder(false, &ws.tmp, func(n *http2priorityNodeRFC7540, openParent bool) bool {
                limit := int32(math.MaxInt32)
                if openParent {
                        limit = ws.writeThrottleLimit
@@ -11514,7 +11558,7 @@ func (ws *http2priorityWriteScheduler) Pop() (wr http2FrameWriteRequest, ok bool
        return wr, ok
 }
 
-func (ws *http2priorityWriteScheduler) addClosedOrIdleNode(list *[]*http2priorityNode, maxSize int, n *http2priorityNode) {
+func (ws *http2priorityWriteSchedulerRFC7540) addClosedOrIdleNode(list *[]*http2priorityNodeRFC7540, maxSize int, n *http2priorityNodeRFC7540) {
        if maxSize == 0 {
                return
        }
@@ -11528,7 +11572,7 @@ func (ws *http2priorityWriteScheduler) addClosedOrIdleNode(list *[]*http2priorit
        *list = append(*list, n)
 }
 
-func (ws *http2priorityWriteScheduler) removeNode(n *http2priorityNode) {
+func (ws *http2priorityWriteSchedulerRFC7540) removeNode(n *http2priorityNodeRFC7540) {
        for n.kids != nil {
                n.kids.setParent(n.parent)
        }
@@ -11536,6 +11580,199 @@ func (ws *http2priorityWriteScheduler) removeNode(n *http2priorityNode) {
        delete(ws.nodes, n.id)
 }
 
+type http2streamMetadata struct {
+       location *http2writeQueue
+       priority http2PriorityParam
+}
+
+type http2priorityWriteSchedulerRFC9218 struct {
+       // control contains control frames (SETTINGS, PING, etc.).
+       control http2writeQueue
+
+       // heads contain the head of a circular list of streams.
+       // We put these heads within a nested array that represents urgency and
+       // incremental, as defined in
+       // https://www.rfc-editor.org/rfc/rfc9218.html#name-priority-parameters.
+       // 8 represents u=0 up to u=7, and 2 represents i=false and i=true.
+       heads [8][2]*http2writeQueue
+
+       // streams contains a mapping between each stream ID and their metadata, so
+       // we can quickly locate them when needing to, for example, adjust their
+       // priority.
+       streams map[uint32]http2streamMetadata
+
+       // queuePool are empty queues for reuse.
+       queuePool http2writeQueuePool
+
+       // prioritizeIncremental is used to determine whether we should prioritize
+       // incremental streams or not, when urgency is the same in a given Pop()
+       // call.
+       prioritizeIncremental bool
+}
+
+func http2newPriorityWriteSchedulerRFC9128() http2WriteScheduler {
+       ws := &http2priorityWriteSchedulerRFC9218{
+               streams: make(map[uint32]http2streamMetadata),
+       }
+       return ws
+}
+
+func (ws *http2priorityWriteSchedulerRFC9218) OpenStream(streamID uint32, opt http2OpenStreamOptions) {
+       if ws.streams[streamID].location != nil {
+               panic(fmt.Errorf("stream %d already opened", streamID))
+       }
+       q := ws.queuePool.get()
+       ws.streams[streamID] = http2streamMetadata{
+               location: q,
+               priority: opt.priority,
+       }
+
+       u, i := opt.priority.urgency, opt.priority.incremental
+       if ws.heads[u][i] == nil {
+               ws.heads[u][i] = q
+               q.next = q
+               q.prev = q
+       } else {
+               // Queues are stored in a ring.
+               // Insert the new stream before ws.head, putting it at the end of the list.
+               q.prev = ws.heads[u][i].prev
+               q.next = ws.heads[u][i]
+               q.prev.next = q
+               q.next.prev = q
+       }
+}
+
+func (ws *http2priorityWriteSchedulerRFC9218) CloseStream(streamID uint32) {
+       metadata := ws.streams[streamID]
+       q, u, i := metadata.location, metadata.priority.urgency, metadata.priority.incremental
+       if q == nil {
+               return
+       }
+       if q.next == q {
+               // This was the only open stream.
+               ws.heads[u][i] = nil
+       } else {
+               q.prev.next = q.next
+               q.next.prev = q.prev
+               if ws.heads[u][i] == q {
+                       ws.heads[u][i] = q.next
+               }
+       }
+       delete(ws.streams, streamID)
+       ws.queuePool.put(q)
+}
+
+func (ws *http2priorityWriteSchedulerRFC9218) AdjustStream(streamID uint32, priority http2PriorityParam) {
+       metadata := ws.streams[streamID]
+       q, u, i := metadata.location, metadata.priority.urgency, metadata.priority.incremental
+       if q == nil {
+               return
+       }
+
+       // Remove stream from current location.
+       if q.next == q {
+               // This was the only open stream.
+               ws.heads[u][i] = nil
+       } else {
+               q.prev.next = q.next
+               q.next.prev = q.prev
+               if ws.heads[u][i] == q {
+                       ws.heads[u][i] = q.next
+               }
+       }
+
+       // Insert stream to the new queue.
+       u, i = priority.urgency, priority.incremental
+       if ws.heads[u][i] == nil {
+               ws.heads[u][i] = q
+               q.next = q
+               q.prev = q
+       } else {
+               // Queues are stored in a ring.
+               // Insert the new stream before ws.head, putting it at the end of the list.
+               q.prev = ws.heads[u][i].prev
+               q.next = ws.heads[u][i]
+               q.prev.next = q
+               q.next.prev = q
+       }
+}
+
+func (ws *http2priorityWriteSchedulerRFC9218) Push(wr http2FrameWriteRequest) {
+       if wr.isControl() {
+               ws.control.push(wr)
+               return
+       }
+       q := ws.streams[wr.StreamID()].location
+       if q == nil {
+               // This is a closed stream.
+               // wr should not be a HEADERS or DATA frame.
+               // We push the request onto the control queue.
+               if wr.DataSize() > 0 {
+                       panic("add DATA on non-open stream")
+               }
+               ws.control.push(wr)
+               return
+       }
+       q.push(wr)
+}
+
+func (ws *http2priorityWriteSchedulerRFC9218) Pop() (http2FrameWriteRequest, bool) {
+       // Control and RST_STREAM frames first.
+       if !ws.control.empty() {
+               return ws.control.shift(), true
+       }
+
+       // On the next Pop(), we want to prioritize incremental if we prioritized
+       // non-incremental request of the same urgency this time. Vice-versa.
+       // i.e. when there are incremental and non-incremental requests at the same
+       // priority, we give 50% of our bandwidth to the incremental ones in
+       // aggregate and 50% to the first non-incremental one (since
+       // non-incremental streams do not use round-robin writes).
+       ws.prioritizeIncremental = !ws.prioritizeIncremental
+
+       // Always prioritize lowest u (i.e. highest urgency level).
+       for u := range ws.heads {
+               for i := range ws.heads[u] {
+                       // When we want to prioritize incremental, we try to pop i=true
+                       // first before i=false when u is the same.
+                       if ws.prioritizeIncremental {
+                               i = (i + 1) % 2
+                       }
+                       q := ws.heads[u][i]
+                       if q == nil {
+                               continue
+                       }
+                       for {
+                               if wr, ok := q.consume(math.MaxInt32); ok {
+                                       if i == 1 {
+                                               // For incremental streams, we update head to q.next so
+                                               // we can round-robin between multiple streams that can
+                                               // immediately benefit from partial writes.
+                                               ws.heads[u][i] = q.next
+                                       } else {
+                                               // For non-incremental streams, we try to finish one to
+                                               // completion rather than doing round-robin. However,
+                                               // we update head here so that if q.consume() is !ok
+                                               // (e.g. the stream has no more frame to consume), head
+                                               // is updated to the next q that has frames to consume
+                                               // on future iterations. This way, we do not prioritize
+                                               // writing to unavailable stream on next Pop() calls,
+                                               // preventing head-of-line blocking.
+                                               ws.heads[u][i] = q
+                                       }
+                                       return wr, true
+                               }
+                               q = q.next
+                               if q == ws.heads[u][i] {
+                                       break
+                               }
+                       }
+
+               }
+       }
+       return http2FrameWriteRequest{}, false
+}
+
 // NewRandomWriteScheduler constructs a WriteScheduler that ignores HTTP/2
 // priorities. Control frames like SETTINGS and PING are written before DATA
 // frames, but if no control frames are queued and multiple streams have queued
@@ -11622,7 +11859,7 @@ type http2roundRobinWriteScheduler struct {
 }
 
 // newRoundRobinWriteScheduler constructs a new write scheduler.
-// The round robin scheduler priorizes control frames
+// The round robin scheduler prioritizes control frames
 // like SETTINGS and PING over DATA frames.
 // When there are no control frames to send, it performs a round-robin
 // selection from the ready streams.
diff --git a/src/net/http/http2_test.go b/src/net/http/http2_test.go
deleted file mode 100644 (file)
index 7841b05..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright 2025 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build !nethttpomithttp2
-
-package http
-
-func init() {
-       // Disable HTTP/2 internal channel pooling which interferes with synctest.
-       http2inTests = true
-}
index 5e0c24035aba20332830b12b899ec638fdc364ea..e3f8ec79e094d3fd0d55479b3168a02d308ba6b3 100644 (file)
@@ -202,7 +202,7 @@ type EncodeHeadersParam struct {
        DefaultUserAgent string
 }
 
-// EncodeHeadersParam is the result of EncodeHeaders.
+// EncodeHeadersResult is the result of EncodeHeaders.
 type EncodeHeadersResult struct {
        HasBody     bool
        HasTrailers bool
@@ -550,7 +550,7 @@ type ServerRequestResult struct {
 
        // If the request should be rejected, this is a short string suitable for passing
        // to the http2 package's CountError function.
-       // It might be a bit odd to return errors this way rather than returing an error,
+       // It might be a bit odd to return errors this way rather than returning an error,
        // but this ensures we don't forget to include a CountError reason.
        InvalidReason string
 }
index 776b03d941a405882594ca67d8cd459790061ded..862e5fa2a5165d0dd931a9c188a395fe77b37e8d 100644 (file)
@@ -453,7 +453,7 @@ func (up *socksUsernamePassword) Authenticate(ctx context.Context, rw io.ReadWri
                b = append(b, up.Username...)
                b = append(b, byte(len(up.Password)))
                b = append(b, up.Password...)
-               // TODO(mikio): handle IO deadlines and cancelation if
+               // TODO(mikio): handle IO deadlines and cancellation if
                // necessary
                if _, err := rw.Write(b); err != nil {
                        return err
index 4297d408c0477c7488d0ac034a4a58942b6152f2..8b98dfe21c5d303f236b6500a4a1852dece78bb8 100644 (file)
@@ -142,7 +142,7 @@ func testPingPong(t *testing.T, c1, c2 net.Conn) {
 }
 
 // testRacyRead tests that it is safe to mutate the input Read buffer
-// immediately after cancelation has occurred.
+// immediately after cancellation has occurred.
 func testRacyRead(t *testing.T, c1, c2 net.Conn) {
        go chunkedCopy(c2, rand.New(rand.NewSource(0)))
 
@@ -170,7 +170,7 @@ func testRacyRead(t *testing.T, c1, c2 net.Conn) {
 }
 
 // testRacyWrite tests that it is safe to mutate the input Write buffer
-// immediately after cancelation has occurred.
+// immediately after cancellation has occurred.
 func testRacyWrite(t *testing.T, c1, c2 net.Conn) {
        go chunkedCopy(io.Discard, c2)
 
@@ -318,7 +318,7 @@ func testCloseTimeout(t *testing.T, c1, c2 net.Conn) {
        defer wg.Wait()
        wg.Add(3)
 
-       // Test for cancelation upon connection closure.
+       // Test for cancellation upon connection closure.
        c1.SetDeadline(neverTimeout)
        go func() {
                defer wg.Done()
index cf5c27c74579d0ea5e0964207aac0835a80caefb..a2a0c0b3e85f23b7bb2ed350281e5e07d629bffb 100644 (file)
@@ -6,7 +6,7 @@ golang.org/x/crypto/cryptobyte
 golang.org/x/crypto/cryptobyte/asn1
 golang.org/x/crypto/internal/alias
 golang.org/x/crypto/internal/poly1305
-# golang.org/x/net v0.44.0
+# golang.org/x/net v0.44.1-0.20251002015445-edb764c2296f
 ## explicit; go 1.24.0
 golang.org/x/net/dns/dnsmessage
 golang.org/x/net/http/httpguts