| // Copyright 2023 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 go1.21 |
| |
| package quic |
| |
| import ( |
| "context" |
| ) |
| |
| // Limits on the number of open streams. |
| // Every connection has separate limits for bidirectional and unidirectional streams. |
| // |
| // Note that the MAX_STREAMS limit includes closed as well as open streams. |
| // Closing a stream doesn't enable an endpoint to open a new one; |
| // only an increase in the MAX_STREAMS limit does. |
| |
| // localStreamLimits are limits on the number of open streams created by us. |
| type localStreamLimits struct { |
| gate gate |
| max int64 // peer-provided MAX_STREAMS |
| opened int64 // number of streams opened by us, -1 when conn is closed |
| } |
| |
| func (lim *localStreamLimits) init() { |
| lim.gate = newGate() |
| } |
| |
| // open creates a new local stream, blocking until MAX_STREAMS quota is available. |
| func (lim *localStreamLimits) open(ctx context.Context, c *Conn) (num int64, err error) { |
| // TODO: Send a STREAMS_BLOCKED when blocked. |
| if err := lim.gate.waitAndLock(ctx, c.testHooks); err != nil { |
| return 0, err |
| } |
| if lim.opened < 0 { |
| lim.gate.unlock(true) |
| return 0, errConnClosed |
| } |
| num = lim.opened |
| lim.opened++ |
| lim.gate.unlock(lim.opened < lim.max) |
| return num, nil |
| } |
| |
| // connHasClosed indicates the connection has been closed, locally or by the peer. |
| func (lim *localStreamLimits) connHasClosed() { |
| lim.gate.lock() |
| lim.opened = -1 |
| lim.gate.unlock(true) |
| } |
| |
| // setMax sets the MAX_STREAMS provided by the peer. |
| func (lim *localStreamLimits) setMax(maxStreams int64) { |
| lim.gate.lock() |
| lim.max = max(lim.max, maxStreams) |
| lim.gate.unlock(lim.opened < lim.max) |
| } |
| |
| // remoteStreamLimits are limits on the number of open streams created by the peer. |
| type remoteStreamLimits struct { |
| max int64 // last MAX_STREAMS sent to the peer |
| opened int64 // number of streams opened by the peer (including subsequently closed ones) |
| closed int64 // number of peer streams in the "closed" state |
| maxOpen int64 // how many streams we want to let the peer simultaneously open |
| sendMax sentVal // set when we should send MAX_STREAMS |
| } |
| |
| func (lim *remoteStreamLimits) init(maxOpen int64) { |
| lim.maxOpen = maxOpen |
| lim.max = min(maxOpen, implicitStreamLimit) // initial limit sent in transport parameters |
| lim.opened = 0 |
| } |
| |
| // open handles the peer opening a new stream. |
| func (lim *remoteStreamLimits) open(id streamID) error { |
| num := id.num() |
| if num >= lim.max { |
| return localTransportError{ |
| code: errStreamLimit, |
| reason: "stream limit exceeded", |
| } |
| } |
| if num >= lim.opened { |
| lim.opened = num + 1 |
| lim.maybeUpdateMax() |
| } |
| return nil |
| } |
| |
| // close handles the peer closing an open stream. |
| func (lim *remoteStreamLimits) close() { |
| lim.closed++ |
| lim.maybeUpdateMax() |
| } |
| |
| // maybeUpdateMax updates the MAX_STREAMS value we will send to the peer. |
| func (lim *remoteStreamLimits) maybeUpdateMax() { |
| newMax := min( |
| // Max streams the peer can have open at once. |
| lim.closed+lim.maxOpen, |
| // Max streams the peer can open with a single frame. |
| lim.opened+implicitStreamLimit, |
| ) |
| avail := lim.max - lim.opened |
| if newMax > lim.max && (avail < 8 || newMax-lim.max >= 2*avail) { |
| // If the peer has less than 8 streams, or if increasing the peer's |
| // stream limit would double it, then send a MAX_STREAMS. |
| lim.max = newMax |
| lim.sendMax.setUnsent() |
| } |
| } |
| |
| // appendFrame appends a MAX_STREAMS frame to the current packet, if necessary. |
| // |
| // It returns true if no more frames need appending, |
| // false if not everything fit in the current packet. |
| func (lim *remoteStreamLimits) appendFrame(w *packetWriter, typ streamType, pnum packetNumber, pto bool) bool { |
| if lim.sendMax.shouldSendPTO(pto) { |
| if !w.appendMaxStreamsFrame(typ, lim.max) { |
| return false |
| } |
| lim.sendMax.setSent(pnum) |
| } |
| return true |
| } |