feat(quic-interop): multiconnect dispatch + multiplex stream-budget pacing
Two interop-runner gaps closed in one InteropClient pass plus a QuicConnection snapshot helper: 1. multiconnect testcase. The runner's handshakeloss / handshakecorruption tests reuse TESTCASE_CLIENT=multiconnect — 50 sequential connections, each fetching a 1KB file under 30% packet drop or bit-flip, with the runner verifying _count_handshakes()==50 in the pcap. Pre-fix our InteropClient dispatch returned 127 (skip) for "multiconnect", so both tests showed as ?(L1, C1). Added runMulticonnectTest: loops fresh socket + conn + driver + GET + close per URL. Per-iteration qlog files at $QLOGDIR/client-N.sqlog so a stuck iteration leaves a focused trace. 2. multiplex pacing against quic-go. Pre-fix the parallel path chunked the URL list into fixed groups of MULTIPLEX_PARALLELISM=64. Worked against aioquic + picoquic (initial_max_streams_bidi=128) but blew up against quic-go (advertises 100, ramps slowly via MAX_STREAMS_BIDI bumps): second chunk pushed cumulative used past limit, threw QuicStreamLimitException. Now each iteration takes min(MULTIPLEX_PARALLELISM, peerMaxStreamsBidi - used). When budget hits 0, brief 50ms idle waits for the peer's bump. New QuicConnection.localBidiStreamsUsedSnapshot() exposes the consumed-side counter; combined with the existing peerMaxStreamsBidiSnapshot() the InteropClient computes the live available budget without holding streamsLock. Result against quic-go: H, M, LR, L2, C2, C1 pass; was 0/7 at session start (handshake itself failed pre-ALPN-fix), 4/6 after key-update fix, now 6/7. Only L1 (handshakeloss) remains as multiconnect-under-30%-drop flake (same flake picoquic shows). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -978,6 +978,26 @@ class QuicConnection(
|
||||
/** Snapshot of peer-granted uni cap. */
|
||||
fun peerMaxStreamsUniSnapshot(): Long = peerMaxStreamsUni
|
||||
|
||||
/**
|
||||
* Number of client-initiated bidi streams we've allocated so far —
|
||||
* the "consumed" side of the [peerMaxStreamsBidiSnapshot] budget.
|
||||
* Increments on each [openBidiStreamLocked] call and never decreases
|
||||
* (RFC 9000 §4.6: the limit is on cumulative IDs, not concurrent
|
||||
* count).
|
||||
*
|
||||
* Multiplexing callers use this with [peerMaxStreamsBidiSnapshot] to
|
||||
* compute the AVAILABLE budget at any moment (`max - used`) so they
|
||||
* can pace stream creation against peer's MAX_STREAMS_BIDI bumps
|
||||
* instead of throwing [QuicStreamLimitException] on the cap-tightest
|
||||
* peer.
|
||||
*
|
||||
* Read without [streamsLock] — the field is mutated under
|
||||
* `streamsLock`, but callers using this for back-pressure decisions
|
||||
* tolerate a slightly-stale read (worst case: open one fewer stream
|
||||
* than possible this round, get one more next round).
|
||||
*/
|
||||
fun localBidiStreamsUsedSnapshot(): Long = nextLocalBidiIndex
|
||||
|
||||
/**
|
||||
* Coherent point-in-time snapshot of the connection's flow-control
|
||||
* accounting. Acquires [lock] internally so the fields are read
|
||||
|
||||
Reference in New Issue
Block a user