Skip to content

Property-test the Alt-Svc cache state machine Β #33

Description

@passcod

πŸ€–

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:

  1. An origin in failed makes should_use_h3 return None, whatever advertised and confirmed contain.
  2. After confirm_h3(url, port), the confirmed entry's port is exactly port β€” never derived from anywhere else.
  3. confirm_h3 clears the origin's strike count.
  4. record_h3_failure clears advertised, confirmed and strikes, and inserts failed.
  5. should_use_h3 never returns a port differing from the origin's unless follow_advertised_port is set.
  6. record_alt_svc never records an advertisement whose host differs from the origin's (empty host meaning same-host).
  7. cancel_strikes: 0 never demotes, however many cancellations arrive.
  8. Reaching the strike threshold demotes, and demotion is observable as should_use_h3 returning None.
  9. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions