Here is a minimal repro:
use governor::{
clock::QuantaClock,
middleware::StateInformationMiddleware,
state::{InMemoryState, NotKeyed},
Quota, RateLimiter,
};
use std::sync::Arc;
use std::thread;
fn rlspin(rl: Arc<RateLimiter<NotKeyed, InMemoryState, QuantaClock, StateInformationMiddleware>>) {
for _ in 0..1_000_000 {
rl.check().map_err(|e| dbg!(e)).unwrap();
}
}
fn main() {
let clock = QuantaClock::default();
let quota = Quota::per_second(1_000_000_001.try_into().unwrap());
dbg!(quota);
let rate_limiter: Arc<
RateLimiter<NotKeyed, InMemoryState, QuantaClock, StateInformationMiddleware>,
> = Arc::new(
RateLimiter::direct_with_clock(quota, &clock)
.with_middleware::<StateInformationMiddleware>(),
);
let rate_limiter2 = rate_limiter.clone();
thread::spawn(move || {
rlspin(rate_limiter2);
});
rlspin(rate_limiter);
}
(with a Cargo.toml that just depends on governor 0.6.0)
This reliably fails for me:
$ cargo run --release
Compiling govtest v0.1.0 (/home/robday/src/mntnlake/govtest)
Finished release [optimized] target(s) in 0.98s
Running `target/release/govtest`
[src/main.rs:19] quota = Quota {
max_burst: 1000000001,
replenish_1_per: 0ns,
}
[src/main.rs:12] e = NotUntil {
state: StateSnapshot {
t: Nanos(0ns),
tau: Nanos(0ns),
time_of_measurement: Nanos(224.941ยตs),
tat: Nanos(224.941ยตs),
},
start: QuantaInstant(
Nanos(170098.543484062s),
),
}
thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: NotUntil { state: StateSnapshot { t: Nanos(0ns), tau: Nanos(0ns), time_of_measurement: Nanos(224.941ยตs), tat: Nanos(224.941ยตs) }, start: QuantaInstant(Nanos(170098.543484062s)) }', src/main.rs:12:41
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
However, if I change line 18 to let quota = Quota::per_second(1_000_000_000.try_into().unwrap());
it reliably passes:
$ cargo run --release
Compiling govtest v0.1.0 (/home/robday/src/mntnlake/govtest)
Finished release [optimized] target(s) in 0.88s
Running `target/release/govtest`
[src/main.rs:19] quota = Quota {
max_burst: 1000000000,
replenish_1_per: 1ns,
}
$
and if I comment out the thread::spawn it also passes:
$ cargo run --release
Compiling govtest v0.1.0 (/home/robday/src/mntnlake/govtest)
warning: unused import: `std::thread`
--> src/main.rs:8:5
|
8 | use std::thread;
| ^^^^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
warning: unused variable: `rate_limiter2`
--> src/main.rs:28:9
|
28 | let rate_limiter2 = rate_limiter.clone();
| ^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_rate_limiter2`
|
= note: `#[warn(unused_variables)]` on by default
warning: `govtest` (bin "govtest") generated 2 warnings (run `cargo fix --bin "govtest"` to apply 2 suggestions)
Finished release [optimized] target(s) in 0.73s
Running `target/release/govtest`
[src/main.rs:19] quota = Quota {
max_burst: 1000000001,
replenish_1_per: 0ns,
}
I hit this when attempting to disable rate-limiting by configuring a rate-limit of u32::MAX, so I have an easy workaround of configuring a rate-limit of one billion, which is much higher than I need - but I thought you might appreciate the bug report anyway!