29282634e5
Adds the writer's drain side of the retransmit path. The QuicConnection
gains four `pending*` fields:
- pendingMaxStreamsUni: Long?
- pendingMaxStreamsBidi: Long?
- pendingMaxData: Long?
- pendingMaxStreamData: MutableMap<Long, Long> (keyed by stream id)
Each non-null entry signals "the last extension we sent at this value
was lost; re-emit it". appendFlowControlUpdates now drains all four
ahead of the rolling-extension threshold check, emitting a fresh
frame + RecoveryToken for each pending entry and clearing it.
Step 4 only wires the consumer side; the setter side (loss
dispatcher) is step 6. Tests populate `pending*` directly to exercise
the drain in isolation.
Tests added (8, all pass):
- pendingMaxStreamsUni / Bidi / MaxData / MaxStreamData each
individually drain to a SentPacket carrying the matching token
- multiplePending: all four pending types drain into one packet
(writer drains them sequentially, no fan-out)
- noPending: drain produces no extension tokens
- pendingDrainBeforeThresholdCheck_supersedeOrderObservable:
writer drains the pending value as-is — supersede check is the
setter's responsibility (step 6), not the drain's
- pendingClearedAcrossDrains: a cleared pending stays cleared on
subsequent drains
Mirrors neqo's `fc.rs` retransmit tests in the plan
(`need_max_allowed_frame_after_loss`, `lost_after_increase`,
`multiple_retries_after_frame_pending_is_set`,
`new_retired_before_loss`). The supersede-check tests
(`no_max_allowed_frame_after_old_loss`,
`set_max_active_equal_does_not_set_frame_pending`) belong to step 6
and land there.
https://claude.ai/code/session_01PYYez8a6sjiakyjAxsfCEQ