greentic_runner_host/engine/
policy.rs

1use super::error::{GResult, RunnerError};
2use rand::{Rng, rng};
3use std::time::Duration;
4use tokio::time::sleep;
5
6#[derive(Clone, Debug)]
7pub struct RetryPolicy {
8    pub max_attempts: u32,
9    pub initial_backoff: Duration,
10    pub max_backoff: Duration,
11}
12
13impl Default for RetryPolicy {
14    fn default() -> Self {
15        Self {
16            max_attempts: 5,
17            initial_backoff: Duration::from_millis(100),
18            max_backoff: Duration::from_secs(5),
19        }
20    }
21}
22
23#[derive(Clone, Debug)]
24pub struct Policy {
25    pub retry: RetryPolicy,
26    pub max_egress_adapters: usize,
27    pub max_payload_bytes: usize,
28}
29
30impl Default for Policy {
31    fn default() -> Self {
32        Self {
33            retry: RetryPolicy::default(),
34            max_egress_adapters: 32,
35            max_payload_bytes: 512 * 1024,
36        }
37    }
38}
39
40pub async fn retry_with_jitter<F, Fut, T>(policy: &RetryPolicy, mut op: F) -> GResult<T>
41where
42    F: FnMut() -> Fut + Send,
43    Fut: std::future::Future<Output = GResult<T>> + Send,
44    T: Send,
45{
46    let mut attempt = 0u32;
47    loop {
48        match op().await {
49            Ok(value) => return Ok(value),
50            Err(err) => {
51                attempt += 1;
52                if attempt >= policy.max_attempts {
53                    return Err(err);
54                }
55                let backoff = backoff_with_jitter(policy, attempt);
56                sleep(backoff).await;
57            }
58        }
59    }
60}
61
62fn backoff_with_jitter(policy: &RetryPolicy, attempt: u32) -> Duration {
63    let capped_attempt = attempt.min(10);
64    let initial_ms = policy.initial_backoff.as_millis().max(1) as u64;
65    let multiplier = 1u64 << capped_attempt;
66    let mut base_ms = initial_ms.saturating_mul(multiplier);
67    let max_ms = policy.max_backoff.as_millis().max(1) as u64;
68    if base_ms > max_ms {
69        base_ms = max_ms;
70    }
71    let base = Duration::from_millis(base_ms);
72    let mut rng = rng();
73    let jitter_cap = base.as_millis().max(1) as u64;
74    let jitter_ms = rng.random_range(0..=jitter_cap);
75    base + Duration::from_millis(jitter_ms)
76}
77
78pub fn policy_violation(reason: impl Into<String>) -> RunnerError {
79    RunnerError::Policy {
80        reason: reason.into(),
81    }
82}