posemesh_utils/
lib.rs

1use std::time::Duration;
2use std::io;
3use futures::{self, Future};
4
5#[cfg(not(target_family = "wasm"))]
6use tokio::time::sleep;
7
8#[cfg(target_family = "wasm")]
9use futures::FutureExt;
10
11#[cfg(target_family = "wasm")]
12pub async fn sleep(duration: Duration) {
13    gloo_timers::future::TimeoutFuture::new(duration.as_millis() as u32).await;
14}
15
16pub const INFINITE_RETRIES: u32 = 0;
17
18/// Retries an async operation with a delay between attempts.
19/// 
20/// # Arguments
21/// * `f` - The async function to retry
22/// * `max_attempts` - Maximum number of attempts, 0 means infinite retries, 1 means only one attempt
23/// * `delay` - Duration to wait between retries
24/// 
25/// # Returns
26/// * `Ok(T)` - The successful result
27/// * `Err(E)` - The error from the last attempt if all attempts failed
28pub async fn retry_with_delay<F, T, E>(mut f: F, max_attempts: u32, delay: Duration) -> Result<T, E>
29where
30    F: FnMut() -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<T, E>> + Send>>,
31    E: std::fmt::Debug,
32{
33    let mut retries = 0;
34    loop {
35        match f().await {
36            Ok(result) => return Ok(result),
37            Err(e) => {
38                retries += 1;
39                if max_attempts != INFINITE_RETRIES && retries >= max_attempts {
40                    return Err(e);
41                }
42                tracing::warn!("Retry {}/{} after {:?}: {:?}", retries, max_attempts, delay, e);
43                sleep(delay).await;
44            }
45        }
46    }
47}
48
49pub async fn retry_with_increasing_delay<F, T, E>(mut f: F, max_retries: u32, initial_delay: Duration) -> Result<T, E>
50where
51    F: FnMut() -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<T, E>> + Send>>,
52    E: std::fmt::Debug,
53{
54    let mut retries = 0;
55    let mut delay = initial_delay;
56    loop {
57        match f().await {
58            Ok(result) => return Ok(result),
59            Err(e) => {
60                retries += 1;
61                if retries >= max_retries {
62                    return Err(e);
63                }
64                tracing::warn!("Retry {}/{} after {:?}: {:?}", retries, max_retries, delay, e);
65                sleep(delay).await;
66                delay *= 2;
67            }
68        }
69    }
70}
71
72#[cfg(target_family = "wasm")]
73pub async fn timeout<F, T>(duration: Duration, future: F) -> Result<T, io::Error>
74where
75    F: Future<Output = T>,
76{
77    if duration.is_zero() {
78        return Ok(future.await);
79    }
80    let timeout_fut = gloo_timers::future::TimeoutFuture::new(duration.as_millis() as u32);
81    futures::select! {
82        result = future.fuse() => Ok(result),
83        _ = timeout_fut.fuse() => Err(io::Error::new(io::ErrorKind::TimedOut, "Operation timed out")),
84    }
85}
86
87#[cfg(not(target_family = "wasm"))]
88pub async fn timeout<F, T>(duration: Duration, future: F) -> Result<T, io::Error>
89where
90    F: Future<Output = T>,
91{
92    if duration.is_zero() {
93        return Ok(future.await);
94    }
95    tokio::time::timeout(duration, future)
96        .await
97        .map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "Operation timed out"))
98}
99
100#[cfg(target_family = "wasm")]
101pub fn now_unix_secs() -> u64 {
102    let millis: f64 = wasm_bindgen_futures::js_sys::Date::now(); // milliseconds since epoch
103    // truncate/floor safely
104    let secs_f64 = (millis / 1000.0).floor();
105    // convert to u64 safely
106    u64::try_from(secs_f64 as i128).unwrap_or(u64::MAX)
107}
108
109#[cfg(not(target_family = "wasm"))]
110pub fn now_unix_secs() -> u64 {
111    use std::time::{SystemTime, UNIX_EPOCH};
112    SystemTime::now()
113        .duration_since(UNIX_EPOCH)
114        .unwrap()
115        .as_secs()
116}