actr_hyper/lifecycle/
connection_supervisor.rs1use super::network_event::{
2 AppLifecycleState, CleanupReason, LONG_BACKGROUND_RECONNECT_THRESHOLD_MS, NetworkEvent,
3 NetworkRecoveryAction, NetworkSnapshot, ReconnectReason,
4};
5
6#[derive(Debug, Clone, PartialEq, Eq, Hash)]
8pub enum ConnectionFact {
9 NetworkSnapshotChanged(NetworkSnapshot),
10 AppEnteredBackground,
11 AppEnteredForeground { background_duration_ms: u64 },
12 CleanupRequested(CleanupReason),
13 ForceReconnectRequested(ReconnectReason),
14}
15
16impl ConnectionFact {
17 pub fn from_network_event(event: &NetworkEvent) -> Self {
18 match event {
19 NetworkEvent::NetworkPathChanged { snapshot } => {
20 Self::NetworkSnapshotChanged(snapshot.clone())
21 }
22 NetworkEvent::AppLifecycleChanged { state } => match state {
23 AppLifecycleState::Background => Self::AppEnteredBackground,
24 AppLifecycleState::Foreground {
25 background_duration_ms,
26 } => Self::AppEnteredForeground {
27 background_duration_ms: *background_duration_ms,
28 },
29 },
30 NetworkEvent::CleanupConnections { reason } => Self::CleanupRequested(*reason),
31 NetworkEvent::ForceReconnect { reason } => Self::ForceReconnectRequested(*reason),
32 }
33 }
34}
35
36#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct ConnectionSupervisor {
39 cleanup_requested: Option<CleanupReason>,
40 force_reconnect_requested: Option<ReconnectReason>,
41 latest_state_action: NetworkRecoveryAction,
42 latest_snapshot_sequence: Option<u64>,
43}
44
45impl Default for ConnectionSupervisor {
46 fn default() -> Self {
47 Self {
48 cleanup_requested: None,
49 force_reconnect_requested: None,
50 latest_state_action: NetworkRecoveryAction::Noop,
51 latest_snapshot_sequence: None,
52 }
53 }
54}
55
56impl ConnectionSupervisor {
57 pub fn new() -> Self {
58 Self::default()
59 }
60
61 pub fn from_events(events: &[NetworkEvent]) -> Self {
62 let mut supervisor = Self::new();
63 for event in events {
64 supervisor.submit_event(event);
65 }
66 supervisor
67 }
68
69 pub fn select_action(events: &[NetworkEvent]) -> NetworkRecoveryAction {
70 Self::from_events(events).reconcile()
71 }
72
73 pub fn submit_event(&mut self, event: &NetworkEvent) {
74 self.submit_fact(ConnectionFact::from_network_event(event));
75 }
76
77 pub fn submit_fact(&mut self, fact: ConnectionFact) {
78 match fact {
79 ConnectionFact::CleanupRequested(reason) => {
80 self.cleanup_requested = Some(reason);
81 }
82 ConnectionFact::ForceReconnectRequested(reason) => {
83 self.force_reconnect_requested = Some(reason);
84 }
85 ConnectionFact::NetworkSnapshotChanged(snapshot) => {
86 let is_latest = self
87 .latest_snapshot_sequence
88 .map(|sequence| snapshot.sequence >= sequence)
89 .unwrap_or(true);
90 if is_latest {
91 self.latest_snapshot_sequence = Some(snapshot.sequence);
92 self.latest_state_action = if snapshot.is_offline() {
93 NetworkRecoveryAction::Offline
94 } else if snapshot.should_restore() {
95 NetworkRecoveryAction::Restore
96 } else {
97 NetworkRecoveryAction::Probe
98 };
99 }
100 }
101 ConnectionFact::AppEnteredForeground {
102 background_duration_ms,
103 } => {
104 if background_duration_ms >= LONG_BACKGROUND_RECONNECT_THRESHOLD_MS {
105 self.force_reconnect_requested = Some(ReconnectReason::LongBackground);
106 } else if self.latest_state_action == NetworkRecoveryAction::Noop {
107 self.latest_state_action = NetworkRecoveryAction::Probe;
108 }
109 }
110 ConnectionFact::AppEnteredBackground => {}
111 }
112 }
113
114 pub fn reconcile(&self) -> NetworkRecoveryAction {
115 if self.cleanup_requested.is_some() {
116 NetworkRecoveryAction::CleanupOnly
117 } else if self.latest_state_action == NetworkRecoveryAction::Offline {
118 NetworkRecoveryAction::Offline
119 } else if self.force_reconnect_requested.is_some() {
120 NetworkRecoveryAction::ForceReconnect
121 } else {
122 self.latest_state_action
123 }
124 }
125}