use parking_lot::RwLock;
use std::sync::Arc;
use std::time::{Duration, Instant};
use thiserror::Error;
use tracing::{debug, info};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BackgroundState {
Active,
Paused,
Pausing,
Resuming,
}
#[derive(Debug, Clone)]
pub struct BackgroundModeConfig {
pub pause_dht_queries: bool,
pub pause_provider_announcements: bool,
pub close_idle_connections: bool,
pub idle_connection_threshold: Duration,
pub keep_minimal_connections: bool,
pub minimal_connection_count: usize,
pub reduce_dht_frequency: bool,
pub background_dht_interval: Duration,
}
impl Default for BackgroundModeConfig {
fn default() -> Self {
Self {
pause_dht_queries: false,
pause_provider_announcements: true,
close_idle_connections: true,
idle_connection_threshold: Duration::from_secs(300), keep_minimal_connections: true,
minimal_connection_count: 3,
reduce_dht_frequency: true,
background_dht_interval: Duration::from_secs(300), }
}
}
impl BackgroundModeConfig {
pub fn mobile() -> Self {
Self {
pause_dht_queries: true, pause_provider_announcements: true,
close_idle_connections: true,
idle_connection_threshold: Duration::from_secs(60), keep_minimal_connections: true,
minimal_connection_count: 2, reduce_dht_frequency: true,
background_dht_interval: Duration::from_secs(600), }
}
pub fn server() -> Self {
Self {
pause_dht_queries: false,
pause_provider_announcements: false,
close_idle_connections: false,
idle_connection_threshold: Duration::from_secs(3600), keep_minimal_connections: false,
minimal_connection_count: 0,
reduce_dht_frequency: false,
background_dht_interval: Duration::from_secs(60),
}
}
pub fn balanced() -> Self {
Self::default()
}
}
pub struct BackgroundModeManager {
config: BackgroundModeConfig,
state: Arc<RwLock<BackgroundState>>,
stats: Arc<RwLock<BackgroundModeStats>>,
last_transition: Arc<RwLock<Option<Instant>>>,
time_in_active: Arc<RwLock<Duration>>,
time_in_paused: Arc<RwLock<Duration>>,
}
#[derive(Debug, Clone, Default)]
pub struct BackgroundModeStats {
pub pause_count: usize,
pub resume_count: usize,
pub total_background_time: Duration,
pub total_foreground_time: Duration,
pub connections_closed_on_pause: usize,
pub dht_queries_skipped: usize,
}
#[derive(Debug, Error)]
pub enum BackgroundModeError {
#[error("Invalid state transition from {from:?} to {to:?}")]
InvalidStateTransition {
from: BackgroundState,
to: BackgroundState,
},
#[error("Operation not allowed in current state: {0:?}")]
OperationNotAllowed(BackgroundState),
}
impl BackgroundModeManager {
pub fn new(config: BackgroundModeConfig) -> Self {
Self {
config,
state: Arc::new(RwLock::new(BackgroundState::Active)),
stats: Arc::new(RwLock::new(BackgroundModeStats::default())),
last_transition: Arc::new(RwLock::new(Some(Instant::now()))),
time_in_active: Arc::new(RwLock::new(Duration::ZERO)),
time_in_paused: Arc::new(RwLock::new(Duration::ZERO)),
}
}
pub fn state(&self) -> BackgroundState {
*self.state.read()
}
pub fn is_paused(&self) -> bool {
matches!(self.state(), BackgroundState::Paused)
}
pub fn is_active(&self) -> bool {
matches!(self.state(), BackgroundState::Active)
}
pub fn pause(&self) -> Result<(), BackgroundModeError> {
let current_state = *self.state.read();
match current_state {
BackgroundState::Active => {
info!("Pausing network for background mode");
*self.state.write() = BackgroundState::Pausing;
self.update_time_tracking(current_state);
self.perform_pause_operations();
*self.state.write() = BackgroundState::Paused;
*self.last_transition.write() = Some(Instant::now());
let mut stats = self.stats.write();
stats.pause_count += 1;
debug!("Network paused successfully");
Ok(())
}
BackgroundState::Paused => {
Ok(())
}
state => Err(BackgroundModeError::InvalidStateTransition {
from: state,
to: BackgroundState::Paused,
}),
}
}
pub fn resume(&self) -> Result<(), BackgroundModeError> {
let current_state = *self.state.read();
match current_state {
BackgroundState::Paused => {
info!("Resuming network from background mode");
*self.state.write() = BackgroundState::Resuming;
self.update_time_tracking(current_state);
self.perform_resume_operations();
*self.state.write() = BackgroundState::Active;
*self.last_transition.write() = Some(Instant::now());
let mut stats = self.stats.write();
stats.resume_count += 1;
debug!("Network resumed successfully");
Ok(())
}
BackgroundState::Active => {
Ok(())
}
state => Err(BackgroundModeError::InvalidStateTransition {
from: state,
to: BackgroundState::Active,
}),
}
}
fn perform_pause_operations(&self) {
debug!(
"Background mode config: pause_dht={}, pause_announcements={}, close_idle={}",
self.config.pause_dht_queries,
self.config.pause_provider_announcements,
self.config.close_idle_connections
);
}
fn perform_resume_operations(&self) {
debug!("Resuming network operations");
}
fn update_time_tracking(&self, old_state: BackgroundState) {
if let Some(last_transition) = *self.last_transition.read() {
let elapsed = last_transition.elapsed();
match old_state {
BackgroundState::Active => {
*self.time_in_active.write() += elapsed;
let mut stats = self.stats.write();
stats.total_foreground_time += elapsed;
}
BackgroundState::Paused => {
*self.time_in_paused.write() += elapsed;
let mut stats = self.stats.write();
stats.total_background_time += elapsed;
}
_ => {}
}
}
}
pub fn should_allow_dht_query(&self) -> bool {
match self.state() {
BackgroundState::Active | BackgroundState::Resuming => true,
BackgroundState::Paused | BackgroundState::Pausing => !self.config.pause_dht_queries,
}
}
pub fn should_allow_provider_announcements(&self) -> bool {
match self.state() {
BackgroundState::Active | BackgroundState::Resuming => true,
BackgroundState::Paused | BackgroundState::Pausing => {
!self.config.pause_provider_announcements
}
}
}
pub fn config(&self) -> &BackgroundModeConfig {
&self.config
}
pub fn stats(&self) -> BackgroundModeStats {
let current_state = *self.state.read();
self.update_time_tracking(current_state);
self.stats.read().clone()
}
pub fn reset_stats(&self) {
*self.stats.write() = BackgroundModeStats::default();
*self.time_in_active.write() = Duration::ZERO;
*self.time_in_paused.write() = Duration::ZERO;
*self.last_transition.write() = Some(Instant::now());
}
pub fn record_dht_query_skipped(&self) {
self.stats.write().dht_queries_skipped += 1;
}
pub fn record_connections_closed(&self, count: usize) {
self.stats.write().connections_closed_on_pause += count;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_background_mode_creation() {
let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
assert_eq!(manager.state(), BackgroundState::Active);
assert!(manager.is_active());
assert!(!manager.is_paused());
}
#[test]
fn test_pause_resume() {
let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
assert!(manager.pause().is_ok());
assert_eq!(manager.state(), BackgroundState::Paused);
assert!(manager.is_paused());
assert!(!manager.is_active());
assert!(manager.resume().is_ok());
assert_eq!(manager.state(), BackgroundState::Active);
assert!(manager.is_active());
assert!(!manager.is_paused());
}
#[test]
fn test_pause_when_already_paused() {
let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
assert!(manager.pause().is_ok());
assert!(manager.pause().is_ok()); assert_eq!(manager.state(), BackgroundState::Paused);
}
#[test]
fn test_resume_when_already_active() {
let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
assert!(manager.resume().is_ok()); assert_eq!(manager.state(), BackgroundState::Active);
}
#[test]
fn test_dht_query_allowed_in_active_state() {
let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
assert!(manager.should_allow_dht_query());
}
#[test]
fn test_dht_query_behavior_in_paused_state() {
let config = BackgroundModeConfig {
pause_dht_queries: true,
..Default::default()
};
let manager = BackgroundModeManager::new(config);
manager
.pause()
.expect("test: pause should succeed from Active state");
assert!(!manager.should_allow_dht_query());
}
#[test]
fn test_dht_query_allowed_when_not_paused_in_background() {
let config = BackgroundModeConfig {
pause_dht_queries: false,
..Default::default()
};
let manager = BackgroundModeManager::new(config);
manager
.pause()
.expect("test: pause should succeed from Active state");
assert!(manager.should_allow_dht_query());
}
#[test]
fn test_provider_announcements_in_active_state() {
let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
assert!(manager.should_allow_provider_announcements());
}
#[test]
fn test_provider_announcements_in_paused_state() {
let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
manager
.pause()
.expect("test: pause should succeed from Active state");
assert!(!manager.should_allow_provider_announcements());
}
#[test]
fn test_statistics_tracking() {
let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
manager
.pause()
.expect("test: first pause should succeed from Active state");
manager
.resume()
.expect("test: resume should succeed from Paused state");
manager
.pause()
.expect("test: second pause should succeed from Active state");
let stats = manager.stats();
assert_eq!(stats.pause_count, 2);
assert_eq!(stats.resume_count, 1);
}
#[test]
fn test_mobile_config() {
let config = BackgroundModeConfig::mobile();
assert!(config.pause_dht_queries);
assert!(config.pause_provider_announcements);
assert!(config.close_idle_connections);
assert_eq!(config.minimal_connection_count, 2);
}
#[test]
fn test_server_config() {
let config = BackgroundModeConfig::server();
assert!(!config.pause_dht_queries);
assert!(!config.pause_provider_announcements);
assert!(!config.close_idle_connections);
}
#[test]
fn test_record_dht_query_skipped() {
let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
manager.record_dht_query_skipped();
manager.record_dht_query_skipped();
let stats = manager.stats();
assert_eq!(stats.dht_queries_skipped, 2);
}
#[test]
fn test_record_connections_closed() {
let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
manager.record_connections_closed(5);
let stats = manager.stats();
assert_eq!(stats.connections_closed_on_pause, 5);
}
#[test]
fn test_reset_stats() {
let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
manager
.pause()
.expect("test: pause should succeed from Active state");
manager
.resume()
.expect("test: resume should succeed from Paused state");
manager.record_dht_query_skipped();
manager.reset_stats();
let stats = manager.stats();
assert_eq!(stats.pause_count, 0);
assert_eq!(stats.resume_count, 0);
assert_eq!(stats.dht_queries_skipped, 0);
}
}