use super::timer::*;
use super::*;
use once_cell::sync::Lazy;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
static TIMER_MANAGER: Lazy<Arc<TimerManager>> = Lazy::new(|| {
let tm = Arc::new(TimerManager::new());
check_clock_thread(&tm);
tm
});
pub const DEFAULT_FAST_TIMEOUT_TO_TOKIO_THRESHOLD: Duration = Duration::from_secs(15 * 60);
const DEFAULT_FAST_TIMEOUT_TO_TOKIO_THRESHOLD_SECS: u64 =
DEFAULT_FAST_TIMEOUT_TO_TOKIO_THRESHOLD.as_secs();
const FAST_TIMEOUT_TO_TOKIO_DISABLED: u64 = u64::MAX;
static FAST_TIMEOUT_TO_TOKIO_THRESHOLD_SECS: AtomicU64 =
AtomicU64::new(DEFAULT_FAST_TIMEOUT_TO_TOKIO_THRESHOLD_SECS);
fn check_clock_thread(tm: &Arc<TimerManager>) {
if tm.should_i_start_clock() {
std::thread::Builder::new()
.name("Timer thread".into())
.spawn(|| TIMER_MANAGER.clock_thread())
.unwrap();
}
}
fn should_use_tokio_timeout(duration: Duration) -> bool {
let threshold_secs = FAST_TIMEOUT_TO_TOKIO_THRESHOLD_SECS.load(Ordering::Relaxed);
threshold_secs != FAST_TIMEOUT_TO_TOKIO_DISABLED && duration.as_secs() > threshold_secs
}
pub fn set_fast_timeout_to_tokio_threshold(threshold: Option<Duration>) {
FAST_TIMEOUT_TO_TOKIO_THRESHOLD_SECS.store(
threshold.map_or(FAST_TIMEOUT_TO_TOKIO_DISABLED, |threshold| {
threshold.as_secs()
}),
Ordering::Relaxed,
);
}
pub fn fast_timeout_to_tokio_threshold() -> Option<Duration> {
match FAST_TIMEOUT_TO_TOKIO_THRESHOLD_SECS.load(Ordering::Relaxed) {
FAST_TIMEOUT_TO_TOKIO_DISABLED => None,
threshold_secs => Some(Duration::from_secs(threshold_secs)),
}
}
pub struct FastTimeout(Duration);
impl ToTimeout for FastTimeout {
fn timeout(&self) -> Pin<Box<dyn Future<Output = ()> + Send + Sync>> {
Box::pin(TIMER_MANAGER.register_timer(self.0).poll())
}
fn create(d: Duration) -> Self {
FastTimeout(d)
}
}
enum TimeoutKind {
Fast,
Tokio,
}
pub struct FastOrTokioTimeout {
duration: Duration,
kind: TimeoutKind,
}
impl FastOrTokioTimeout {
fn use_tokio(&self) -> bool {
matches!(self.kind, TimeoutKind::Tokio)
}
}
impl ToTimeout for FastOrTokioTimeout {
fn timeout(&self) -> Pin<Box<dyn Future<Output = ()> + Send + Sync>> {
match self.kind {
TimeoutKind::Fast => FastTimeout::create(self.duration).timeout(),
TimeoutKind::Tokio => TokioTimeout::create(self.duration).timeout(),
}
}
fn create(duration: Duration) -> Self {
Self {
duration,
kind: if should_use_tokio_timeout(duration) {
TimeoutKind::Tokio
} else {
TimeoutKind::Fast
},
}
}
}
pub fn fast_timeout<T>(duration: Duration, future: T) -> Timeout<T, FastOrTokioTimeout>
where
T: Future,
{
let callback = FastOrTokioTimeout::create(duration);
if !callback.use_tokio() {
check_clock_thread(&TIMER_MANAGER);
}
Timeout::new_with_callback(future, callback)
}
pub async fn fast_sleep(duration: Duration) {
check_clock_thread(&TIMER_MANAGER);
TIMER_MANAGER.register_timer(duration).poll().await
}
pub fn pause_for_fork() {
TIMER_MANAGER.pause_for_fork();
}
pub fn unpause() {
TIMER_MANAGER.unpause();
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_timeout() {
let fut = tokio_sleep(Duration::from_secs(1000));
let to = fast_timeout(Duration::from_secs(1), fut);
assert!(to.await.is_err())
}
#[tokio::test]
async fn test_instantly_return() {
let fut = async { 1 };
let to = fast_timeout(Duration::from_secs(1), fut);
assert_eq!(to.await.unwrap(), 1)
}
#[tokio::test]
async fn test_delayed_return() {
let fut = async {
tokio_sleep(Duration::from_secs(1)).await;
1
};
let to = fast_timeout(Duration::from_secs(60), fut);
assert_eq!(to.await.unwrap(), 1)
}
#[tokio::test]
async fn test_sleep() {
let fut = async {
fast_sleep(Duration::from_secs(1)).await;
1
};
let to = fast_timeout(Duration::from_secs(60), fut);
assert_eq!(to.await.unwrap(), 1)
}
#[test]
fn test_default_fast_timeout_to_tokio_threshold() {
assert_eq!(
Some(Duration::from_secs(15 * 60)),
fast_timeout_to_tokio_threshold()
);
}
#[test]
fn test_fast_timeout_uses_fast_below_threshold() {
let callback = FastOrTokioTimeout::create(Duration::from_secs(1));
assert!(!callback.use_tokio());
}
#[test]
fn test_fast_timeout_uses_fast_at_threshold() {
let callback = FastOrTokioTimeout::create(fast_timeout_to_tokio_threshold().unwrap());
assert!(!callback.use_tokio());
}
#[test]
fn test_fast_timeout_uses_tokio_above_threshold() {
let callback = FastOrTokioTimeout::create(
fast_timeout_to_tokio_threshold().unwrap() + Duration::from_secs(1),
);
assert!(callback.use_tokio());
}
}