From 4e4c87d75d8ca16d27c2e9a4358ed05f6da1acc0 Mon Sep 17 00:00:00 2001 From: Tim Vilgot Mikael Fredenberg Date: Sun, 21 May 2023 14:13:09 +0200 Subject: [PATCH 1/3] perf(gateway): reuse ratelimiter's cleanup instant The pruning of elapsed permits after the sleep timer's elapsed requires the current time, however, this involves a syscall, so let's reuse it. --- twilight-gateway/src/future.rs | 7 ++++--- twilight-gateway/src/ratelimiter.rs | 28 +++++++++++++++++++--------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/twilight-gateway/src/future.rs b/twilight-gateway/src/future.rs index 29097c93d57..8e85c015f06 100644 --- a/twilight-gateway/src/future.rs +++ b/twilight-gateway/src/future.rs @@ -124,9 +124,10 @@ impl Future for NextMessageFuture<'_> { return Poll::Ready(NextMessageFutureOutput::SendHeartbeat); } - let ratelimited = this.ratelimiter.as_mut().map_or(false, |ratelimiter| { - ratelimiter.poll_available(cx).is_pending() - }); + let ratelimited = this + .ratelimiter + .as_mut() + .map_or(false, |ratelimiter| ratelimiter.poll_ready(cx).is_pending()); // Must poll to register waker. if !ratelimited diff --git a/twilight-gateway/src/ratelimiter.rs b/twilight-gateway/src/ratelimiter.rs index d3f0e7c7a4b..5bdf42aae22 100644 --- a/twilight-gateway/src/ratelimiter.rs +++ b/twilight-gateway/src/ratelimiter.rs @@ -74,17 +74,24 @@ impl CommandRatelimiter { }) } - /// Returns when a ratelimit permit becomes available. + /// Waits for a permit to become available. pub(crate) async fn acquire(&mut self) { - poll_fn(|cx| self.poll_available(cx)).await; + let now = poll_fn(|cx| self.poll_ready(cx)).await; - self.instants.push(Instant::now() + PERIOD); + self.instants.push(now + PERIOD); } - /// Polls for the next time a permit is available. - pub(crate) fn poll_available(&mut self, cx: &mut Context<'_>) -> Poll<()> { + /// Polls for readiness. + /// + /// # Return value + /// + /// The function returns: + /// + /// * `Poll::Pending` if the ratelimiter is full + /// * `Poll::Ready(now)` if the ratelimiter is ready for a new permit. + pub(crate) fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll { if self.instants.len() != self.instants.capacity() { - return Poll::Ready(()); + return Poll::Ready(Instant::now()); } if !self.delay.is_elapsed() { @@ -92,7 +99,8 @@ impl CommandRatelimiter { } let new_deadline = self.instants[0]; - if new_deadline > Instant::now() { + let now = Instant::now(); + if new_deadline > now { tracing::trace!(?new_deadline, old_deadline = ?self.delay.deadline()); self.delay.as_mut().reset(new_deadline); @@ -101,11 +109,13 @@ impl CommandRatelimiter { Poll::Pending } else { - let used_permits = (self.max() - self.available()).into(); + let elapsed_permits = self.instants.partition_point(|&elapsed| elapsed <= now); + let used_permits = self.instants.len() - elapsed_permits; + self.instants.rotate_right(used_permits); self.instants.truncate(used_permits); - Poll::Ready(()) + Poll::Ready(now) } } } From 875b6656d58b1d12c06ecba0291736518625b35e Mon Sep 17 00:00:00 2001 From: Tim Vilgot Mikael Fredenberg Date: Sun, 21 May 2023 14:28:12 +0200 Subject: [PATCH 2/3] feat(gateway): record the duration in the ratelimit event Let's do the calculation for the user & move it to DEBUG as it's now human readable. --- twilight-gateway/src/ratelimiter.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/twilight-gateway/src/ratelimiter.rs b/twilight-gateway/src/ratelimiter.rs index 5bdf42aae22..419fede1a48 100644 --- a/twilight-gateway/src/ratelimiter.rs +++ b/twilight-gateway/src/ratelimiter.rs @@ -101,10 +101,8 @@ impl CommandRatelimiter { let new_deadline = self.instants[0]; let now = Instant::now(); if new_deadline > now { - tracing::trace!(?new_deadline, old_deadline = ?self.delay.deadline()); + tracing::debug!(duration = ?(new_deadline - now), "ratelimited"); self.delay.as_mut().reset(new_deadline); - - // Register waker. _ = self.delay.as_mut().poll(cx); Poll::Pending From 6166c873a5067511762e12a4c2e02e3ad09bc468 Mon Sep 17 00:00:00 2001 From: Tim Vilgot Mikael Fredenberg Date: Thu, 22 Jun 2023 15:23:30 +0200 Subject: [PATCH 3/3] revert huge perf regression --- twilight-gateway/src/ratelimiter.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/twilight-gateway/src/ratelimiter.rs b/twilight-gateway/src/ratelimiter.rs index 419fede1a48..d79c35eaa8c 100644 --- a/twilight-gateway/src/ratelimiter.rs +++ b/twilight-gateway/src/ratelimiter.rs @@ -76,9 +76,9 @@ impl CommandRatelimiter { /// Waits for a permit to become available. pub(crate) async fn acquire(&mut self) { - let now = poll_fn(|cx| self.poll_ready(cx)).await; + poll_fn(|cx| self.poll_ready(cx)).await; - self.instants.push(now + PERIOD); + self.instants.push(Instant::now() + PERIOD); } /// Polls for readiness. @@ -88,10 +88,10 @@ impl CommandRatelimiter { /// The function returns: /// /// * `Poll::Pending` if the ratelimiter is full - /// * `Poll::Ready(now)` if the ratelimiter is ready for a new permit. - pub(crate) fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll { + /// * `Poll::Ready` if the ratelimiter is ready for a new permit. + pub(crate) fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<()> { if self.instants.len() != self.instants.capacity() { - return Poll::Ready(Instant::now()); + return Poll::Ready(()); } if !self.delay.is_elapsed() { @@ -113,7 +113,7 @@ impl CommandRatelimiter { self.instants.rotate_right(used_permits); self.instants.truncate(used_permits); - Poll::Ready(now) + Poll::Ready(()) } } }