Files
amethyst/quic
Vitor Pamplona 3a5c010993 fix(quic): consecutivePtoCount must advance once per PTO firing
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>
2026-05-09 11:40:36 -04:00
..