1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
use std::future::Future;
use tokio::time::{sleep, Duration};
use crate::tracing::CustomLogger;
#[cfg(test)]
#[path = "run_until_test.rs"]
mod run_until_test;
/// Runs an asynchronous function until a condition is met or max attempts are reached.
///
/// # Arguments
/// - `interval`: Time between each attempt (in milliseconds).
/// - `max_attempts`: Maximum number of attempts.
/// - `executable`: An asynchronous function to execute, which returns a future type `T` value.
/// - `condition`: A closure that takes a value of type `T` and returns `true` if the condition is
/// met.
/// - `logger`: Optional trace logger.
///
/// # Returns
/// - `Option<T>`: Returns `Some(value)` if the condition is met within the attempts, otherwise
/// `None`.
pub async fn run_until<T, F, C, Fut>(
interval: u64,
max_attempts: usize,
mut executable: F,
condition: C,
logger: Option<CustomLogger>,
) -> Option<T>
where
T: Send + std::fmt::Debug + 'static,
F: FnMut() -> Fut,
Fut: Future<Output = T>,
C: Fn(&T) -> bool + Send + Sync,
{
for attempt in 1..=max_attempts {
let result = executable().await;
// Log attempt message.
if let Some(config) = &logger {
let attempt_message = format!("Attempt {attempt}/{max_attempts}, Value {result:?}");
config.log_message(&attempt_message);
}
// Check if the condition is met.
if condition(&result) {
if let Some(config) = &logger {
let success_message = format!("Condition met on attempt {attempt}/{max_attempts}");
config.log_message(&success_message);
}
return Some(result);
}
// Wait for the interval before the next attempt.
sleep(Duration::from_millis(interval)).await;
}
if let Some(config) = &logger {
let failure_message =
format!("Condition not met after the maximum number of {max_attempts} attempts.");
config.log_message(&failure_message);
}
None
}