Skip to content

v0.8.0 — Runtime flexibility & feature freeze

Pre-release
Pre-release

Choose a tag to compare

@jamesgober jamesgober released this 06 Jun 05:14
· 5 commits to main since this release

throttle-net v0.8.0 — Runtime flexibility & feature freeze

Run it on the executor you already use, and freeze the surface for 1.0. v0.8.0 makes the waiting acquire surface runtime-agnostic — it runs on tokio or smol with identical call-site code — adds a no_std-capable algorithm core, fills out the example set, and declares the public API frozen for the run to 1.0. No breaking changes.

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, reports what it is doing — and, as of this release, does the waiting on whichever runtime you prefer.

What's new in 0.8.0

Pick your runtime: tokio or smol

The code that does the waiting is now runtime-agnostic. Internally it parks on an event-listener notification and races a wake-up against a timeout with futures-lite, instead of a tokio-specific Notify / select!. The only runtime-specific piece left is the timer, selected by feature. The result: the same async methods run unchanged on either backend.

# tokio (default — nothing to change):
throttle-net = "0.8"

# smol instead:
throttle-net = { version = "0.8", default-features = false, features = ["smol"] }
// Identical on both backends:
let throttle = throttle_net::Throttle::per_second(100);
throttle.acquire().await?;

Requesting the waiting surface without enabling a backend is a clear compile error rather than a confusing one. async-std is intentionally not supported — it is discontinued (RUSTSEC-2025-0052); tokio and smol cover the runtime-flexibility goal.

A no_std-capable algorithm core

With std off, the pure algorithm types — Backoff, BackoffIter, Jitter, and Decision — compile and run without the standard library, alongside VERSION. No clock, no allocator, no async runtime. The limiter surface still requires std.

# Algorithm core only:
throttle-net = { version = "0.8", default-features = false }
use core::time::Duration;
use throttle_net::Backoff;

// Deterministic backoff with no system clock:
let mut delays = Backoff::exponential(Duration::from_millis(50), 2.0).iter_seeded(1);
let _first = delays.next_delay();

Under no_std, Backoff::iter() seeds its jitter from a monotonic counter instead of system entropy; iter_seeded(seed) is fully deterministic on both.

The example set, filled out

The roadmap's worked examples are all present and runnable:

cargo run --example llm_budget                                       # multi-dimensional LLM budgets
cargo run --example retry_backoff                                    # retry with backoff + Retry-After
cargo run --example circuit_breaker      --features circuit-breaker  # trip, shed, recover
cargo run --example adaptive_concurrency --features adaptive         # learn the limit from feedback
cargo run --example per_tenant_quotas                               # per-tenant budgets under a global cap

per_tenant_quotas is new: a Layered global ceiling over a PerKey per-tenant cap, so each tenant gets its own budget and no single noisy tenant can starve the others or the service.

Feature freeze

The public API is frozen as of this release. The remaining 0.x work — fuzzing, loom model checks, comparative benchmarks — is hardening, not API change. Nothing will change incompatibly before 1.0.

Breaking changes

None. smol and no_std are additive; tokio remains the default. The one dependency-footprint change is internal: the tokio feature now pulls only tokio's time feature.

Verification

Run on Windows x86_64, Rust stable; the same commands run in the CI matrix on Linux, macOS, and Windows across stable and MSRV 1.85, with a dedicated job for the smol + no_std runtime matrix:

cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo clippy --all-targets --no-default-features -- -D warnings
cargo clippy --no-default-features --features smol --all-targets -- -D warnings
cargo test --all-features
cargo test                                          # default: tokio backend
cargo test --no-default-features                    # no_std core + its doctests
cargo test --no-default-features --features smol    # smol backend
cargo build --no-default-features                   # no_std lib builds
cargo doc --no-deps && RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features
cargo deny check
cargo audit

All green. The runtime-flexibility exit criterion is covered directly: the full test suite passes on both the tokio and smol timer backends (timing tests that assert exact virtual-clock elapsed time stay tokio-only, since they depend on tokio's paused-time test clock), and the no_std core builds and runs its doctests with std off.

What's next

  • v0.9.0 → 1.0 — Polish & 1.0. Fuzzing, loom concurrency model checks, comparative benchmarks against the alternatives, and the 1.0 release. The public API is already frozen as of this release.

Installation

[dependencies]
throttle-net = "0.8"

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

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

MSRV: Rust 1.85.

Documentation


Changelog: CHANGELOG.md.

Full Changelog: v0.7.0...v0.8.0