diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f68efd95..ad8e317e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,6 +20,7 @@ jobs: secrets: "inherit" uses: "boinkor-net/ci-baseline-rust/.github/workflows/ci_baseline_rust_tests.yml@main" strategy: + fail-fast: false matrix: rust_toolchain: [nightly, stable] cargo_test_args: diff --git a/governor/CHANGELOG.md b/governor/CHANGELOG.md index 51cf8df2..a1c96c7a 100644 --- a/governor/CHANGELOG.md +++ b/governor/CHANGELOG.md @@ -4,6 +4,17 @@ ## [Unreleased] - ReleaseDate +### Changed + +* The `.per_second` constructor for `Quota` now constructs a quota + that ensures all rate-limiting calls succeed when given values in + excess of 1 billion (previously, this would result in rate limiters + that would incorrectly reject values). Reported in + [#203](https://github.com/antifuchs/governor/issues/203). + +### Contributors +* [@rkd-msw](https://github.com/rkd-msw) + ## [[0.6.0](https://docs.rs/governor/0.6.0/governor/)] - 2023-07-12 ### Added diff --git a/governor/src/gcra.rs b/governor/src/gcra.rs index aba2c195..cf2a0b0b 100644 --- a/governor/src/gcra.rs +++ b/governor/src/gcra.rs @@ -85,7 +85,9 @@ pub(crate) struct Gcra { impl Gcra { pub(crate) fn new(quota: Quota) -> Self { - let tau: Nanos = (quota.replenish_1_per * quota.max_burst.get()).into(); + let tau: Nanos = (cmp::max(quota.replenish_1_per, Duration::from_nanos(1)) + * quota.max_burst.get()) + .into(); let t: Nanos = quota.replenish_1_per.into(); Gcra { t, tau } } diff --git a/governor/tests/direct.rs b/governor/tests/direct.rs index 95602bc2..ca21f13d 100644 --- a/governor/tests/direct.rs +++ b/governor/tests/direct.rs @@ -162,3 +162,27 @@ fn default_direct() { RateLimiter::direct_with_clock(Quota::per_second(nonzero!(20u32)), &clock); assert_eq!(Ok(()), limiter.check()); } + +#[cfg(feature = "std")] +#[test] +fn stresstest_large_quotas() { + use std::{sync::Arc, thread}; + + use governor::middleware::StateInformationMiddleware; + + let quota = Quota::per_second(nonzero!(1_000_000_001u32)); + let rate_limiter = + Arc::new(RateLimiter::direct(quota).with_middleware::()); + + fn rlspin(rl: Arc>) { + for _ in 0..1_000_000 { + rl.check().map_err(|e| dbg!(e)).unwrap(); + } + } + + let rate_limiter2 = rate_limiter.clone(); + thread::spawn(move || { + rlspin(rate_limiter2); + }); + rlspin(rate_limiter); +}