You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Every bug found while fixing the HTTP/3 cancellation wedge (#23, #28, #29, #30) lived in one place β the Alt-Svc cache state machine and its interaction with cancellation:
record_h3_failure unreachable under cancellation, so nothing ever demoted a broken origin
confirm_h3 synthesising a confirmed port from the origin's own port when a concurrent failure had cleared both caches
the advertised host silently discarded, so an advertisement for another host read as same-host
cache hits manufacturing HTTP/3 confirmations from a replayed stored version
strike-counter semantics under concurrency
What to build
Generate sequences of cache operations β record_alt_svc, add_hint, confirm_h3, record_h3_failure, record_h3_cancellation, should_use_h3 β over a small pool of origins, and assert the invariants after every operation. proptest is the natural tool.
Start with invariant checking over sequences rather than a full model-based test: it needs no reference model to maintain and still catches this bug class. A reference model would additionally catch behavioural divergence, and is a reasonable later step if the invariants prove too weak.
Invariants worth asserting:
An origin in failed makes should_use_h3 return None, whatever advertised and confirmed contain.
After confirm_h3(url, port), the confirmed entry's port is exactly port β never derived from anywhere else.
confirm_h3 clears the origin's strike count.
record_h3_failure clears advertised, confirmed and strikes, and inserts failed.
should_use_h3 never returns a port differing from the origin's unless follow_advertised_port is set.
record_alt_svc never records an advertisement whose host differs from the origin's (empty host meaning same-host).
cancel_strikes: 0 never demotes, however many cancellations arrive.
Reaching the strike threshold demotes, and demotion is observable as should_use_h3 returning None.
add_hint always produces an actionable entry, since it builds the origin key from the same port it stores.
Invariant 2 is the one that matters most, because it is exactly the bug that shipped and had to be fixed in review: before the fix, confirm_h3 derived the port from the caches and fell back to the origin's own port when both were empty. The sequence that breaks it is three operations long β record_alt_svc with a mismatched port, record_h3_failure, then confirm_h3 β which proptest would find in seconds.
One design consideration: several invariants involve TTLs. AltSvcCache::new already takes the strike window as a constructor parameter precisely so tests can pass a short one, and the TTLs are similarly injectable β worth keeping generated durations small and explicit rather than reaching for a clock abstraction.
Two lesser candidates, recorded but not recommended now
Prove no input can abort the Node process. A robustness property specific to native addons: throw hostile option objects and bodies at Agent and fetch from JS and assert every outcome is a thrown JS error, never a process abort. We leaned on this property when requiring H3AttemptGuard::drop be infallible, since a panic while unwinding aborts. Nothing currently tests it.
Generative differential testing against native fetch.test/helpers.js already compares faith and undici on fixed paths; a generative version would diff status/headers/body/redirected over varied requests. Natural extension of the conformance harness (Design a multi-server conformance test suiteΒ #25) rather than a separate thing.
π€
Every bug found while fixing the HTTP/3 cancellation wedge (#23, #28, #29, #30) lived in one place β the Alt-Svc cache state machine and its interaction with cancellation:
record_h3_failureunreachable under cancellation, so nothing ever demoted a broken originconfirm_h3synthesising a confirmed port from the origin's own port when a concurrent failure had cleared both cachesWhat to build
Generate sequences of cache operations β
record_alt_svc,add_hint,confirm_h3,record_h3_failure,record_h3_cancellation,should_use_h3β over a small pool of origins, and assert the invariants after every operation.proptestis the natural tool.Start with invariant checking over sequences rather than a full model-based test: it needs no reference model to maintain and still catches this bug class. A reference model would additionally catch behavioural divergence, and is a reasonable later step if the invariants prove too weak.
Invariants worth asserting:
failedmakesshould_use_h3returnNone, whateveradvertisedandconfirmedcontain.confirm_h3(url, port), the confirmed entry's port is exactlyportβ never derived from anywhere else.confirm_h3clears the origin's strike count.record_h3_failureclears advertised, confirmed and strikes, and insertsfailed.should_use_h3never returns a port differing from the origin's unlessfollow_advertised_portis set.record_alt_svcnever records an advertisement whose host differs from the origin's (empty host meaning same-host).cancel_strikes: 0never demotes, however many cancellations arrive.should_use_h3returningNone.add_hintalways produces an actionable entry, since it builds the origin key from the same port it stores.Invariant 2 is the one that matters most, because it is exactly the bug that shipped and had to be fixed in review: before the fix,
confirm_h3derived the port from the caches and fell back to the origin's own port when both were empty. The sequence that breaks it is three operations long βrecord_alt_svcwith a mismatched port,record_h3_failure, thenconfirm_h3β which proptest would find in seconds.One design consideration: several invariants involve TTLs.
AltSvcCache::newalready takes the strike window as a constructor parameter precisely so tests can pass a short one, and the TTLs are similarly injectable β worth keeping generated durations small and explicit rather than reaching for a clock abstraction.Two lesser candidates, recorded but not recommended now
Agentandfetchfrom JS and assert every outcome is a thrown JS error, never a process abort. We leaned on this property when requiringH3AttemptGuard::dropbe infallible, since a panic while unwinding aborts. Nothing currently tests it.test/helpers.jsalready compares faith and undici on fixed paths; a generative version would diff status/headers/body/redirected over varied requests. Natural extension of the conformance harness (Design a multi-server conformance test suiteΒ #25) rather than a separate thing.