piper_driver/
heartbeat.rs1use std::sync::OnceLock;
11use std::sync::atomic::{AtomicU64, Ordering};
12use std::time::{Duration, Instant};
13
14static APP_START: OnceLock<Instant> = OnceLock::new();
17
18fn get_monotonic_micros() -> u64 {
25 let start = APP_START.get_or_init(Instant::now);
26 start.elapsed().as_micros() as u64
27}
28
29pub struct ConnectionMonitor {
33 last_feedback: AtomicU64,
34 timeout: Duration,
35}
36
37impl ConnectionMonitor {
38 pub fn new(timeout: Duration) -> Self {
50 let now = get_monotonic_micros();
52 Self {
53 last_feedback: AtomicU64::new(now),
54 timeout,
55 }
56 }
57
58 pub fn check_connection(&self) -> bool {
62 let last_us = self.last_feedback.load(Ordering::Relaxed);
63 let now_us = get_monotonic_micros();
64
65 let elapsed_us = now_us.saturating_sub(last_us);
67 let elapsed = Duration::from_micros(elapsed_us);
68
69 elapsed < self.timeout
70 }
71
72 pub fn register_feedback(&self) {
76 let now = get_monotonic_micros();
77 self.last_feedback.store(now, Ordering::Relaxed);
78 }
79
80 pub fn time_since_last_feedback(&self) -> Duration {
82 let last_us = self.last_feedback.load(Ordering::Relaxed);
83 let now_us = get_monotonic_micros();
84 Duration::from_micros(now_us.saturating_sub(last_us))
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91 use std::thread;
92
93 #[test]
94 fn test_monotonic_time_always_increases() {
95 let t1 = get_monotonic_micros();
96 thread::sleep(Duration::from_millis(10));
97 let t2 = get_monotonic_micros();
98
99 assert!(t2 > t1, "Monotonic time should always increase");
100 }
101
102 #[test]
103 fn test_connection_monitor_initially_alive() {
104 let monitor = ConnectionMonitor::new(Duration::from_secs(1));
105 assert!(
106 monitor.check_connection(),
107 "Connection should be alive initially"
108 );
109 }
110
111 #[test]
112 fn test_connection_monitor_timeout_after_delay() {
113 let monitor = ConnectionMonitor::new(Duration::from_millis(50));
114
115 assert!(monitor.check_connection());
117
118 thread::sleep(Duration::from_millis(100));
120
121 assert!(
123 !monitor.check_connection(),
124 "Connection should timeout after delay"
125 );
126 }
127
128 #[test]
129 fn test_connection_monitor_feedback_resets_timer() {
130 let monitor = ConnectionMonitor::new(Duration::from_millis(200));
132
133 thread::sleep(Duration::from_millis(50));
135
136 monitor.register_feedback();
138
139 thread::sleep(Duration::from_millis(60));
141
142 assert!(
144 monitor.check_connection(),
145 "Feedback should reset timeout timer"
146 );
147 }
148
149 #[test]
150 fn test_time_since_last_feedback() {
151 let monitor = ConnectionMonitor::new(Duration::from_secs(1));
152
153 thread::sleep(Duration::from_millis(10));
154 let elapsed = monitor.time_since_last_feedback();
155
156 assert!(elapsed >= Duration::from_millis(10));
157 assert!(elapsed < Duration::from_millis(200)); }
160
161 #[test]
162 fn test_monotonic_micros_no_panic_on_system_clock_change() {
163 let mut last = get_monotonic_micros();
169
170 for _ in 0..100 {
171 thread::sleep(Duration::from_micros(100));
172 let current = get_monotonic_micros();
173 assert!(
174 current >= last,
175 "Monotonic time should never decrease (current={}, last={})",
176 current,
177 last
178 );
179 last = current;
180 }
181 }
182}