use super::partition::{PartitionEvent, PartitionHandler};
use std::sync::{Arc, RwLock};
use std::time::Instant;
use tracing::{info, warn};
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum AutonomousState {
#[default]
Normal,
Autonomous {
entered_at: Instant,
detection_attempts: u32,
},
}
#[derive(Debug)]
pub struct AutonomousOperationHandler {
state: Arc<RwLock<AutonomousState>>,
}
impl AutonomousOperationHandler {
pub fn new() -> Self {
Self {
state: Arc::new(RwLock::new(AutonomousState::default())),
}
}
pub fn is_autonomous(&self) -> bool {
matches!(
*self.state.read().unwrap_or_else(|e| e.into_inner()),
AutonomousState::Autonomous { .. }
)
}
pub fn get_state(&self) -> AutonomousState {
self.state.read().unwrap_or_else(|e| e.into_inner()).clone()
}
fn enter_autonomous_mode(&self, detection_attempts: u32) {
let mut state = self.state.write().unwrap_or_else(|e| e.into_inner());
if matches!(*state, AutonomousState::Autonomous { .. }) {
warn!("Already in autonomous mode, ignoring duplicate partition detection");
return;
}
*state = AutonomousState::Autonomous {
entered_at: Instant::now(),
detection_attempts,
};
info!(
"Entered autonomous operation mode after {} detection attempts",
detection_attempts
);
}
fn exit_autonomous_mode(&self, autonomous_duration: std::time::Duration) {
let mut state = self.state.write().unwrap_or_else(|e| e.into_inner());
if matches!(*state, AutonomousState::Normal) {
warn!("Already in normal mode, ignoring duplicate partition healing");
return;
}
*state = AutonomousState::Normal;
info!(
"Exited autonomous operation mode after {:?} duration",
autonomous_duration
);
}
}
impl Default for AutonomousOperationHandler {
fn default() -> Self {
Self::new()
}
}
impl PartitionHandler for AutonomousOperationHandler {
fn on_partition_detected(&self, event: &PartitionEvent) {
if let PartitionEvent::PartitionDetected {
attempts,
detection_duration,
} = event
{
info!(
"Network partition detected after {} attempts over {:?}",
attempts, detection_duration
);
self.enter_autonomous_mode(*attempts);
}
}
fn on_partition_healed(&self, event: &PartitionEvent) {
if let PartitionEvent::PartitionHealed {
visible_beacons,
partition_duration,
} = event
{
info!(
"Network partition healed, {} higher-level beacons visible, partition lasted {:?}",
visible_beacons, partition_duration
);
self.exit_autonomous_mode(*partition_duration);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn test_autonomous_handler_creation() {
let handler = AutonomousOperationHandler::new();
assert!(!handler.is_autonomous());
assert_eq!(handler.get_state(), AutonomousState::Normal);
}
#[test]
fn test_enter_autonomous_mode() {
let handler = AutonomousOperationHandler::new();
let event = PartitionEvent::PartitionDetected {
attempts: 3,
detection_duration: Duration::from_secs(6),
};
handler.on_partition_detected(&event);
assert!(handler.is_autonomous());
match handler.get_state() {
AutonomousState::Autonomous {
detection_attempts, ..
} => {
assert_eq!(detection_attempts, 3);
}
_ => panic!("Expected Autonomous state"),
}
}
#[test]
fn test_exit_autonomous_mode() {
let handler = AutonomousOperationHandler::new();
let partition_event = PartitionEvent::PartitionDetected {
attempts: 3,
detection_duration: Duration::from_secs(6),
};
handler.on_partition_detected(&partition_event);
assert!(handler.is_autonomous());
let healed_event = PartitionEvent::PartitionHealed {
visible_beacons: 2,
partition_duration: Duration::from_secs(120),
};
handler.on_partition_healed(&healed_event);
assert!(!handler.is_autonomous());
assert_eq!(handler.get_state(), AutonomousState::Normal);
}
#[test]
fn test_duplicate_partition_detection() {
let handler = AutonomousOperationHandler::new();
let event = PartitionEvent::PartitionDetected {
attempts: 3,
detection_duration: Duration::from_secs(6),
};
handler.on_partition_detected(&event);
assert!(handler.is_autonomous());
let state1 = handler.get_state();
handler.on_partition_detected(&event);
let state2 = handler.get_state();
assert_eq!(state1, state2);
}
#[test]
fn test_duplicate_partition_healing() {
let handler = AutonomousOperationHandler::new();
let healed_event = PartitionEvent::PartitionHealed {
visible_beacons: 2,
partition_duration: Duration::from_secs(120),
};
handler.on_partition_healed(&healed_event);
assert!(!handler.is_autonomous());
assert_eq!(handler.get_state(), AutonomousState::Normal);
}
#[test]
fn test_autonomous_state_transition() {
let handler = AutonomousOperationHandler::new();
let partition_event = PartitionEvent::PartitionDetected {
attempts: 5,
detection_duration: Duration::from_secs(10),
};
handler.on_partition_detected(&partition_event);
assert!(handler.is_autonomous());
let healed_event = PartitionEvent::PartitionHealed {
visible_beacons: 1,
partition_duration: Duration::from_secs(300),
};
handler.on_partition_healed(&healed_event);
assert!(!handler.is_autonomous());
}
}