use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;
#[cfg(not(target_family = "wasm"))]
pub(crate) use tokio::time::Instant;
#[cfg(target_family = "wasm")]
pub(crate) use web_time::Instant;
#[cfg(feature = "retry")]
pub(crate) fn since_epoch() -> Duration {
#[cfg(not(target_family = "wasm"))]
use std::time::{SystemTime, UNIX_EPOCH};
#[cfg(target_family = "wasm")]
use web_time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
}
pub(crate) struct Sleep {
#[cfg(not(target_family = "wasm"))]
inner: Pin<Box<tokio::time::Sleep>>,
#[cfg(target_family = "wasm")]
deadline: Instant,
#[cfg(target_family = "wasm")]
timeout: Option<gloo_timers::future::TimeoutFuture>,
}
impl Sleep {
pub(crate) fn until(deadline: Instant) -> Self {
#[cfg(not(target_family = "wasm"))]
{
Self {
inner: Box::pin(tokio::time::sleep_until(deadline)),
}
}
#[cfg(target_family = "wasm")]
{
Self {
deadline,
timeout: None,
}
}
}
pub(crate) fn reset(&mut self, deadline: Instant) {
#[cfg(not(target_family = "wasm"))]
self.inner.as_mut().reset(deadline);
#[cfg(target_family = "wasm")]
{
self.deadline = deadline;
self.timeout = None;
}
}
}
impl Future for Sleep {
type Output = ();
#[cfg(not(target_family = "wasm"))]
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
self.inner.as_mut().poll(cx)
}
#[cfg(target_family = "wasm")]
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
let this = &mut *self;
loop {
let left = this.deadline.saturating_duration_since(Instant::now());
if left.is_zero() {
this.timeout = None;
return Poll::Ready(());
}
let timeout = this.timeout.get_or_insert_with(|| {
gloo_timers::future::TimeoutFuture::new(timeout_millis(left))
});
match Pin::new(timeout).poll(cx) {
Poll::Ready(()) => this.timeout = None,
Poll::Pending => return Poll::Pending,
}
}
}
}
#[cfg_attr(not(target_family = "wasm"), allow(dead_code))]
fn timeout_millis(delay: Duration) -> u32 {
const MAX: u128 = i32::MAX as u128;
delay.as_nanos().div_ceil(1_000_000).min(MAX) as u32
}
impl std::fmt::Debug for Sleep {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Sleep").finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn timeouts_round_up_and_stay_in_range() {
assert_eq!(timeout_millis(Duration::ZERO), 0);
assert_eq!(timeout_millis(Duration::from_nanos(1)), 1);
assert_eq!(timeout_millis(Duration::from_micros(1500)), 2);
assert_eq!(timeout_millis(Duration::from_secs(2)), 2000);
assert_eq!(
timeout_millis(Duration::from_secs(100 * 86_400)),
i32::MAX as u32
);
}
}