Skip to main content

nmbrs_rate/
lib.rs

1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! # nmbrs-rate
5//!
6//! Contract & axioms: [SRD 06](../../docs/SRD/06_rate_limiter.md).
7//!
8//! Async-ready token-bucket rate limiter built on
9//! [`tokio::sync::Semaphore`]. Designed for the nmbrs op-dispatch
10//! loop but usable anywhere you need a rate cap that surfaces
11//! *coordinated omission* — i.e., reports the time a caller spent
12//! waiting for a permit, not just the time their op spent
13//! executing.
14//!
15//! ## Design at a glance
16//!
17//! - A **spec** ([`RateSpec`]) declares the target ops/sec, an
18//!   optional burst-recovery ratio, and a [`TimeUnit`] precision
19//!   for the internal tick representation.
20//! - A **limiter** ([`RateLimiter`]) is a long-lived handle that
21//!   spawns a tokio refill task when started. Each
22//!   [`RateLimiter::acquire`] call awaits a permit; the elapsed
23//!   wait is exposed via [`RateLimiter::wait_time_nanos`].
24//! - Live retarget via [`RateLimiter::reconfigure`] swaps the
25//!   spec atomically without stopping the refill task — the next
26//!   acquire reads the new tick-per-op count.
27//!
28//! ## Quick start
29//!
30//! ```no_run
31//! use nmbrs_rate::{RateLimiter, RateSpec};
32//!
33//! # async fn run() {
34//! // 1000 ops/sec target, default burst.
35//! let limiter = RateLimiter::start(RateSpec::new(1_000.0));
36//!
37//! for _ in 0..10_000 {
38//!     let backlog_ticks = limiter.acquire().await;
39//!     // ... do work ...
40//!     # let _ = backlog_ticks;
41//! }
42//!
43//! // Live retarget: bump the ceiling 10x without stopping.
44//! limiter.reconfigure(RateSpec::new(10_000.0)).unwrap();
45//! # }
46//! ```
47//!
48//! ## Spec syntax
49//!
50//! [`RateSpec::parse`] accepts comma-separated forms used by
51//! workload params and CLI flags:
52//!
53//! ```text
54//! 1000              # 1000 ops/s, default burst (1.1x), start verb
55//! 1000,1.5          # 1000 ops/s, 1.5x burst recovery
56//! 1000,1.1,restart  # full form with explicit verb
57//! ```
58//!
59//! ```
60//! use nmbrs_rate::RateSpec;
61//!
62//! let spec = RateSpec::parse("1000,1.5").unwrap();
63//! assert_eq!(spec.ops_per_sec, 1000.0);
64//! assert!((spec.burst_ratio - 1.5).abs() < 1e-9);
65//! ```
66//!
67//! See `docs/SRD/notes/19_rate_limiter.md` for the design brief and
68//! the coordinated-omission rationale.
69
70mod applier;
71mod limiter;
72mod spec;
73
74pub use applier::RateLimiterApplier;
75pub use limiter::RateLimiter;
76pub use spec::{RateSpec, TimeUnit, Verb};