use core::fmt;
use crate::compat::Duration;
use crate::state::RetryState;
use super::Wait;
#[cfg(any(target_has_atomic = "ptr", target_has_atomic = "64"))]
use core::sync::atomic::Ordering;
#[cfg(target_has_atomic = "ptr")]
use core::sync::atomic::AtomicUsize;
#[cfg(target_has_atomic = "64")]
use core::sync::atomic::AtomicU64;
#[cfg(not(target_has_atomic = "64"))]
use core::cell::Cell;
const DEFAULT_JITTER_SEED: u64 = 0x5A5A_5A5A_5A5A_5A5A;
const GAMMA: u64 = 0x9e37_79b9_7f4a_7c15;
#[cfg(target_has_atomic = "ptr")]
static JITTER_NONCE_COUNTER: AtomicUsize = AtomicUsize::new(1);
struct SplitMix64 {
#[cfg(target_has_atomic = "64")]
state: AtomicU64,
#[cfg(not(target_has_atomic = "64"))]
state: Cell<u64>,
}
impl SplitMix64 {
fn new(seed: u64) -> Self {
Self { state: seed.into() }
}
#[cfg(target_has_atomic = "64")]
fn advance(&self) -> u64 {
self.state
.fetch_add(GAMMA, Ordering::Relaxed)
.wrapping_add(GAMMA)
}
#[cfg(not(target_has_atomic = "64"))]
fn advance(&self) -> u64 {
let next = self.state.get().wrapping_add(GAMMA);
self.state.set(next);
next
}
fn next_u64(&self) -> u64 {
let mut z = self.advance();
z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
z ^ (z >> 31)
}
fn next_bounded(&self, max: u64) -> u64 {
if max == u64::MAX {
return self.next_u64();
}
let range = max + 1;
self.next_u64() % range
}
}
impl fmt::Debug for SplitMix64 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SplitMix64").finish_non_exhaustive()
}
}
fn seeded_rng(seed: u64, nonce: u64) -> SplitMix64 {
SplitMix64::new(seed ^ nonce)
}
fn derive_nonce(seed: u64) -> u64 {
SplitMix64::new(seed).next_u64()
}
#[derive(Debug, Clone, Copy)]
enum JitterKind {
Additive(Duration),
Full,
Equal,
Decorrelated,
}
#[derive(Debug)]
pub struct Jittered<W> {
inner: W,
kind: JitterKind,
seed: u64,
rng: SplitMix64,
}
impl<W> Jittered<W> {
fn new(inner: W, kind: JitterKind) -> Self {
Self {
inner,
kind,
seed: DEFAULT_JITTER_SEED,
rng: seeded_rng(DEFAULT_JITTER_SEED, next_jitter_nonce()),
}
}
pub(super) fn additive(inner: W, max_jitter: Duration) -> Self {
Self::new(inner, JitterKind::Additive(max_jitter))
}
pub(super) fn full(inner: W) -> Self {
Self::new(inner, JitterKind::Full)
}
pub(super) fn equal(inner: W) -> Self {
Self::new(inner, JitterKind::Equal)
}
pub(super) fn decorrelated(inner: W) -> Self {
Self::new(inner, JitterKind::Decorrelated)
}
#[must_use]
pub fn with_seed(mut self, seed: u64) -> Self {
self.seed = seed;
self.rng = seeded_rng(seed, derive_nonce(seed));
self
}
#[must_use]
pub fn with_nonce(mut self, nonce: u64) -> Self {
self.rng = seeded_rng(self.seed, nonce);
self
}
}
impl<W: Clone> Clone for Jittered<W> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
kind: self.kind,
seed: self.seed,
rng: seeded_rng(self.seed, next_jitter_nonce()),
}
}
}
impl<W: Wait> Wait for Jittered<W> {
fn next_wait(&self, state: &RetryState) -> Duration {
let base = self.inner.next_wait(state);
match self.kind {
JitterKind::Additive(max_jitter) => {
let jitter = random_jitter_duration(max_jitter, &self.rng);
base.saturating_add(jitter)
}
JitterKind::Full => random_jitter_duration(base, &self.rng),
JitterKind::Equal => {
let half = base / 2;
let jitter = random_jitter_duration(half, &self.rng);
half.saturating_add(jitter)
}
JitterKind::Decorrelated => {
let lower = base;
let upper = state.previous_delay.unwrap_or(lower).saturating_mul(3);
random_duration_in(lower, upper, &self.rng)
}
}
}
}
#[must_use]
pub fn decorrelated_jitter(base: Duration) -> Jittered<super::WaitFixed> {
Jittered::decorrelated(super::fixed(base))
}
fn random_jitter_duration(max_jitter: Duration, rng: &SplitMix64) -> Duration {
random_duration_in(Duration::ZERO, max_jitter, rng)
}
fn random_duration_in(lower: Duration, upper: Duration, rng: &SplitMix64) -> Duration {
const MAX_RANGE_NANOS: u128 = u64::MAX as u128;
let range_nanos = upper.as_nanos().saturating_sub(lower.as_nanos());
if range_nanos == 0 {
return lower;
}
let max_nanos = range_nanos.min(MAX_RANGE_NANOS) as u64;
let random = rng.next_bounded(max_nanos);
lower.saturating_add(Duration::from_nanos(random))
}
#[cfg(target_has_atomic = "ptr")]
fn next_jitter_nonce() -> u64 {
let counter = JITTER_NONCE_COUNTER.fetch_add(1, Ordering::Relaxed) as u64;
#[cfg(feature = "std")]
{
use std::time::{Duration as StdDuration, SystemTime, UNIX_EPOCH};
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(StdDuration::ZERO);
counter ^ (now.as_nanos() as u64)
}
#[cfg(not(feature = "std"))]
{
counter
}
}
#[cfg(not(target_has_atomic = "ptr"))]
fn next_jitter_nonce() -> u64 {
1
}