Skip to main content

noxu_rep/
node_state.rs

1//! Node state machine for replication nodes.
2//!
3//! encapsulates the
4//! current replicator state and the ability to validate state transitions.
5
6use noxu_sync::RwLock;
7use std::fmt;
8use std::time::{Duration, Instant};
9
10/// The possible states of a replication node.
11///
12///
13///
14/// These states determine which operations are permitted on the node. For
15/// example, only the Master node can execute write operations.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub enum NodeState {
18    /// Node is not associated with the group. Its handle has been closed.
19    /// No operations can be performed on the environment when it is in this
20    /// state.
21    Detached,
22
23    /// Node is not currently in contact with the master, but is actively
24    /// trying to establish contact with, or decide upon, a master. While in
25    /// this state the node is restricted to performing just read operations
26    /// on its environment. In a functioning group, this state is transitory.
27    Unknown,
28
29    /// Node is the unique master of the group and can both read and write
30    /// to its environment. When the node transitions to this state, the
31    /// application running on the node must make provisions to start
32    /// processing application level write requests in addition to read
33    /// requests.
34    Master,
35
36    /// Node is a replica that is being updated by the master. It is
37    /// restricted to reading its environment. When the node transitions to
38    /// this state, the application running on the node must arrange for all
39    /// write requests to be routed to the master.
40    Replica,
41
42    /// Node is shutting down. No operations can be performed.
43    Shutdown,
44}
45
46impl NodeState {
47    /// Whether this state accepts write operations.
48    pub fn is_writable(&self) -> bool {
49        matches!(self, NodeState::Master)
50    }
51
52    /// Whether this state accepts read operations.
53    pub fn is_readable(&self) -> bool {
54        matches!(self, NodeState::Master | NodeState::Replica)
55    }
56
57    /// Whether this state is active (participating in group).
58    pub fn is_active(&self) -> bool {
59        matches!(
60            self,
61            NodeState::Master | NodeState::Replica | NodeState::Unknown
62        )
63    }
64
65    /// Whether this is the Master state.
66    pub fn is_master(&self) -> bool {
67        matches!(self, NodeState::Master)
68    }
69
70    /// Whether this is the Replica state.
71    pub fn is_replica(&self) -> bool {
72        matches!(self, NodeState::Replica)
73    }
74
75    /// Whether this is the Detached state.
76    pub fn is_detached(&self) -> bool {
77        matches!(self, NodeState::Detached)
78    }
79
80    /// Whether this is the Unknown state.
81    pub fn is_unknown(&self) -> bool {
82        matches!(self, NodeState::Unknown)
83    }
84
85    /// Returns the set of states that this state can transition to.
86    fn valid_transitions(&self) -> &'static [NodeState] {
87        match self {
88            NodeState::Detached => &[NodeState::Unknown, NodeState::Shutdown],
89            NodeState::Unknown => {
90                &[NodeState::Master, NodeState::Replica, NodeState::Shutdown]
91            }
92            NodeState::Master => {
93                &[NodeState::Unknown, NodeState::Replica, NodeState::Shutdown]
94            }
95            NodeState::Replica => {
96                &[NodeState::Unknown, NodeState::Master, NodeState::Shutdown]
97            }
98            NodeState::Shutdown => &[],
99        }
100    }
101
102    /// Check if a transition to the given state is valid from this state.
103    pub fn can_transition_to(&self, new_state: NodeState) -> bool {
104        self.valid_transitions().contains(&new_state)
105    }
106}
107
108impl fmt::Display for NodeState {
109    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110        match self {
111            NodeState::Detached => write!(f, "DETACHED"),
112            NodeState::Unknown => write!(f, "UNKNOWN"),
113            NodeState::Master => write!(f, "MASTER"),
114            NodeState::Replica => write!(f, "REPLICA"),
115            NodeState::Shutdown => write!(f, "SHUTDOWN"),
116        }
117    }
118}
119
120/// Manages state transitions for a replication node.
121///
122/// encapsulates the
123/// current replicator state, validates transitions, and tracks timing.
124///
125/// All methods are thread-safe. The state machine enforces that only valid
126/// transitions are permitted according to the replication protocol.
127pub struct NodeStateMachine {
128    /// Current state of the node.
129    state: RwLock<NodeState>,
130    /// Time at which the last state change occurred.
131    state_change_time: RwLock<Instant>,
132    /// Total number of state transitions that have occurred.
133    transition_count: RwLock<u64>,
134}
135
136impl NodeStateMachine {
137    /// Create a new state machine starting in the Detached state.
138    pub fn new() -> Self {
139        Self {
140            state: RwLock::new(NodeState::Detached),
141            state_change_time: RwLock::new(Instant::now()),
142            transition_count: RwLock::new(0),
143        }
144    }
145
146    /// Get the current state.
147    pub fn get_state(&self) -> NodeState {
148        *self.state.read()
149    }
150
151    /// Get the time at which the last state change occurred.
152    pub fn get_state_change_time(&self) -> Instant {
153        *self.state_change_time.read()
154    }
155
156    /// Get the total number of state transitions.
157    pub fn get_transition_count(&self) -> u64 {
158        *self.transition_count.read()
159    }
160
161    /// Transition to a new state, validating the transition is legal.
162    ///
163    /// Returns the previous state on success.
164    ///
165    /// # Errors
166    ///
167    /// Returns `RepError::InvalidStateTransition` if the transition from the
168    /// current state to the new state is not permitted.
169    pub fn transition_to(
170        &self,
171        new_state: NodeState,
172    ) -> crate::error::Result<NodeState> {
173        let mut state = self.state.write();
174        let old_state = *state;
175
176        if !old_state.can_transition_to(new_state) {
177            return Err(crate::error::RepError::InvalidStateTransition(
178                format!("{} -> {}", old_state, new_state),
179            ));
180        }
181
182        *state = new_state;
183        *self.state_change_time.write() = Instant::now();
184        *self.transition_count.write() += 1;
185
186        log::info!("Node state change from {} to {}", old_state, new_state);
187
188        Ok(old_state)
189    }
190
191    /// Check if a transition to the given state is valid from the current state.
192    pub fn can_transition_to(&self, new_state: NodeState) -> bool {
193        self.state.read().can_transition_to(new_state)
194    }
195
196    /// Get the time spent in the current state.
197    pub fn time_in_state(&self) -> Duration {
198        self.state_change_time.read().elapsed()
199    }
200}
201
202impl Default for NodeStateMachine {
203    fn default() -> Self {
204        Self::new()
205    }
206}
207
208#[cfg(test)]
209mod tests {
210    use super::*;
211
212    // --- NodeState enum tests ---
213
214    #[test]
215    fn test_node_state_is_writable() {
216        assert!(NodeState::Master.is_writable());
217        assert!(!NodeState::Replica.is_writable());
218        assert!(!NodeState::Unknown.is_writable());
219        assert!(!NodeState::Detached.is_writable());
220        assert!(!NodeState::Shutdown.is_writable());
221    }
222
223    #[test]
224    fn test_node_state_is_readable() {
225        assert!(NodeState::Master.is_readable());
226        assert!(NodeState::Replica.is_readable());
227        assert!(!NodeState::Unknown.is_readable());
228        assert!(!NodeState::Detached.is_readable());
229        assert!(!NodeState::Shutdown.is_readable());
230    }
231
232    #[test]
233    fn test_node_state_is_active() {
234        assert!(NodeState::Master.is_active());
235        assert!(NodeState::Replica.is_active());
236        assert!(NodeState::Unknown.is_active());
237        assert!(!NodeState::Detached.is_active());
238        assert!(!NodeState::Shutdown.is_active());
239    }
240
241    #[test]
242    fn test_node_state_convenience_methods() {
243        assert!(NodeState::Master.is_master());
244        assert!(!NodeState::Replica.is_master());
245        assert!(NodeState::Replica.is_replica());
246        assert!(!NodeState::Master.is_replica());
247        assert!(NodeState::Detached.is_detached());
248        assert!(NodeState::Unknown.is_unknown());
249    }
250
251    #[test]
252    fn test_node_state_display() {
253        assert_eq!(format!("{}", NodeState::Detached), "DETACHED");
254        assert_eq!(format!("{}", NodeState::Unknown), "UNKNOWN");
255        assert_eq!(format!("{}", NodeState::Master), "MASTER");
256        assert_eq!(format!("{}", NodeState::Replica), "REPLICA");
257        assert_eq!(format!("{}", NodeState::Shutdown), "SHUTDOWN");
258    }
259
260    // --- Valid transition tests ---
261
262    #[test]
263    fn test_valid_transitions_from_detached() {
264        assert!(NodeState::Detached.can_transition_to(NodeState::Unknown));
265        assert!(NodeState::Detached.can_transition_to(NodeState::Shutdown));
266        assert!(!NodeState::Detached.can_transition_to(NodeState::Master));
267        assert!(!NodeState::Detached.can_transition_to(NodeState::Replica));
268        assert!(!NodeState::Detached.can_transition_to(NodeState::Detached));
269    }
270
271    #[test]
272    fn test_valid_transitions_from_unknown() {
273        assert!(NodeState::Unknown.can_transition_to(NodeState::Master));
274        assert!(NodeState::Unknown.can_transition_to(NodeState::Replica));
275        assert!(NodeState::Unknown.can_transition_to(NodeState::Shutdown));
276        assert!(!NodeState::Unknown.can_transition_to(NodeState::Detached));
277        assert!(!NodeState::Unknown.can_transition_to(NodeState::Unknown));
278    }
279
280    #[test]
281    fn test_valid_transitions_from_master() {
282        assert!(NodeState::Master.can_transition_to(NodeState::Unknown));
283        assert!(NodeState::Master.can_transition_to(NodeState::Replica));
284        assert!(NodeState::Master.can_transition_to(NodeState::Shutdown));
285        assert!(!NodeState::Master.can_transition_to(NodeState::Detached));
286        assert!(!NodeState::Master.can_transition_to(NodeState::Master));
287    }
288
289    #[test]
290    fn test_valid_transitions_from_replica() {
291        assert!(NodeState::Replica.can_transition_to(NodeState::Unknown));
292        assert!(NodeState::Replica.can_transition_to(NodeState::Master));
293        assert!(NodeState::Replica.can_transition_to(NodeState::Shutdown));
294        assert!(!NodeState::Replica.can_transition_to(NodeState::Detached));
295        assert!(!NodeState::Replica.can_transition_to(NodeState::Replica));
296    }
297
298    #[test]
299    fn test_valid_transitions_from_shutdown() {
300        assert!(!NodeState::Shutdown.can_transition_to(NodeState::Detached));
301        assert!(!NodeState::Shutdown.can_transition_to(NodeState::Unknown));
302        assert!(!NodeState::Shutdown.can_transition_to(NodeState::Master));
303        assert!(!NodeState::Shutdown.can_transition_to(NodeState::Replica));
304        assert!(!NodeState::Shutdown.can_transition_to(NodeState::Shutdown));
305    }
306
307    // --- NodeStateMachine tests ---
308
309    #[test]
310    fn test_initial_state() {
311        let sm = NodeStateMachine::new();
312        assert_eq!(sm.get_state(), NodeState::Detached);
313        assert_eq!(sm.get_transition_count(), 0);
314    }
315
316    #[test]
317    fn test_default_impl() {
318        let sm = NodeStateMachine::default();
319        assert_eq!(sm.get_state(), NodeState::Detached);
320    }
321
322    #[test]
323    fn test_valid_transition_detached_to_unknown() {
324        let sm = NodeStateMachine::new();
325        let old = sm.transition_to(NodeState::Unknown).unwrap();
326        assert_eq!(old, NodeState::Detached);
327        assert_eq!(sm.get_state(), NodeState::Unknown);
328        assert_eq!(sm.get_transition_count(), 1);
329    }
330
331    #[test]
332    fn test_valid_transition_unknown_to_master() {
333        let sm = NodeStateMachine::new();
334        sm.transition_to(NodeState::Unknown).unwrap();
335        let old = sm.transition_to(NodeState::Master).unwrap();
336        assert_eq!(old, NodeState::Unknown);
337        assert_eq!(sm.get_state(), NodeState::Master);
338        assert_eq!(sm.get_transition_count(), 2);
339    }
340
341    #[test]
342    fn test_valid_transition_unknown_to_replica() {
343        let sm = NodeStateMachine::new();
344        sm.transition_to(NodeState::Unknown).unwrap();
345        let old = sm.transition_to(NodeState::Replica).unwrap();
346        assert_eq!(old, NodeState::Unknown);
347        assert_eq!(sm.get_state(), NodeState::Replica);
348    }
349
350    #[test]
351    fn test_valid_transition_master_to_unknown() {
352        let sm = NodeStateMachine::new();
353        sm.transition_to(NodeState::Unknown).unwrap();
354        sm.transition_to(NodeState::Master).unwrap();
355        let old = sm.transition_to(NodeState::Unknown).unwrap();
356        assert_eq!(old, NodeState::Master);
357        assert_eq!(sm.get_state(), NodeState::Unknown);
358    }
359
360    #[test]
361    fn test_valid_transition_master_to_replica() {
362        let sm = NodeStateMachine::new();
363        sm.transition_to(NodeState::Unknown).unwrap();
364        sm.transition_to(NodeState::Master).unwrap();
365        let old = sm.transition_to(NodeState::Replica).unwrap();
366        assert_eq!(old, NodeState::Master);
367        assert_eq!(sm.get_state(), NodeState::Replica);
368    }
369
370    #[test]
371    fn test_valid_transition_replica_to_unknown() {
372        let sm = NodeStateMachine::new();
373        sm.transition_to(NodeState::Unknown).unwrap();
374        sm.transition_to(NodeState::Replica).unwrap();
375        let old = sm.transition_to(NodeState::Unknown).unwrap();
376        assert_eq!(old, NodeState::Replica);
377    }
378
379    #[test]
380    fn test_valid_transition_replica_to_master() {
381        let sm = NodeStateMachine::new();
382        sm.transition_to(NodeState::Unknown).unwrap();
383        sm.transition_to(NodeState::Replica).unwrap();
384        let old = sm.transition_to(NodeState::Master).unwrap();
385        assert_eq!(old, NodeState::Replica);
386        assert_eq!(sm.get_state(), NodeState::Master);
387    }
388
389    #[test]
390    fn test_valid_transition_to_shutdown_from_all() {
391        // From Detached
392        let sm = NodeStateMachine::new();
393        sm.transition_to(NodeState::Shutdown).unwrap();
394        assert_eq!(sm.get_state(), NodeState::Shutdown);
395
396        // From Unknown
397        let sm = NodeStateMachine::new();
398        sm.transition_to(NodeState::Unknown).unwrap();
399        sm.transition_to(NodeState::Shutdown).unwrap();
400        assert_eq!(sm.get_state(), NodeState::Shutdown);
401
402        // From Master
403        let sm = NodeStateMachine::new();
404        sm.transition_to(NodeState::Unknown).unwrap();
405        sm.transition_to(NodeState::Master).unwrap();
406        sm.transition_to(NodeState::Shutdown).unwrap();
407        assert_eq!(sm.get_state(), NodeState::Shutdown);
408
409        // From Replica
410        let sm = NodeStateMachine::new();
411        sm.transition_to(NodeState::Unknown).unwrap();
412        sm.transition_to(NodeState::Replica).unwrap();
413        sm.transition_to(NodeState::Shutdown).unwrap();
414        assert_eq!(sm.get_state(), NodeState::Shutdown);
415    }
416
417    #[test]
418    fn test_invalid_transition_detached_to_master() {
419        let sm = NodeStateMachine::new();
420        let result = sm.transition_to(NodeState::Master);
421        assert!(result.is_err());
422        assert_eq!(sm.get_state(), NodeState::Detached);
423        assert_eq!(sm.get_transition_count(), 0);
424    }
425
426    #[test]
427    fn test_invalid_transition_detached_to_replica() {
428        let sm = NodeStateMachine::new();
429        let result = sm.transition_to(NodeState::Replica);
430        assert!(result.is_err());
431        assert_eq!(sm.get_state(), NodeState::Detached);
432    }
433
434    #[test]
435    fn test_invalid_transition_from_shutdown() {
436        let sm = NodeStateMachine::new();
437        sm.transition_to(NodeState::Shutdown).unwrap();
438
439        assert!(sm.transition_to(NodeState::Detached).is_err());
440        assert!(sm.transition_to(NodeState::Unknown).is_err());
441        assert!(sm.transition_to(NodeState::Master).is_err());
442        assert!(sm.transition_to(NodeState::Replica).is_err());
443        assert!(sm.transition_to(NodeState::Shutdown).is_err());
444        assert_eq!(sm.get_state(), NodeState::Shutdown);
445        // Only one successful transition (Detached -> Shutdown)
446        assert_eq!(sm.get_transition_count(), 1);
447    }
448
449    #[test]
450    fn test_invalid_self_transition() {
451        let sm = NodeStateMachine::new();
452        // Detached -> Detached should fail
453        assert!(sm.transition_to(NodeState::Detached).is_err());
454
455        sm.transition_to(NodeState::Unknown).unwrap();
456        // Unknown -> Unknown should fail
457        assert!(sm.transition_to(NodeState::Unknown).is_err());
458    }
459
460    #[test]
461    fn test_transition_counting() {
462        let sm = NodeStateMachine::new();
463        assert_eq!(sm.get_transition_count(), 0);
464
465        sm.transition_to(NodeState::Unknown).unwrap();
466        assert_eq!(sm.get_transition_count(), 1);
467
468        sm.transition_to(NodeState::Master).unwrap();
469        assert_eq!(sm.get_transition_count(), 2);
470
471        sm.transition_to(NodeState::Unknown).unwrap();
472        assert_eq!(sm.get_transition_count(), 3);
473
474        sm.transition_to(NodeState::Replica).unwrap();
475        assert_eq!(sm.get_transition_count(), 4);
476
477        // Failed transition should not increment
478        let _ = sm.transition_to(NodeState::Detached);
479        assert_eq!(sm.get_transition_count(), 4);
480    }
481
482    #[test]
483    fn test_time_in_state() {
484        let sm = NodeStateMachine::new();
485        // Should be very short since we just created it
486        let d = sm.time_in_state();
487        assert!(d < Duration::from_secs(1));
488    }
489
490    #[test]
491    fn test_state_change_time_updates() {
492        let sm = NodeStateMachine::new();
493        let t1 = sm.get_state_change_time();
494        sm.transition_to(NodeState::Unknown).unwrap();
495        let t2 = sm.get_state_change_time();
496        assert!(t2 >= t1);
497    }
498
499    #[test]
500    fn test_can_transition_to_on_machine() {
501        let sm = NodeStateMachine::new();
502        assert!(sm.can_transition_to(NodeState::Unknown));
503        assert!(sm.can_transition_to(NodeState::Shutdown));
504        assert!(!sm.can_transition_to(NodeState::Master));
505
506        sm.transition_to(NodeState::Unknown).unwrap();
507        assert!(sm.can_transition_to(NodeState::Master));
508        assert!(sm.can_transition_to(NodeState::Replica));
509        assert!(!sm.can_transition_to(NodeState::Detached));
510    }
511
512    #[test]
513    fn test_full_lifecycle() {
514        let sm = NodeStateMachine::new();
515        assert_eq!(sm.get_state(), NodeState::Detached);
516
517        // Start up: enter election
518        sm.transition_to(NodeState::Unknown).unwrap();
519        assert_eq!(sm.get_state(), NodeState::Unknown);
520
521        // Win election: become master
522        sm.transition_to(NodeState::Master).unwrap();
523        assert_eq!(sm.get_state(), NodeState::Master);
524        assert!(sm.get_state().is_writable());
525        assert!(sm.get_state().is_readable());
526
527        // Master transfer: become replica
528        sm.transition_to(NodeState::Replica).unwrap();
529        assert_eq!(sm.get_state(), NodeState::Replica);
530        assert!(!sm.get_state().is_writable());
531        assert!(sm.get_state().is_readable());
532
533        // Lose contact with master
534        sm.transition_to(NodeState::Unknown).unwrap();
535        assert_eq!(sm.get_state(), NodeState::Unknown);
536
537        // Re-election: become master again
538        sm.transition_to(NodeState::Master).unwrap();
539        assert_eq!(sm.get_state(), NodeState::Master);
540
541        // Shutdown
542        sm.transition_to(NodeState::Shutdown).unwrap();
543        assert_eq!(sm.get_state(), NodeState::Shutdown);
544        assert!(!sm.get_state().is_writable());
545        assert!(!sm.get_state().is_readable());
546        assert!(!sm.get_state().is_active());
547
548        assert_eq!(sm.get_transition_count(), 6);
549    }
550
551    #[test]
552    fn test_send_sync() {
553        fn assert_send_sync<T: Send + Sync>() {}
554        assert_send_sync::<NodeStateMachine>();
555        assert_send_sync::<NodeState>();
556    }
557}