3a5c010993
RFC 9002 §6.2.2 says the consecutive-PTO count goes up by exactly
ONE per PTO timer expiry, so the §6.2.1 backoff is `pto_base * 2^count`
per firing. Pre-fix, the count incremented THREE times per firing:
1. inside `handlePtoFired` itself,
2. inside `requeueInflightForProbe` (which `handlePtoFired` calls),
3. again from the send loop's between-probe re-requeue (RFC §6.2.4
two-packet probe budget calls `requeueInflightForProbe` a
second time).
That made the effective backoff `2^(3*N)` per firing — 1×, 8×, 64×
of pto_base. With pto_base ≈ 150 ms post-handshake, PTO #3 didn't
fire until ~11 s post-handshake, well past the 5 s amp-limited
idle timeout strict servers (quic-go) enforce.
Quic-go's `amplificationlimit` testcase exposed this. The sim
drops client packets 2–7. Our PTO #2 second probe is packet #8
— the first one that gets through. With correct 1× increment
per PTO, packet #8 reaches the server in ~900 ms; pre-fix it
arrived at ~12 s, after quic-go had already destroyed the
connection ("Amplification window limited" → "Destroying
connection: timeout: no recent network activity"). Same root
cause for `handshakeloss` flakiness against quic-go (multiconnect
under 30% loss can't recover within the 30 s per-iteration
budget when PTO ramps to 64× by iteration 3).
Fix:
- Move the count increment to the top of `handlePtoFired`,
before it calls `requeueInflightForProbe`. The threshold
check inside the latter (RFC 9000 §9 — trigger
PATH_PROBE_PTO_THRESHOLD migration) reads the
post-increment value, preserving the prior 2-PTO-firing
trigger semantics.
- Remove the count increment from `requeueInflightForProbe`.
The send loop's between-probe re-requeue stays a no-op for
the count (same PTO firing).
Tests: PtoCryptoRetransmitTest.consecutivePtoCountAdvancesByOnePerPtoFiringNotPerProbePacket
pins the contract (full handlePtoFired → drain → re-requeue →
drain → handlePtoFired sequence; asserts count after each step).
Verified end-to-end against the live interop runner:
- amplificationlimit vs quic-go: 5/5 (was 0/3 — consistent
30 s timeout). Now passes in ~7 s.
- handshakeloss vs quic-go: 5/5 (was 2/3 — flaky multiconnect
iteration timeouts). Now consistently ~30 s.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>