use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::watch;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NetworkState {
Online,
Offline,
Syncing,
Error,
Reconnecting,
}
#[derive(Debug, Clone)]
pub struct NetworkConfig {
pub connect_timeout_ms: u64,
pub request_timeout_ms: u64,
pub max_retries: u32,
pub initial_backoff_ms: u64,
pub max_backoff_ms: u64,
pub health_check_interval_ms: u64,
}
impl Default for NetworkConfig {
fn default() -> Self {
Self {
connect_timeout_ms: 10_000, request_timeout_ms: 30_000, max_retries: 5, initial_backoff_ms: 1_000, max_backoff_ms: 60_000, health_check_interval_ms: 30_000, }
}
}
impl NetworkConfig {
pub fn mobile() -> Self {
Self {
connect_timeout_ms: 5_000, request_timeout_ms: 15_000, max_retries: 8, initial_backoff_ms: 500, max_backoff_ms: 30_000, health_check_interval_ms: 15_000, }
}
pub fn weak_network() -> Self {
Self {
connect_timeout_ms: 30_000, request_timeout_ms: 60_000, max_retries: 10, initial_backoff_ms: 2_000, max_backoff_ms: 120_000, health_check_interval_ms: 60_000, }
}
}
pub struct NetworkMonitor {
state: Arc<std::sync::RwLock<NetworkState>>,
sender: watch::Sender<NetworkState>,
receiver: watch::Receiver<NetworkState>,
config: NetworkConfig,
retry_count: AtomicU32,
last_success_time: AtomicU64,
}
impl NetworkMonitor {
pub fn new() -> Self {
Self::with_config(NetworkConfig::default())
}
pub fn with_config(config: NetworkConfig) -> Self {
let (sender, receiver) = watch::channel(NetworkState::Offline);
Self {
state: Arc::new(std::sync::RwLock::new(NetworkState::Offline)),
sender,
receiver,
config,
retry_count: AtomicU32::new(0),
last_success_time: AtomicU64::new(0),
}
}
pub fn state(&self) -> NetworkState {
*self.state.read().unwrap()
}
pub fn set_state(&self, state: NetworkState) {
let prev_state = *self.state.read().unwrap();
*self.state.write().unwrap() = state;
let _ = self.sender.send(state);
if prev_state != state {
log::info!(
"[NetworkMonitor] State changed: {:?} -> {:?}",
prev_state,
state
);
}
if state == NetworkState::Online {
self.retry_count.store(0, Ordering::SeqCst);
self.last_success_time.store(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
Ordering::SeqCst,
);
}
}
pub fn is_online(&self) -> bool {
matches!(self.state(), NetworkState::Online | NetworkState::Syncing)
}
pub fn subscribe(&self) -> watch::Receiver<NetworkState> {
self.receiver.clone()
}
pub fn config(&self) -> &NetworkConfig {
&self.config
}
pub fn next_backoff(&self) -> Duration {
let retry = self.retry_count.fetch_add(1, Ordering::SeqCst);
let backoff = self.config.initial_backoff_ms * 2u64.pow(retry.min(10));
let capped = backoff.min(self.config.max_backoff_ms);
let jitter = (capped as f64 * 0.1 * rand_jitter()) as u64;
Duration::from_millis(capped + jitter)
}
pub fn should_retry(&self) -> bool {
self.retry_count.load(Ordering::SeqCst) < self.config.max_retries
}
pub fn reset_retries(&self) {
self.retry_count.store(0, Ordering::SeqCst);
}
pub fn connect_timeout(&self) -> Duration {
Duration::from_millis(self.config.connect_timeout_ms)
}
pub fn request_timeout(&self) -> Duration {
Duration::from_millis(self.config.request_timeout_ms)
}
pub fn health_check_interval(&self) -> Duration {
Duration::from_millis(self.config.health_check_interval_ms)
}
pub fn seconds_since_last_success(&self) -> u64 {
let last = self.last_success_time.load(Ordering::SeqCst);
if last == 0 {
return u64::MAX;
}
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
now.saturating_sub(last)
}
}
impl Default for NetworkMonitor {
fn default() -> Self {
Self::new()
}
}
fn rand_jitter() -> f64 {
use std::collections::hash_map::RandomState;
use std::hash::{BuildHasher, Hasher};
let mut hasher = RandomState::new().build_hasher();
hasher.write_u64(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64,
);
(hasher.finish() % 1000) as f64 / 1000.0
}