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