1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
//! Idle functions that may optionally be used by [`crate::RetryService`] and [`crate::PollService`].
//!
//! Utility functions for common idle strategies.
//! These idle strategies will all first check the static [`KEEP_RUNNING`] boolean, and will return `Err(RetryError::Interrupted)` when `KEEP_RUNNING` returns false.
//!
//! For an idle strategy with a good balance between performance and CPU usage, see [`backoff`].
use crate::RetryError;
use std::{
sync::atomic::{AtomicBool, Ordering},
thread,
time::Duration,
};
/// Defaults to true, can be set to false to terminate all idle strategies.
///
/// Here is an example to use the `ctrlc` crate to set this [`AtomicBool`] to false to gracefully terminate any idle loops when a `SIGINT` is received by the process:
/// ```
/// use std::sync::atomic::Ordering;
///
/// ctrlc::set_handler(move || {
/// sod::idle::KEEP_RUNNING.store(false, Ordering::SeqCst);
/// }).expect("Error setting Ctrl-C handler");
/// ```
pub static KEEP_RUNNING: AtomicBool = AtomicBool::new(true);
/// First busy spin for 10 cycles, then yield for 10 cycles, then park for 1us, increasing by powers of two each attempt, maxing out at 1024us (1.024ms).
pub fn backoff<E>(attempts: usize) -> Result<(), RetryError<E>> {
check_keep_running()?;
if attempts < 10 {
} else if attempts < 20 {
thread::yield_now();
} else if attempts < 30 {
let micros = 1 << (attempts - 20);
thread::park_timeout(Duration::from_micros(micros));
} else {
thread::park_timeout(Duration::from_micros(1024));
}
Ok(())
}
/// No-Op
pub fn spin<E>(_: usize) -> Result<(), RetryError<E>> {
check_keep_running()?;
Ok(())
}
/// Calls [`std::thread::yield_now()`]
pub fn yielding<E>(_: usize) -> Result<(), RetryError<E>> {
check_keep_running()?;
thread::yield_now();
Ok(())
}
/// Calls [`std::thread::park_timeout`] with the given timeout
pub fn park<E>(_: usize, timeout: Duration) -> Result<(), RetryError<E>> {
check_keep_running()?;
thread::park_timeout(timeout);
Ok(())
}
/// Calls [`std::thread::park_timeout`] with a 1us timeout
pub fn park_one_micro<E>(attempts: usize) -> Result<(), RetryError<E>> {
check_keep_running()?;
park(attempts, Duration::from_micros(1))
}
/// Calls [`std::thread::park_timeout`] with a 1ms timeout
pub fn park_one_milli<E>(attempts: usize) -> Result<(), RetryError<E>> {
check_keep_running()?;
park(attempts, Duration::from_millis(1))
}
/// Calls [`std::thread::park_timeout`] with a 1s timeout
pub fn park_one_sec<E>(attempts: usize) -> Result<(), RetryError<E>> {
check_keep_running()?;
park(attempts, Duration::from_secs(1))
}
/// Calls [`std::thread::sleep`] with the given duration
pub fn sleep<E>(_: usize, duration: Duration) -> Result<(), RetryError<E>> {
check_keep_running()?;
thread::sleep(duration);
Ok(())
}
/// Calls [`std::thread::sleep`] with a 1us duration
pub fn sleep_one_micro<E>(_: usize) -> Result<(), RetryError<E>> {
check_keep_running()?;
thread::sleep(Duration::from_micros(1));
Ok(())
}
/// Calls [`std::thread::sleep`] with a 1ms duration
pub fn sleep_one_milli<E>(_: usize) -> Result<(), RetryError<E>> {
check_keep_running()?;
thread::sleep(Duration::from_millis(1));
Ok(())
}
/// Calls [`std::thread::sleep`] with a 1s duration
pub fn sleep_one_sec<E>(_: usize) -> Result<(), RetryError<E>> {
check_keep_running()?;
thread::sleep(Duration::from_secs(1));
Ok(())
}
fn check_keep_running<E>() -> Result<(), RetryError<E>> {
if KEEP_RUNNING.load(Ordering::Acquire) {
Ok(())
} else {
Err(RetryError::Interrupted)
}
}