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
22pub 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(); let secs_f64 = (millis / 1000.0).floor();
109 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"))]
128pub fn get_runtime() -> &'static Runtime {
130 &*GLOBAL_RUNTIME
131}