use core::hint;
use std::sync::atomic::{AtomicI64, Ordering};
pub const INITIAL_SEQUENCE: i64 = -1;
pub trait WaitStrategy: Send + Sync {
fn wait_for(&self, target: i64, cursor: &AtomicI64) -> i64;
#[inline]
fn signal(&self) {}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct BusySpinWait;
impl WaitStrategy for BusySpinWait {
#[inline]
fn wait_for(&self, target: i64, cursor: &AtomicI64) -> i64 {
loop {
let current = cursor.load(Ordering::Acquire);
if current >= target {
return current;
}
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct SpinLoopHintWait;
impl WaitStrategy for SpinLoopHintWait {
#[inline]
fn wait_for(&self, target: i64, cursor: &AtomicI64) -> i64 {
loop {
let current = cursor.load(Ordering::Acquire);
if current >= target {
return current;
}
hint::spin_loop();
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct YieldingWait {
spin_tries: u32,
}
impl YieldingWait {
#[inline]
pub const fn new(spin_tries: u32) -> Self {
Self { spin_tries }
}
}
impl Default for YieldingWait {
fn default() -> Self {
Self::new(100)
}
}
impl WaitStrategy for YieldingWait {
fn wait_for(&self, target: i64, cursor: &AtomicI64) -> i64 {
let mut spins = 0u32;
loop {
let current = cursor.load(Ordering::Acquire);
if current >= target {
return current;
}
if spins < self.spin_tries {
hint::spin_loop();
spins += 1;
} else {
std::thread::yield_now();
spins = 0;
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct BackoffWait {
initial_spins: u32,
max_spins: u32,
}
impl BackoffWait {
#[inline]
pub const fn new(initial_spins: u32, max_spins: u32) -> Self {
Self {
initial_spins,
max_spins,
}
}
}
impl Default for BackoffWait {
fn default() -> Self {
Self::new(10, 1000)
}
}
impl WaitStrategy for BackoffWait {
fn wait_for(&self, target: i64, cursor: &AtomicI64) -> i64 {
let max_spins = self.max_spins.max(1);
let mut spins = self.initial_spins.max(1).min(max_spins);
loop {
for _ in 0..spins {
let current = cursor.load(Ordering::Acquire);
if current >= target {
return current;
}
hint::spin_loop();
}
if spins >= max_spins {
std::thread::yield_now();
} else {
spins = spins.saturating_mul(2).min(max_spins);
}
}
}
}
#[cfg(test)]
mod tests {
use super::{BackoffWait, BusySpinWait, SpinLoopHintWait, WaitStrategy, YieldingWait};
use std::sync::{
Arc,
atomic::{AtomicI64, Ordering},
};
use std::thread;
#[test]
fn wait_strategies_return_when_target_is_already_visible() {
let cursor = AtomicI64::new(3);
assert_eq!(BusySpinWait.wait_for(2, &cursor), 3);
assert_eq!(SpinLoopHintWait.wait_for(2, &cursor), 3);
assert_eq!(YieldingWait::new(0).wait_for(2, &cursor), 3);
assert_eq!(BackoffWait::new(0, 8).wait_for(2, &cursor), 3);
}
#[test]
fn backoff_with_zero_initial_spins_observes_progress() {
let cursor = Arc::new(AtomicI64::new(0));
let waiter_cursor = Arc::clone(&cursor);
let waiter = thread::spawn(move || BackoffWait::new(0, 8).wait_for(1, &waiter_cursor));
cursor.store(1, Ordering::Release);
assert_eq!(waiter.join().unwrap(), 1);
}
}