mod loop_helper;
#[cfg(windows)]
mod windows;
pub use crate::loop_helper::*;
use std::{
thread,
time::{Duration, Instant},
};
pub type Seconds = f64;
pub type RatePerSecond = f64;
pub type Nanoseconds = u64;
pub type SubsecondNanoseconds = u32;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SpinSleeper {
native_accuracy_ns: u32,
spin_strategy: SpinStrategy,
}
#[cfg(not(windows))]
const DEFAULT_NATIVE_SLEEP_ACCURACY: SubsecondNanoseconds = 125_000;
#[inline]
pub fn native_sleep(duration: Duration) {
#[cfg(windows)]
windows::native_sleep(duration);
#[cfg(not(windows))]
thread::sleep(duration);
}
impl Default for SpinSleeper {
#[inline]
fn default() -> Self {
#[cfg(windows)]
let accuracy = windows::sleep_accuracy();
#[cfg(not(windows))]
let accuracy = DEFAULT_NATIVE_SLEEP_ACCURACY;
SpinSleeper::new(accuracy)
}
}
impl SpinSleeper {
#[inline]
pub fn new(native_accuracy_ns: SubsecondNanoseconds) -> SpinSleeper {
SpinSleeper {
native_accuracy_ns,
spin_strategy: <_>::default(),
}
}
pub fn native_accuracy_ns(self) -> SubsecondNanoseconds {
self.native_accuracy_ns
}
pub fn spin_strategy(self) -> SpinStrategy {
self.spin_strategy
}
pub fn with_spin_strategy(mut self, strategy: SpinStrategy) -> Self {
self.spin_strategy = strategy;
self
}
#[inline]
fn spin_sleep(self, duration: Duration, deadline: Instant) {
let accuracy = Duration::new(0, self.native_accuracy_ns);
if duration > accuracy {
native_sleep(duration - accuracy);
}
match self.spin_strategy {
SpinStrategy::YieldThread => {
while Instant::now() < deadline {
thread::yield_now()
}
}
SpinStrategy::SpinLoopHint => {
while Instant::now() < deadline {
std::hint::spin_loop()
}
}
}
}
pub fn sleep(self, duration: Duration) {
let deadline = Instant::now() + duration;
self.spin_sleep(duration, deadline);
}
pub fn sleep_until(self, deadline: Instant) {
let duration = deadline.saturating_duration_since(Instant::now());
self.spin_sleep(duration, deadline);
}
pub fn sleep_s(self, seconds: Seconds) {
if seconds > 0.0 {
self.sleep(Duration::from_secs_f64(seconds));
}
}
pub fn sleep_ns(self, nanoseconds: Nanoseconds) {
self.sleep(Duration::from_nanos(nanoseconds))
}
}
pub fn sleep(duration: Duration) {
SpinSleeper::default().sleep(duration);
}
pub fn sleep_until(deadline: Instant) {
SpinSleeper::default().sleep_until(deadline);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum SpinStrategy {
YieldThread,
SpinLoopHint,
}
impl Default for SpinStrategy {
#[inline]
fn default() -> Self {
#[cfg(windows)]
return Self::SpinLoopHint;
#[cfg(not(windows))]
Self::YieldThread
}
}
#[cfg(feature = "nondeterministic_tests")]
#[cfg(test)]
mod spin_sleep_test {
use super::*;
const ACCEPTABLE_DELTA_NS: SubsecondNanoseconds = 50_000;
macro_rules! passes_eventually {
($test:expr) => {{
let mut error = None;
for _ in 0..50 {
match ::std::panic::catch_unwind(|| $test) {
Ok(_) => break,
Err(err) => {
error = error.or(Some(err));
thread::sleep(Duration::new(0, 1000));
}
}
}
assert!(
error.is_none(),
"Test failed 50/50 times: {:?}",
error.unwrap()
);
}};
}
#[test]
fn sleep_small() {
passes_eventually!({
let ns_duration = 12_345_678;
let ps = SpinSleeper::new(20_000_000);
ps.sleep(Duration::new(0, 1000));
let before = Instant::now();
ps.sleep(Duration::new(0, ns_duration));
let elapsed = before.elapsed();
println!("Actual: {:?}", elapsed);
assert!(elapsed <= Duration::new(0, ns_duration + ACCEPTABLE_DELTA_NS));
assert!(elapsed >= Duration::new(0, ns_duration - ACCEPTABLE_DELTA_NS));
});
}
#[test]
fn sleep_big() {
passes_eventually!({
let ns_duration = 212_345_678;
let ps = SpinSleeper::new(20_000_000);
ps.sleep(Duration::new(0, 1000));
let before = Instant::now();
ps.sleep(Duration::new(1, ns_duration));
let elapsed = before.elapsed();
println!("Actual: {:?}", elapsed);
assert!(elapsed <= Duration::new(1, ns_duration + ACCEPTABLE_DELTA_NS));
assert!(elapsed >= Duration::new(1, ns_duration - ACCEPTABLE_DELTA_NS));
});
}
#[test]
fn sleep_s() {
passes_eventually!({
let ns_duration = 12_345_678_f64;
let ps = SpinSleeper::new(20_000_000);
ps.sleep_s(0.000001);
let before = Instant::now();
ps.sleep_s(ns_duration / 1_000_000_000_f64);
let elapsed = before.elapsed();
println!("Actual: {:?}", elapsed);
assert!(elapsed <= Duration::new(0, ns_duration.round() as u32 + ACCEPTABLE_DELTA_NS));
assert!(elapsed >= Duration::new(0, ns_duration.round() as u32 - ACCEPTABLE_DELTA_NS));
});
}
#[test]
fn sleep_ns() {
passes_eventually!({
let ns_duration: u32 = 12_345_678;
let ps = SpinSleeper::new(20_000_000);
ps.sleep_ns(1000);
let before = Instant::now();
ps.sleep_ns(ns_duration as u64);
let elapsed = before.elapsed();
println!("Actual: {:?}", elapsed);
assert!(elapsed <= Duration::new(0, ns_duration + ACCEPTABLE_DELTA_NS));
assert!(elapsed >= Duration::new(0, ns_duration - ACCEPTABLE_DELTA_NS));
});
}
}