use core::cell::Cell;
use std::time::Duration;
const SPIN_MAX: u32 = 2048;
const SPIN_START: u32 = 256;
const SPIN_RESTART: u32 = 64;
const SHORT_PARK: Duration = Duration::from_micros(50);
const SPIN_GAP_MAX: u32 = 64;
pub(crate) struct Spin {
budget: Cell<u32>,
}
impl Spin {
pub(super) fn new() -> Self {
Self {
budget: Cell::new(SPIN_START),
}
}
pub(super) fn run(&self, mut found: impl FnMut() -> bool) -> bool {
let budget = self.budget.get();
let mut spent = 0;
let mut gap = 1;
while spent < budget {
for _ in 0..gap {
core::hint::spin_loop();
}
spent += gap;
gap = (gap * 2).min(SPIN_GAP_MAX);
if found() {
self.budget.set((budget * 2).clamp(SPIN_RESTART, SPIN_MAX));
return true;
}
}
self.budget.set(budget / 2);
false
}
pub(super) fn parked_for(&self, slept: Duration) {
if slept < SHORT_PARK {
self.budget.set(self.budget.get().max(SPIN_RESTART));
}
}
}
#[cfg(all(test, not(loom)))]
mod tests {
use std::time::Duration;
use super::{SPIN_MAX, SPIN_RESTART, SPIN_START, Spin};
#[test]
fn a_spin_that_finds_work_doubles_the_budget_and_one_that_runs_out_halves_it() {
let spin = Spin::new();
assert_eq!(spin.budget.get(), SPIN_START);
assert!(spin.run(|| true));
assert_eq!(spin.budget.get(), SPIN_START * 2);
assert!(!spin.run(|| false));
assert_eq!(spin.budget.get(), SPIN_START);
for _ in 0..20 {
spin.run(|| true);
}
assert_eq!(spin.budget.get(), SPIN_MAX, "capped");
for _ in 0..20 {
spin.run(|| false);
}
assert_eq!(spin.budget.get(), 0, "an idle shard stops spinning");
assert!(!spin.run(|| true), "a spent budget does not spin");
}
#[test]
fn a_short_park_starts_the_spin_again() {
let spin = Spin::new();
for _ in 0..20 {
spin.run(|| false);
}
spin.parked_for(Duration::from_millis(5));
assert_eq!(
spin.budget.get(),
0,
"a long sleep: spinning would not have helped"
);
spin.parked_for(Duration::from_micros(3));
assert_eq!(spin.budget.get(), SPIN_RESTART);
}
}