use std::time::Duration;
tokio::task_local! {
static INVOCATION_DEADLINE_MS: u64;
}
fn now_millis() -> u64 {
let now = time::OffsetDateTime::now_utc();
now.unix_timestamp() as u64 * 1_000 + now.millisecond() as u64
}
pub async fn scope_deadline<F>(deadline_ms: u64, fut: F) -> F::Output
where
F: Future,
{
INVOCATION_DEADLINE_MS.scope(deadline_ms, fut).await
}
pub fn current_deadline_ms() -> Option<u64> {
INVOCATION_DEADLINE_MS.try_with(|&ms| ms).ok()
}
pub fn time_remaining() -> Option<Duration> {
INVOCATION_DEADLINE_MS
.try_with(|deadline_ms| {
let now = now_millis();
deadline_ms.saturating_sub(now)
})
.ok()
.map(Duration::from_millis)
}
pub fn total_time_limit() -> Option<Duration> {
INVOCATION_DEADLINE_MS
.try_with(|deadline_ms| {
Duration::from_millis(*deadline_ms).saturating_sub(Duration::from_millis(now_millis()))
})
.ok()
}
pub fn is_duration_available(duration: Duration) -> bool {
time_remaining()
.map(|remaining| remaining > Duration::ZERO && remaining >= duration)
.unwrap_or(false)
}
#[cfg(test)]
mod tests {
use tokio::time::sleep;
use super::*;
#[tokio::test]
async fn scope_provides_remaining_time() {
let now = now_millis();
let fut = async move {
let remaining = time_remaining().expect("deadline present");
println!("Remaining time: {:?}", remaining.as_nanos());
for i in 0..5 {
sleep(Duration::from_millis(100)).await;
println!("Slept {}00 ms", i + 1);
let remaining = time_remaining().expect("deadline present");
println!("Remaining time: {:?}", remaining.as_nanos());
}
assert!(remaining >= Duration::from_millis(950));
};
scope_deadline(now + 1_000, fut).await;
}
#[tokio::test]
async fn test_total_time_limit() {
let fut = async move {
let total_time = total_time_limit().expect("deadline present");
assert_eq!(total_time, Duration::from_millis(2_000));
};
scope_deadline(now_millis() + 2_000, fut).await;
}
#[test]
fn missing_deadline_returns_none() {
assert!(time_remaining().is_none());
}
#[tokio::test]
async fn test_is_runtime_time_available() {
let fut = async move {
assert!(is_duration_available(Duration::from_millis(500)));
assert!(!is_duration_available(Duration::from_millis(1_500)));
};
scope_deadline(now_millis() + 1_000, fut).await;
}
}