use std::time::{Duration, Instant};
#[derive(Clone, Debug)]
pub struct Gcra {
interval: Duration,
tat: Instant,
}
impl Gcra {
pub fn new(rate_per_second: f64, now: Instant) -> Self {
Self {
interval: rate_to_interval(rate_per_second),
tat: now,
}
}
pub fn rate(&self) -> f64 {
self.interval.as_secs_f64().recip()
}
pub fn interval(&self) -> Duration {
self.interval
}
pub fn next_at(&self, now: Instant) -> Instant {
self.tat.max(now)
}
pub fn set_rate(&mut self, rate_per_second: f64, now: Instant) {
let interval = rate_to_interval(rate_per_second);
if interval == self.interval {
return;
}
let phase =
self.tat.saturating_duration_since(now).as_secs_f64() / self.interval.as_secs_f64();
self.interval = interval;
let remaining = duration_from_secs_saturating(phase * interval.as_secs_f64());
self.tat = saturating_add(now, remaining);
}
pub fn commit(&mut self, dispatched_at: Instant) {
let base = self.next_at(dispatched_at);
self.tat = saturating_add(base, self.interval);
}
pub fn tat(&self) -> Instant {
self.tat
}
}
fn rate_to_interval(rate_per_second: f64) -> Duration {
assert!(
rate_per_second.is_finite() && rate_per_second > 0.0,
"GCRA rate must be finite and positive"
);
let seconds = 1.0 / rate_per_second;
let Ok(interval) = Duration::try_from_secs_f64(seconds) else {
panic!("GCRA rate is too low to represent as a Duration");
};
interval.max(Duration::from_nanos(1))
}
pub(crate) fn saturating_add(instant: Instant, duration: Duration) -> Instant {
if let Some(result) = instant.checked_add(duration) {
return result;
}
let mut lower = 0;
let mut upper = duration.as_nanos();
while lower < upper {
let middle = lower + (upper - lower).div_ceil(2);
if instant.checked_add(duration_from_nanos(middle)).is_some() {
lower = middle;
} else {
upper = middle - 1;
}
}
instant
.checked_add(duration_from_nanos(lower))
.expect("a zero-duration Instant addition must be representable")
}
fn duration_from_nanos(nanos: u128) -> Duration {
const NANOS_PER_SECOND: u128 = 1_000_000_000;
Duration::new(
(nanos / NANOS_PER_SECOND) as u64,
(nanos % NANOS_PER_SECOND) as u32,
)
}
fn duration_from_secs_saturating(seconds: f64) -> Duration {
if seconds.is_nan() || seconds <= 0.0 {
return Duration::ZERO;
}
if !seconds.is_finite() {
return Duration::MAX;
}
match Duration::try_from_secs_f64(seconds) {
Ok(duration) => duration,
Err(_) => Duration::MAX,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn overflowing_instant_addition_saturates_in_the_future() {
let now = Instant::now();
let saturated = saturating_add(now, Duration::MAX);
assert!(saturated > now);
assert!(saturated.checked_add(Duration::from_nanos(1)).is_none());
}
}