ralph_workflow/agents/
retry_timer.rs1use std::sync::Arc;
8use std::time::Duration;
9
10pub trait RetryTimerProvider: Send + Sync {
16 fn sleep(&self, duration: Duration);
18}
19
20#[derive(Debug, Clone)]
22struct ProductionRetryTimer;
23
24impl RetryTimerProvider for ProductionRetryTimer {
25 fn sleep(&self, duration: Duration) {
26 std::thread::sleep(duration);
27 }
28}
29
30pub fn production_timer() -> Arc<dyn RetryTimerProvider> {
34 Arc::new(ProductionRetryTimer)
35}
36
37#[cfg(test)]
42#[derive(Debug, Clone)]
43pub struct TestRetryTimer {
44 tracked: Option<Arc<std::sync::atomic::AtomicU64>>,
47}
48
49#[cfg(test)]
50impl Default for TestRetryTimer {
51 fn default() -> Self {
52 Self::new()
53 }
54}
55
56#[cfg(test)]
57impl TestRetryTimer {
58 pub fn new() -> Self {
60 Self { tracked: None }
61 }
62
63 #[cfg(test)]
68 pub fn with_tracking() -> (Self, Arc<std::sync::atomic::AtomicU64>) {
69 let tracked = Arc::new(std::sync::atomic::AtomicU64::new(0));
70 (
71 Self {
72 tracked: Some(tracked.clone()),
73 },
74 tracked,
75 )
76 }
77
78 #[cfg(test)]
80 pub fn total_sleep_ms(&self) -> Option<u64> {
81 self.tracked
82 .as_ref()
83 .map(|t| t.load(std::sync::atomic::Ordering::Relaxed))
84 }
85}
86
87#[cfg(test)]
88impl RetryTimerProvider for TestRetryTimer {
89 fn sleep(&self, duration: Duration) {
90 if let Some(tracked) = &self.tracked {
91 tracked.fetch_add(
92 duration.as_millis() as u64,
93 std::sync::atomic::Ordering::Relaxed,
94 );
95 }
96 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn test_production_retry_timer_sleeps() {
106 let timer = production_timer();
107 let start = std::time::Instant::now();
108 timer.sleep(Duration::from_millis(10));
109 let elapsed = start.elapsed();
110 assert!(elapsed >= Duration::from_millis(10));
111 }
112
113 #[test]
114 fn test_test_retry_timer_immediate() {
115 let timer = TestRetryTimer::new();
116 let start = std::time::Instant::now();
117 timer.sleep(Duration::from_secs(10));
118 let elapsed = start.elapsed();
119 assert!(
120 elapsed < Duration::from_millis(100),
121 "Should return immediately"
122 );
123 }
124
125 #[test]
126 fn test_test_retry_timer_tracking() {
127 let (timer, tracked) = TestRetryTimer::with_tracking();
128
129 timer.sleep(Duration::from_millis(100));
130 timer.sleep(Duration::from_millis(200));
131 timer.sleep(Duration::from_millis(300));
132
133 assert_eq!(timer.total_sleep_ms(), Some(600));
134 assert_eq!(tracked.load(std::sync::atomic::Ordering::Relaxed), 600);
135 }
136
137 #[test]
138 fn test_test_retry_timer_no_tracking() {
139 let timer = TestRetryTimer::new();
140 timer.sleep(Duration::from_millis(100));
141 assert_eq!(timer.total_sleep_ms(), None);
142 }
143}