posemesh_utils/
lib.rs

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