greentic_runner_host/engine/
policy.rs1use 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}