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);
let phase =
self.tat.saturating_duration_since(now).as_secs_f64() / self.interval.as_secs_f64();
self.interval = interval;
let remaining = Duration::from_secs_f64(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;
assert!(
seconds.is_finite() && seconds <= Duration::MAX.as_secs_f64(),
"GCRA rate is too low to represent as a Duration"
);
let seconds = seconds.max(1e-9);
Duration::from_secs_f64(seconds)
}
pub(crate) fn saturating_add(instant: Instant, duration: Duration) -> Instant {
instant
.checked_add(duration)
.expect("GCRA schedule overflowed Instant")
}