cranpose_services/
network_status.rs1use std::sync::{Arc, OnceLock};
9
10use crate::registry::{RecoveryGate, ServiceRegistry};
11
12#[derive(Clone, Copy, PartialEq, Eq, Debug)]
14pub struct NetworkStatus {
15 pub online: bool,
17 pub metered: bool,
19}
20
21impl Default for NetworkStatus {
22 fn default() -> Self {
23 Self {
24 online: true,
25 metered: false,
26 }
27 }
28}
29
30pub trait NetworkMonitor: Send + Sync {
32 fn status(&self) -> NetworkStatus;
33 fn is_alive(&self) -> bool;
34 fn reconnect(&self);
35}
36
37pub type NetworkMonitorRef = Arc<dyn NetworkMonitor>;
38
39struct DefaultNetworkMonitor;
40
41impl NetworkMonitor for DefaultNetworkMonitor {
42 fn status(&self) -> NetworkStatus {
43 NetworkStatus::default()
44 }
45
46 fn is_alive(&self) -> bool {
47 true
48 }
49
50 fn reconnect(&self) {}
51}
52
53static PLATFORM_NETWORK_MONITOR: ServiceRegistry<dyn NetworkMonitor> = ServiceRegistry::new();
54static DEFAULT_NETWORK_MONITOR: OnceLock<NetworkMonitorRef> = OnceLock::new();
55static NETWORK_MONITOR_HANDLE: OnceLock<NetworkMonitorRef> = OnceLock::new();
56static NETWORK_RECOVERY: RecoveryGate = RecoveryGate::new();
57
58struct PlatformNetworkMonitor;
59
60fn registered_network_monitor() -> NetworkMonitorRef {
61 PLATFORM_NETWORK_MONITOR
62 .get_or_warn("network monitor")
63 .unwrap_or_else(|| {
64 DEFAULT_NETWORK_MONITOR
65 .get_or_init(|| Arc::new(DefaultNetworkMonitor))
66 .clone()
67 })
68}
69
70fn active_network_monitor() -> NetworkMonitorRef {
71 let monitor = registered_network_monitor();
72 if monitor.is_alive() {
73 NETWORK_RECOVERY.succeeded();
74 } else if NETWORK_RECOVERY.try_start() {
75 monitor.reconnect();
76 }
77 monitor
78}
79
80impl NetworkMonitor for PlatformNetworkMonitor {
81 fn status(&self) -> NetworkStatus {
82 active_network_monitor().status()
83 }
84
85 fn is_alive(&self) -> bool {
86 registered_network_monitor().is_alive()
87 }
88
89 fn reconnect(&self) {
90 registered_network_monitor().reconnect();
91 }
92}
93
94pub fn set_platform_network_monitor(monitor: NetworkMonitorRef) {
96 PLATFORM_NETWORK_MONITOR.set(monitor);
97 NETWORK_RECOVERY.succeeded();
98}
99
100pub fn clear_platform_network_monitor() {
102 PLATFORM_NETWORK_MONITOR.clear();
103}
104
105pub fn network_monitor() -> NetworkMonitorRef {
108 NETWORK_MONITOR_HANDLE
109 .get_or_init(|| Arc::new(PlatformNetworkMonitor))
110 .clone()
111}
112
113pub fn network_status() -> NetworkStatus {
115 let monitor = network_monitor();
116 if monitor.is_alive() {
117 NETWORK_RECOVERY.succeeded();
118 } else if NETWORK_RECOVERY.try_start() {
119 monitor.reconnect();
120 }
121 monitor.status()
122}
123
124#[cfg(test)]
125mod tests {
126 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
127
128 use super::*;
129
130 #[test]
131 fn default_is_online_unmetered_and_overridable() {
132 let _guard = crate::registry::test_service_guard();
133 clear_platform_network_monitor();
134 assert_eq!(
135 network_status(),
136 NetworkStatus {
137 online: true,
138 metered: false
139 }
140 );
141 struct Metered;
142 impl NetworkMonitor for Metered {
143 fn status(&self) -> NetworkStatus {
144 NetworkStatus {
145 online: true,
146 metered: true,
147 }
148 }
149
150 fn is_alive(&self) -> bool {
151 true
152 }
153
154 fn reconnect(&self) {}
155 }
156 set_platform_network_monitor(Arc::new(Metered));
157 assert!(network_status().metered);
158 clear_platform_network_monitor();
159 }
160
161 #[test]
162 fn dead_monitor_reconnects_before_status_is_read() {
163 let _guard = crate::registry::test_service_guard();
164 struct Reconnecting {
165 alive: AtomicBool,
166 reconnects: AtomicUsize,
167 }
168 impl NetworkMonitor for Reconnecting {
169 fn status(&self) -> NetworkStatus {
170 NetworkStatus {
171 online: self.alive.load(Ordering::Acquire),
172 metered: false,
173 }
174 }
175 fn is_alive(&self) -> bool {
176 self.alive.load(Ordering::Acquire)
177 }
178 fn reconnect(&self) {
179 self.reconnects.fetch_add(1, Ordering::AcqRel);
180 self.alive.store(true, Ordering::Release);
181 }
182 }
183 clear_platform_network_monitor();
184 let monitor = Arc::new(Reconnecting {
185 alive: AtomicBool::new(false),
186 reconnects: AtomicUsize::new(0),
187 });
188 set_platform_network_monitor(monitor.clone());
189 assert!(network_status().online);
190 assert_eq!(monitor.reconnects.load(Ordering::Acquire), 1);
191 clear_platform_network_monitor();
192 }
193}