Skip to content

v0.9.0 — Polish and hardening

Pre-release
Pre-release

Choose a tag to compare

@jamesgober jamesgober released this 06 Jun 14:47
· 2 commits to main since this release

throttle-net v0.9.0 — Polish and hardening

Adversarial, correct, fast, documented. v0.9.0 is the hardening release: fuzzed parsers, a loom model check of the lock-free slot accounting, property tests for every limiter invariant, comparative benchmarks against governor, and two new guides. The public API was frozen at 0.8 and is unchanged here — this release is about confidence, not surface.

What is throttle-net?

A general-purpose outbound throttling and resilience library. Where rate-net protects your service from being overwhelmed (inbound), throttle-net protects your service from overwhelming the downstreams it calls — and from being banned by them. It paces outbound work, composes limits across dimensions and scopes, retries, fails fast, queues fairly, adapts its concurrency, syncs to provider headers, and reports what it is doing — on tokio or smol.

What's new in 0.9.0

Fuzzing

Two cargo-fuzz targets exercise the byte-facing parsers, where untrusted input
arrives from the network:

  • retry_afterparse_retry_after against arbitrary bytes and reference instants.
  • provider_headers — every provider header profile against arbitrary header sets.

The contract is "malformed input never panics, only ever returns None / drops the
value." A CI job runs each target as a timed smoke check, and
tests/fuzz_smoke.rs carries an always-on deterministic corpus plus a
pseudo-random sweep of the same property, so the guarantee is checked on every
cargo test on every platform.

This already paid for itself: the fuzz-smoke suite caught an integer-overflow panic
in provider header parsing (an extreme rate-limit reset timestamp combined with an
extreme reference time overflowed the reset - now subtraction). Fixed with
saturating arithmetic and a regression test — see Fixes.

A loom concurrency model check

tests/loom_throttle.rs uses loom to
exhaustively explore the legal thread interleavings of the adaptive limiter's
reserve/release path — the one piece of lock-free state the crate owns itself (the
token bucket lives in better-bucket, model-checked there). It proves two
invariants under arbitrary interleavings:

  • No over-admission — in-flight permits never exceed the limit.
  • No lost slots — once every permit is settled, the in-flight count is back to
    zero; a permit can neither leak a slot nor release one twice (including the
    drop-as-failure path).

The model check is wired through a crate-private --cfg throttle_loom so it
instruments only throttle-net's own atomics and does not switch on the cfg(loom)
paths inside transitive dependencies.

Property tests for every invariant

The proptest suite gained exact-bound coverage for the remaining algorithms:

  • The sliding-window log admits at most limit per window, and reclaims capacity
    exactly as the window slides.
  • Backoff delays stay within the configured ceiling under every jitter mode, and
    the unjittered curve never goes backwards.
  • The adaptive limit always stays within [floor, ceiling] after any sequence of
    outcomes.

Comparative and contention benchmarks

  • comparison_bench measures the uncontended try_acquire floor against
    governor on the same workload.
  • contention_bench measures aggregate throughput at 1 / 4 / 16 / 64 concurrent
    acquirers on a shared throttle, confirming the lock-free path scales rather than
    collapsing under contention.

Guides

Breaking changes

None. The public API was frozen at 0.8. This release adds tests, benchmarks,
docs, and one internal bug fix.

Fixes

  • Provider header parsing overflow. An extreme rate-limit reset timestamp
    combined with an extreme reference time could overflow the reset - now
    subtraction and panic in debug builds. The subtraction now saturates; a past or
    unreachable reset clamps the time-until-reset to zero. Found by the new
    fuzz-smoke suite; covered by a regression test.

Verification

Run on Windows x86_64, Rust stable; the same commands run in CI on Linux, macOS,
and Windows across stable and MSRV 1.85, with dedicated jobs for the smol +
no_std matrix, the loom model check, and the fuzz smoke run:

cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-features
cargo bench --no-run --all-features
RUSTFLAGS="--cfg throttle_loom" cargo test --test loom_throttle \
    --no-default-features --features adaptive --release
cargo +nightly fuzz run retry_after -- -max_total_time=30
cargo +nightly fuzz run provider_headers -- -max_total_time=30
cargo deny check
cargo audit

All green. Counts at this tag (--all-features): 130 unit tests, 11 property
tests, 4 fuzz-smoke tests, 3 circuit-breaker integration tests, 2 retry
integration tests, 1 observability integration test, 67 doctests, plus 2 loom
model checks. The performance targets hold: uncontended try_acquire is ~27 ns
(target < 50 ns) and is benchmarked head-to-head with governor.

What's next

  • v1.0.0 — Stable. First-consumer integration, final captured benchmarks, then
    the stable release with the public API frozen until 2.0.

Installation

[dependencies]
throttle-net = "0.9"

# Optional features:
throttle-net = { version = "0.9", features = ["circuit-breaker", "adaptive", "provider-llm", "metrics", "tracing"] }

# On smol instead of tokio:
throttle-net = { version = "0.9", default-features = false, features = ["smol"] }

MSRV: Rust 1.85.

Documentation


Changelog: CHANGELOG.md.

Full Changelog: v0.8.0...v0.9.0