v0.9.0 — Polish and hardening
Pre-releasethrottle-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_after—parse_retry_afteragainst 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
limitper 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_benchmeasures the uncontendedtry_acquirefloor against
governoron the same workload.contention_benchmeasures 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
docs/COOKBOOK.md— task-oriented recipes for the common problems.docs/MIGRATING_FROM_GOVERNOR.md— API mapping and before/after for moving an outbound setup offgovernor.
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 thereset - 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 auditAll 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