diff --git a/twilight-http-ratelimiting/src/actor.rs b/twilight-http-ratelimiting/src/actor.rs index ae0e4ecc58..b46df23a26 100644 --- a/twilight-http-ratelimiting/src/actor.rs +++ b/twilight-http-ratelimiting/src/actor.rs @@ -110,7 +110,7 @@ pub async fn runner( .reset(Instant::now() + GLOBAL_LIMIT_PERIOD); } else if global_remaining == 0 { let now = Instant::now(); - let reset_after = now.saturating_duration_since(global_timer.deadline()); + let reset_after = global_timer.deadline().saturating_duration_since(now); if reset_after.is_zero() { global_remaining = global_limit - 1; global_timer.as_mut().reset(now + GLOBAL_LIMIT_PERIOD); diff --git a/twilight-http-ratelimiting/src/lib.rs b/twilight-http-ratelimiting/src/lib.rs index 57028a3b42..d8735258d0 100644 --- a/twilight-http-ratelimiting/src/lib.rs +++ b/twilight-http-ratelimiting/src/lib.rs @@ -192,7 +192,11 @@ impl RateLimiter { /// # Example /// /// ```no_run - /// # #[tokio::main] async fn main() { + /// # let rt = tokio::runtime::Builder::new_current_thread() + /// # .enable_time() + /// # .build() + /// # .unwrap(); + /// # rt.block_on(async { /// # let rate_limiter = twilight_http_ratelimiting::RateLimiter::default(); /// use twilight_http_ratelimiting::Path; /// @@ -203,7 +207,7 @@ impl RateLimiter { /// let headers = unimplemented!("send /applications/@me request"); /// permit.complete(headers); /// } - /// # } + /// # }); /// ``` #[allow(clippy::missing_panics_doc)] pub fn acquire_if
(&self, path: Path, predicate: P) -> MaybePermitFuture diff --git a/twilight-http-ratelimiting/tests/test.rs b/twilight-http-ratelimiting/tests/test.rs index 3acb8c2b3c..b0e376b554 100644 --- a/twilight-http-ratelimiting/tests/test.rs +++ b/twilight-http-ratelimiting/tests/test.rs @@ -1,10 +1,11 @@ use tokio::{ task, - time::{Duration, Instant}, + time::{advance, Duration, Instant}, }; use twilight_http_ratelimiting::{Path, RateLimitHeaders, RateLimiter, GLOBAL_LIMIT_PERIOD}; const PATH: Path = Path::ApplicationsMe; +const NOT_LIMITED_PATH: Path = Path::InteractionCallback(1); #[tokio::test] async fn acquire_serial() { @@ -55,18 +56,62 @@ async fn bucket() { } #[tokio::test(start_paused = true)] -async fn global() { +async fn global_limit() { let rate_limiter = RateLimiter::new(1); let now = Instant::now(); + drop(rate_limiter.acquire(PATH).await); + assert!(now.elapsed() < GLOBAL_LIMIT_PERIOD, "did not run instantly"); + rate_limiter.acquire(PATH).await.complete(None); + assert!(now.elapsed() < GLOBAL_LIMIT_PERIOD, "did not run instantly"); + rate_limiter.acquire(NOT_LIMITED_PATH).await.complete(None); assert!(now.elapsed() < GLOBAL_LIMIT_PERIOD, "did not run instantly"); rate_limiter.acquire(PATH).await.complete(None); + assert!( + now.elapsed() >= GLOBAL_LIMIT_PERIOD, + "misstimed global refill" + ); +} + +#[tokio::test(start_paused = true)] +async fn global_reset_on_cancel() { + let rate_limiter = RateLimiter::new(1); + + let permit = rate_limiter.acquire(PATH).await; + + advance(GLOBAL_LIMIT_PERIOD / 2).await; + + drop(permit); + + rate_limiter.acquire(PATH).await.complete(None); + let now = Instant::now(); + rate_limiter.acquire(PATH).await.complete(None); assert!( now.elapsed() >= GLOBAL_LIMIT_PERIOD, "misstimed global refill" ); } + +#[tokio::test(start_paused = true)] +async fn global_reset_preemptive() { + let rate_limiter = RateLimiter::new(2); + + rate_limiter.acquire(PATH).await.complete(None); + + advance(GLOBAL_LIMIT_PERIOD).await; + + rate_limiter.acquire(PATH).await.complete(None); + rate_limiter.acquire(PATH).await.complete(None); + + let now = Instant::now(); + rate_limiter.acquire(PATH).await.complete(None); + + assert!( + dbg!(now.elapsed()) >= GLOBAL_LIMIT_PERIOD, + "misstimed global refill" + ); +}