Skip to main content

oximedia_distributed/
distributed_enhancements.rs

1//! Enhanced distributed primitives: Raft vote, work-stealing queue,
2//! backpressure controller, distributed checkpointing, consistent hash ring,
3//! distributed circuit breaker, shard allocator, service registry, and
4//! replication manager.
5
6#![allow(dead_code)]
7
8use std::collections::HashMap;
9use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
10use std::sync::Mutex;
11use std::time::{Duration, Instant};
12
13// ---------------------------------------------------------------------------
14// Raft primitives
15// ---------------------------------------------------------------------------
16
17/// Persistent and volatile state for a Raft node (u64-keyed variant).
18#[derive(Debug)]
19pub struct RaftState {
20    /// Latest term this node has seen.
21    pub current_term: u64,
22    /// Node ID of the candidate this node voted for in the current term.
23    pub voted_for: Option<u64>,
24    /// Replicated log entries.
25    pub log: Vec<LogEntry>,
26}
27
28/// A single entry in the Raft log.
29#[derive(Debug, Clone)]
30pub struct LogEntry {
31    /// Term in which the entry was created.
32    pub term: u64,
33    /// 1-based log index.
34    pub index: u64,
35    /// Encoded command payload.
36    pub command: String,
37}
38
39impl RaftState {
40    /// Create a new Raft state at term 0.
41    #[must_use]
42    pub fn new() -> Self {
43        Self {
44            current_term: 0,
45            voted_for: None,
46            log: Vec::new(),
47        }
48    }
49}
50
51impl Default for RaftState {
52    fn default() -> Self {
53        Self::new()
54    }
55}
56
57/// Response to a `RequestVote` RPC.
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct VoteResponse {
60    /// The term at which the vote was evaluated.
61    pub term: u64,
62    /// Whether the vote was granted.
63    pub vote_granted: bool,
64}
65
66/// A single Raft node.
67#[derive(Debug)]
68pub struct RaftNode {
69    /// This node's unique ID.
70    pub node_id: u64,
71    /// Raft state (wrapped for interior mutability in concurrent use).
72    state: Mutex<RaftState>,
73}
74
75impl RaftNode {
76    /// Create a new Raft node with the given ID.
77    #[must_use]
78    pub fn new(node_id: u64) -> Self {
79        Self {
80            node_id,
81            state: Mutex::new(RaftState::new()),
82        }
83    }
84
85    /// Handle a `RequestVote` RPC from a candidate.
86    ///
87    /// Implements the Raft voting rules:
88    /// - If `term < current_term`, deny the vote.
89    /// - If `term > current_term`, update the term and clear any prior vote.
90    /// - Grant the vote if `voted_for` is `None` or already equals `candidate_id`,
91    ///   **and** the candidate's log is at least as up-to-date as ours
92    ///   (last log index and term comparison).
93    ///
94    /// # Panics
95    ///
96    /// Panics if the internal mutex is poisoned (should never happen in normal use).
97    pub fn request_vote(
98        &self,
99        term: u64,
100        candidate_id: u64,
101        last_log_index: u64,
102        last_log_term: u64,
103    ) -> VoteResponse {
104        let mut state = self.state.lock().unwrap_or_else(|e| e.into_inner());
105
106        // If we see a higher term, update and clear our vote.
107        if term > state.current_term {
108            state.current_term = term;
109            state.voted_for = None;
110        }
111
112        // Deny if the candidate's term is stale.
113        if term < state.current_term {
114            return VoteResponse {
115                term: state.current_term,
116                vote_granted: false,
117            };
118        }
119
120        // Check whether we have already voted for someone else this term.
121        let can_vote = state.voted_for.is_none() || state.voted_for == Some(candidate_id);
122        if !can_vote {
123            return VoteResponse {
124                term: state.current_term,
125                vote_granted: false,
126            };
127        }
128
129        // Check log up-to-date-ness (§5.4.1 of the Raft paper).
130        let our_last_term = state.log.last().map_or(0, |e| e.term);
131        let our_last_index = state.log.len() as u64;
132
133        let candidate_log_ok = if last_log_term != our_last_term {
134            last_log_term > our_last_term
135        } else {
136            last_log_index >= our_last_index
137        };
138
139        if candidate_log_ok {
140            state.voted_for = Some(candidate_id);
141            VoteResponse {
142                term: state.current_term,
143                vote_granted: true,
144            }
145        } else {
146            VoteResponse {
147                term: state.current_term,
148                vote_granted: false,
149            }
150        }
151    }
152
153    /// Return the current term.
154    pub fn current_term(&self) -> u64 {
155        self.state
156            .lock()
157            .unwrap_or_else(|e| e.into_inner())
158            .current_term
159    }
160
161    /// Return who we voted for in the current term, if anyone.
162    pub fn voted_for(&self) -> Option<u64> {
163        self.state
164            .lock()
165            .unwrap_or_else(|e| e.into_inner())
166            .voted_for
167    }
168}
169
170// ---------------------------------------------------------------------------
171// Work-stealing queue
172// ---------------------------------------------------------------------------
173
174/// A per-worker work-stealing deque.
175///
176/// The owner pushes/pops from the back; thieves steal from the front.
177#[derive(Debug)]
178pub struct WorkStealingQueue<T> {
179    /// Tasks owned by this worker (LIFO end = back).
180    local: Vec<T>,
181    /// Tasks stolen from other workers (to be processed next).
182    stolen: Vec<T>,
183}
184
185impl<T> WorkStealingQueue<T> {
186    /// Create an empty queue.
187    #[must_use]
188    pub fn new() -> Self {
189        Self {
190            local: Vec::new(),
191            stolen: Vec::new(),
192        }
193    }
194
195    /// Push a task onto the local (owner's) end.
196    pub fn push(&mut self, item: T) {
197        self.local.push(item);
198    }
199
200    /// Pop a task for the owner to execute.
201    ///
202    /// Checks the `stolen` buffer first (so stolen tasks are prioritised),
203    /// then falls back to the local deque (LIFO).
204    pub fn pop(&mut self) -> Option<T> {
205        if let Some(item) = self.stolen.pop() {
206            return Some(item);
207        }
208        self.local.pop()
209    }
210
211    /// Steal a task from the front of the local deque (FIFO).
212    ///
213    /// Returns `None` if the local deque is empty.
214    pub fn steal(&mut self) -> Option<T> {
215        if self.local.is_empty() {
216            None
217        } else {
218            Some(self.local.remove(0))
219        }
220    }
221
222    /// Number of tasks in the local deque (not counting stolen tasks).
223    #[must_use]
224    pub fn len(&self) -> usize {
225        self.local.len() + self.stolen.len()
226    }
227
228    /// Returns `true` if both the local and stolen buffers are empty.
229    #[must_use]
230    pub fn is_empty(&self) -> bool {
231        self.local.is_empty() && self.stolen.is_empty()
232    }
233}
234
235impl<T> Default for WorkStealingQueue<T> {
236    fn default() -> Self {
237        Self::new()
238    }
239}
240
241// ---------------------------------------------------------------------------
242// BackpressureController
243// ---------------------------------------------------------------------------
244
245/// A simple pending-count-based backpressure controller.
246///
247/// `try_submit` returns `true` only when the number of in-flight items is
248/// strictly below `max_pending`. `complete_one` decrements the counter.
249#[derive(Debug)]
250pub struct BackpressureController {
251    /// Maximum number of simultaneously in-flight items.
252    max_pending: usize,
253    /// Current count of in-flight items.
254    pending: AtomicUsize,
255}
256
257impl BackpressureController {
258    /// Create a controller with the given maximum pending count.
259    #[must_use]
260    pub fn new(max_pending: usize) -> Self {
261        Self {
262            max_pending,
263            pending: AtomicUsize::new(0),
264        }
265    }
266
267    /// Attempt to submit a new item.
268    ///
269    /// Returns `true` and increments the pending counter if the limit has not
270    /// been reached. Returns `false` without modifying state if the queue is full.
271    pub fn try_submit(&self) -> bool {
272        // Use a compare-and-swap loop to atomically increment only if below limit.
273        loop {
274            let current = self.pending.load(Ordering::Acquire);
275            if current >= self.max_pending {
276                return false;
277            }
278            match self.pending.compare_exchange(
279                current,
280                current + 1,
281                Ordering::AcqRel,
282                Ordering::Acquire,
283            ) {
284                Ok(_) => return true,
285                Err(_) => continue, // raced, retry
286            }
287        }
288    }
289
290    /// Decrement the pending counter when an item completes.
291    ///
292    /// Will not decrement below zero (saturating).
293    pub fn complete_one(&self) {
294        let _ = self
295            .pending
296            .fetch_update(Ordering::AcqRel, Ordering::Acquire, |v| {
297                if v > 0 {
298                    Some(v - 1)
299                } else {
300                    None
301                }
302            });
303    }
304
305    /// Current number of in-flight items.
306    #[must_use]
307    pub fn pending_count(&self) -> usize {
308        self.pending.load(Ordering::Acquire)
309    }
310
311    /// Maximum allowed pending count.
312    #[must_use]
313    pub fn max_pending(&self) -> usize {
314        self.max_pending
315    }
316}
317
318// ---------------------------------------------------------------------------
319// Distributed checkpointing
320// ---------------------------------------------------------------------------
321
322/// A single distributed checkpoint snapshot.
323#[derive(Debug, Clone)]
324pub struct DistributedCheckpoint {
325    /// The node that created this checkpoint.
326    pub node_id: u64,
327    /// Monotonically increasing sequence number (unique per node).
328    pub sequence: u64,
329    /// Opaque serialised state data.
330    pub state: Vec<u8>,
331}
332
333/// Coordinates checkpoint creation and storage across multiple nodes.
334#[derive(Debug, Default)]
335pub struct CheckpointCoordinator {
336    /// Per-node sequence counters.
337    sequences: HashMap<u64, u64>,
338    /// All stored checkpoints.
339    checkpoints: Vec<DistributedCheckpoint>,
340}
341
342impl CheckpointCoordinator {
343    /// Create a new coordinator.
344    #[must_use]
345    pub fn new() -> Self {
346        Self::default()
347    }
348
349    /// Take a checkpoint for `node_id` with the given state.
350    ///
351    /// Allocates the next sequence number for the node and stores the
352    /// checkpoint. Returns the assigned sequence number.
353    pub fn take_checkpoint(&mut self, node_id: u64, state: &[u8]) -> u64 {
354        let seq = self.sequences.entry(node_id).or_insert(0);
355        *seq += 1;
356        let sequence = *seq;
357
358        self.checkpoints.push(DistributedCheckpoint {
359            node_id,
360            sequence,
361            state: state.to_vec(),
362        });
363
364        sequence
365    }
366
367    /// Retrieve the most recent checkpoint for a node.
368    #[must_use]
369    pub fn latest_checkpoint(&self, node_id: u64) -> Option<&DistributedCheckpoint> {
370        self.checkpoints
371            .iter()
372            .filter(|c| c.node_id == node_id)
373            .max_by_key(|c| c.sequence)
374    }
375
376    /// Total number of stored checkpoints.
377    #[must_use]
378    pub fn checkpoint_count(&self) -> usize {
379        self.checkpoints.len()
380    }
381}
382
383// ---------------------------------------------------------------------------
384// Consistent hash ring
385// ---------------------------------------------------------------------------
386
387/// A consistent hash ring with virtual-node support for even key distribution.
388///
389/// Uses the FNV-1a hash algorithm for deterministic, dependency-free hashing.
390#[derive(Debug)]
391pub struct ConsistentHashRing {
392    /// Number of virtual nodes per physical node.
393    virtual_nodes: u32,
394    /// Sorted ring: (hash_position, node_id).
395    ring: Vec<(u64, u64)>,
396    /// Set of registered physical nodes.
397    nodes: Vec<u64>,
398}
399
400impl ConsistentHashRing {
401    /// Create a new ring with the given number of virtual nodes per physical node.
402    #[must_use]
403    pub fn new(virtual_nodes: u32) -> Self {
404        Self {
405            virtual_nodes,
406            ring: Vec::new(),
407            nodes: Vec::new(),
408        }
409    }
410
411    /// Add a physical node to the ring.
412    pub fn add_node(&mut self, id: u64) {
413        if self.nodes.contains(&id) {
414            return;
415        }
416        self.nodes.push(id);
417        for i in 0..self.virtual_nodes {
418            let key = format!("{id}:vn:{i}");
419            let h = Self::fnv1a(key.as_bytes());
420            self.ring.push((h, id));
421        }
422        self.ring.sort_unstable_by_key(|(h, _)| *h);
423    }
424
425    /// Remove a physical node from the ring.
426    pub fn remove_node(&mut self, id: u64) {
427        self.nodes.retain(|&n| n != id);
428        for i in 0..self.virtual_nodes {
429            let key = format!("{id}:vn:{i}");
430            let h = Self::fnv1a(key.as_bytes());
431            self.ring.retain(|(rh, _)| *rh != h);
432        }
433    }
434
435    /// Look up which physical node owns the given key.
436    ///
437    /// Returns `None` if the ring is empty.
438    #[must_use]
439    pub fn get_node(&self, key: &[u8]) -> Option<u64> {
440        if self.ring.is_empty() {
441            return None;
442        }
443        let h = Self::fnv1a(key);
444        // Binary-search for the first ring entry >= h.
445        match self.ring.binary_search_by_key(&h, |(rh, _)| *rh) {
446            Ok(idx) => Some(self.ring[idx].1),
447            Err(idx) => {
448                // Wrap around to first entry if h > all ring entries.
449                let idx = idx % self.ring.len();
450                Some(self.ring[idx].1)
451            }
452        }
453    }
454
455    /// Number of registered physical nodes.
456    #[must_use]
457    pub fn node_count(&self) -> usize {
458        self.nodes.len()
459    }
460
461    /// FNV-1a 64-bit hash.
462    fn fnv1a(data: &[u8]) -> u64 {
463        let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
464        for &byte in data {
465            hash ^= u64::from(byte);
466            hash = hash.wrapping_mul(0x0100_0000_01b3);
467        }
468        hash
469    }
470}
471
472// ---------------------------------------------------------------------------
473// Distributed circuit breaker
474// ---------------------------------------------------------------------------
475
476/// A distributed circuit breaker with failure threshold and timeout.
477#[derive(Debug)]
478pub struct DistributedCircuitBreaker {
479    /// Number of consecutive failures that trip the circuit.
480    threshold: u32,
481    /// How long the circuit stays open (milliseconds).
482    timeout_ms: u64,
483    /// Consecutive failure count.
484    failures: AtomicU64,
485    /// Timestamp (ms) when the circuit was opened (0 = not open).
486    opened_at_ms: AtomicU64,
487    /// Whether the circuit is currently open.
488    open: AtomicBool,
489}
490
491impl DistributedCircuitBreaker {
492    /// Create a new circuit breaker.
493    #[must_use]
494    pub fn new(threshold: u32, timeout_ms: u64) -> Self {
495        Self {
496            threshold,
497            timeout_ms,
498            failures: AtomicU64::new(0),
499            opened_at_ms: AtomicU64::new(0),
500            open: AtomicBool::new(false),
501        }
502    }
503
504    /// Record a successful call. Resets the failure count if the circuit is
505    /// closed.
506    pub fn call_succeeded(&self) {
507        if !self.open.load(Ordering::Acquire) {
508            self.failures.store(0, Ordering::Release);
509        }
510    }
511
512    /// Record a failed call. Opens the circuit if the failure threshold is
513    /// reached.
514    pub fn call_failed(&self) {
515        let prev = self.failures.fetch_add(1, Ordering::AcqRel);
516        if prev + 1 >= u64::from(self.threshold) {
517            let now_ms = Self::now_ms();
518            self.opened_at_ms.store(now_ms, Ordering::Release);
519            self.open.store(true, Ordering::Release);
520        }
521    }
522
523    /// Returns `true` if the circuit is currently open (requests should be
524    /// rejected).
525    ///
526    /// If the circuit was opened more than `timeout_ms` ago, it automatically
527    /// transitions back to closed (allowing probe requests through).
528    pub fn is_open(&self) -> bool {
529        if !self.open.load(Ordering::Acquire) {
530            return false;
531        }
532        // Check whether the timeout has elapsed.
533        let opened_at = self.opened_at_ms.load(Ordering::Acquire);
534        let elapsed = Self::now_ms().saturating_sub(opened_at);
535        if elapsed >= self.timeout_ms {
536            // Transition to closed (half-open probe).
537            self.open.store(false, Ordering::Release);
538            self.failures.store(0, Ordering::Release);
539            return false;
540        }
541        true
542    }
543
544    /// Current consecutive failure count.
545    #[must_use]
546    pub fn failure_count(&self) -> u64 {
547        self.failures.load(Ordering::Acquire)
548    }
549
550    /// Reset the circuit breaker to closed state.
551    pub fn reset(&self) {
552        self.failures.store(0, Ordering::Release);
553        self.open.store(false, Ordering::Release);
554        self.opened_at_ms.store(0, Ordering::Release);
555    }
556
557    /// Returns the current time as milliseconds since the Unix epoch.
558    /// Falls back to 0 on error (should never happen on a healthy system).
559    fn now_ms() -> u64 {
560        std::time::SystemTime::now()
561            .duration_since(std::time::UNIX_EPOCH)
562            .map(|d| d.as_millis() as u64)
563            .unwrap_or(0)
564    }
565}
566
567// ---------------------------------------------------------------------------
568// Shard allocation
569// ---------------------------------------------------------------------------
570
571/// Assign a key hash to a shard using simple modulo sharding.
572#[must_use]
573pub fn shard_assign(key_hash: u64, num_shards: u32) -> u32 {
574    if num_shards == 0 {
575        return 0;
576    }
577    (key_hash % u64::from(num_shards)) as u32
578}
579
580/// A simple FNV-1a hash helper for byte slices.
581#[must_use]
582pub fn fnv1a_hash(data: &[u8]) -> u64 {
583    let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
584    for &byte in data {
585        hash ^= u64::from(byte);
586        hash = hash.wrapping_mul(0x0100_0000_01b3);
587    }
588    hash
589}
590
591/// A fixed-shard key-value map.
592///
593/// Keys are hashed with FNV-1a, then assigned to a shard via modulo. Each
594/// shard holds an independent `HashMap` to allow future parallelism.
595#[derive(Debug)]
596pub struct SimpleShardMap {
597    shards: Vec<HashMap<Vec<u8>, Vec<u8>>>,
598    num_shards: u32,
599}
600
601impl SimpleShardMap {
602    /// Create a new shard map with `num_shards` shards.
603    #[must_use]
604    pub fn new(num_shards: u32) -> Self {
605        let count = num_shards.max(1) as usize;
606        Self {
607            shards: vec![HashMap::new(); count],
608            num_shards: num_shards.max(1),
609        }
610    }
611
612    /// Insert a key-value pair.
613    pub fn insert(&mut self, key: &[u8], value: Vec<u8>) {
614        let h = fnv1a_hash(key);
615        let shard = shard_assign(h, self.num_shards) as usize;
616        self.shards[shard].insert(key.to_vec(), value);
617    }
618
619    /// Get a value by key.
620    #[must_use]
621    pub fn get(&self, key: &[u8]) -> Option<&[u8]> {
622        let h = fnv1a_hash(key);
623        let shard = shard_assign(h, self.num_shards) as usize;
624        self.shards[shard].get(key).map(|v| v.as_slice())
625    }
626
627    /// Number of entries across all shards.
628    #[must_use]
629    pub fn len(&self) -> usize {
630        self.shards.iter().map(|s| s.len()).sum()
631    }
632
633    /// Returns `true` if the map has no entries.
634    #[must_use]
635    pub fn is_empty(&self) -> bool {
636        self.len() == 0
637    }
638
639    /// Returns entries-per-shard counts for load analysis.
640    #[must_use]
641    pub fn shard_counts(&self) -> Vec<usize> {
642        self.shards.iter().map(|s| s.len()).collect()
643    }
644}
645
646// ---------------------------------------------------------------------------
647// Service registry (TTL-based)
648// ---------------------------------------------------------------------------
649
650/// A registered service endpoint with TTL tracking.
651#[derive(Debug, Clone)]
652struct ServiceEntry {
653    /// Network address string (e.g. "192.168.1.10:50052").
654    addr: String,
655    /// Monotonic instant when this registration expires.
656    expires_at: Instant,
657}
658
659/// A simple in-process service registry with TTL-based expiry.
660#[derive(Debug)]
661pub struct ServiceRegistry {
662    entries: Mutex<HashMap<u64, ServiceEntry>>,
663    /// Default TTL for new registrations.
664    default_ttl: Duration,
665}
666
667impl ServiceRegistry {
668    /// Create a registry with the given default TTL.
669    #[must_use]
670    pub fn new(default_ttl: Duration) -> Self {
671        Self {
672            entries: Mutex::new(HashMap::new()),
673            default_ttl,
674        }
675    }
676
677    /// Create a registry with a 60-second default TTL.
678    #[must_use]
679    pub fn with_default_ttl() -> Self {
680        Self::new(Duration::from_secs(60))
681    }
682
683    /// Register a service endpoint.
684    ///
685    /// If a registration for `service_id` already exists it is replaced.
686    pub fn register(&self, service_id: u64, addr: &str) {
687        let entry = ServiceEntry {
688            addr: addr.to_string(),
689            expires_at: Instant::now() + self.default_ttl,
690        };
691        self.entries
692            .lock()
693            .unwrap_or_else(|e| e.into_inner())
694            .insert(service_id, entry);
695    }
696
697    /// Discover the address of a registered service.
698    ///
699    /// Returns `None` if the service is not registered or has expired.
700    pub fn discover(&self, service_id: u64) -> Option<String> {
701        let mut guard = self.entries.lock().unwrap_or_else(|e| e.into_inner());
702        match guard.get(&service_id) {
703            Some(entry) if entry.expires_at > Instant::now() => Some(entry.addr.clone()),
704            Some(_) => {
705                // Expired — remove and return None.
706                guard.remove(&service_id);
707                None
708            }
709            None => None,
710        }
711    }
712
713    /// Number of (potentially expired) registered services.
714    #[must_use]
715    pub fn registered_count(&self) -> usize {
716        self.entries.lock().unwrap_or_else(|e| e.into_inner()).len()
717    }
718
719    /// Purge all expired entries.
720    pub fn evict_expired(&self) {
721        let now = Instant::now();
722        self.entries
723            .lock()
724            .unwrap_or_else(|e| e.into_inner())
725            .retain(|_, e| e.expires_at > now);
726    }
727}
728
729// ---------------------------------------------------------------------------
730// Replication manager
731// ---------------------------------------------------------------------------
732
733/// Manages replica placement for data items.
734#[derive(Debug, Default)]
735pub struct ReplicationManager {
736    /// Map from data key → list of node IDs holding replicas.
737    replicas: HashMap<String, Vec<u64>>,
738}
739
740impl ReplicationManager {
741    /// Create a new replication manager.
742    #[must_use]
743    pub fn new() -> Self {
744        Self::default()
745    }
746
747    /// Replicate data to `factor` nodes selected from `nodes`.
748    ///
749    /// Selection is deterministic: the first `factor` nodes from the slice are
750    /// used. The mapping is stored internally and the selected node IDs are
751    /// returned.
752    ///
753    /// If `nodes` has fewer entries than `factor`, all provided nodes are used.
754    pub fn replicate(&mut self, data: &[u8], factor: u32, nodes: &[u64]) -> Vec<u64> {
755        let count = (factor as usize).min(nodes.len());
756        let selected: Vec<u64> = nodes[..count].to_vec();
757
758        // Use the FNV-1a hash of the data as the key for deduplication.
759        let key = format!("{:x}", fnv1a_hash(data));
760        self.replicas.insert(key, selected.clone());
761
762        selected
763    }
764
765    /// Return the nodes holding replicas for the given data.
766    #[must_use]
767    pub fn replica_nodes(&self, data: &[u8]) -> Option<&[u64]> {
768        let key = format!("{:x}", fnv1a_hash(data));
769        self.replicas.get(&key).map(|v| v.as_slice())
770    }
771
772    /// Total number of tracked replication records.
773    #[must_use]
774    pub fn record_count(&self) -> usize {
775        self.replicas.len()
776    }
777}
778
779// ---------------------------------------------------------------------------
780// Tests
781// ---------------------------------------------------------------------------
782
783#[cfg(test)]
784mod tests {
785    use super::*;
786
787    // ── RaftNode ─────────────────────────────────────────────────────────
788
789    #[test]
790    fn test_raft_vote_granted_when_term_greater() {
791        let node = RaftNode::new(1);
792        let resp = node.request_vote(5, 2, 0, 0);
793        assert!(resp.vote_granted, "should grant vote for higher term");
794        assert_eq!(resp.term, 5);
795    }
796
797    #[test]
798    fn test_raft_vote_denied_stale_term() {
799        let node = RaftNode::new(1);
800        // First grant vote at term 5
801        node.request_vote(5, 2, 0, 0);
802        // Now deny a request with term 3 (stale)
803        let resp = node.request_vote(3, 3, 0, 0);
804        assert!(!resp.vote_granted, "should deny vote for stale term");
805    }
806
807    #[test]
808    fn test_raft_vote_denied_already_voted() {
809        let node = RaftNode::new(1);
810        node.request_vote(1, 2, 0, 0); // vote for node 2
811        let resp = node.request_vote(1, 3, 0, 0); // try to vote for node 3 in same term
812        assert!(!resp.vote_granted, "should deny double-vote in same term");
813    }
814
815    #[test]
816    fn test_raft_vote_same_candidate_ok() {
817        let node = RaftNode::new(1);
818        node.request_vote(1, 2, 0, 0); // vote for node 2
819        let resp = node.request_vote(1, 2, 0, 0); // same candidate again
820        assert!(
821            resp.vote_granted,
822            "idempotent vote for same candidate should succeed"
823        );
824    }
825
826    #[test]
827    fn test_raft_vote_new_term_clears_old_vote() {
828        let node = RaftNode::new(1);
829        node.request_vote(1, 2, 0, 0); // vote for 2 in term 1
830        let resp = node.request_vote(2, 3, 0, 0); // new term → vote for 3
831        assert!(resp.vote_granted);
832        assert_eq!(node.voted_for(), Some(3));
833    }
834
835    // ── WorkStealingQueue ─────────────────────────────────────────────────
836
837    #[test]
838    fn test_wsq_push_pop_lifo() {
839        let mut q: WorkStealingQueue<u32> = WorkStealingQueue::new();
840        q.push(1);
841        q.push(2);
842        q.push(3);
843        assert_eq!(q.pop(), Some(3)); // LIFO
844        assert_eq!(q.pop(), Some(2));
845        assert_eq!(q.pop(), Some(1));
846        assert_eq!(q.pop(), None);
847    }
848
849    #[test]
850    fn test_wsq_steal_fifo() {
851        let mut q: WorkStealingQueue<u32> = WorkStealingQueue::new();
852        q.push(1);
853        q.push(2);
854        q.push(3);
855        assert_eq!(q.steal(), Some(1)); // FIFO
856        assert_eq!(q.steal(), Some(2));
857        assert_eq!(q.steal(), Some(3));
858        assert_eq!(q.steal(), None);
859    }
860
861    #[test]
862    fn test_wsq_len_and_empty() {
863        let mut q: WorkStealingQueue<&str> = WorkStealingQueue::new();
864        assert!(q.is_empty());
865        q.push("a");
866        q.push("b");
867        assert_eq!(q.len(), 2);
868    }
869
870    // ── BackpressureController ────────────────────────────────────────────
871
872    #[test]
873    fn test_backpressure_allows_up_to_max() {
874        let bp = BackpressureController::new(3);
875        assert!(bp.try_submit());
876        assert!(bp.try_submit());
877        assert!(bp.try_submit());
878        assert!(!bp.try_submit(), "should be rejected when at max");
879    }
880
881    #[test]
882    fn test_backpressure_complete_frees_slot() {
883        let bp = BackpressureController::new(1);
884        assert!(bp.try_submit());
885        assert!(!bp.try_submit()); // full
886        bp.complete_one();
887        assert!(bp.try_submit()); // slot freed
888    }
889
890    #[test]
891    fn test_backpressure_pending_count() {
892        let bp = BackpressureController::new(10);
893        bp.try_submit();
894        bp.try_submit();
895        assert_eq!(bp.pending_count(), 2);
896        bp.complete_one();
897        assert_eq!(bp.pending_count(), 1);
898    }
899
900    // ── CheckpointCoordinator ─────────────────────────────────────────────
901
902    #[test]
903    fn test_checkpoint_sequence_increments() {
904        let mut coord = CheckpointCoordinator::new();
905        let s1 = coord.take_checkpoint(1, b"state_a");
906        let s2 = coord.take_checkpoint(1, b"state_b");
907        assert_eq!(s1, 1);
908        assert_eq!(s2, 2);
909    }
910
911    #[test]
912    fn test_checkpoint_latest() {
913        let mut coord = CheckpointCoordinator::new();
914        coord.take_checkpoint(1, b"old");
915        coord.take_checkpoint(1, b"new");
916        let latest = coord.latest_checkpoint(1).expect("should have checkpoint");
917        assert_eq!(latest.state, b"new");
918        assert_eq!(latest.sequence, 2);
919    }
920
921    #[test]
922    fn test_checkpoint_independent_per_node() {
923        let mut coord = CheckpointCoordinator::new();
924        let s1 = coord.take_checkpoint(1, b"n1");
925        let s2 = coord.take_checkpoint(2, b"n2");
926        assert_eq!(s1, 1);
927        assert_eq!(s2, 1); // each node starts at 1
928        assert_eq!(coord.checkpoint_count(), 2);
929    }
930
931    // ── ConsistentHashRing ────────────────────────────────────────────────
932
933    #[test]
934    fn test_hash_ring_get_node_returns_same_for_same_key() {
935        let mut ring = ConsistentHashRing::new(100);
936        ring.add_node(1);
937        ring.add_node(2);
938        ring.add_node(3);
939        let n1 = ring.get_node(b"my-key");
940        let n2 = ring.get_node(b"my-key");
941        assert_eq!(n1, n2, "same key should always map to same node");
942    }
943
944    #[test]
945    fn test_hash_ring_empty_returns_none() {
946        let ring = ConsistentHashRing::new(50);
947        assert!(ring.get_node(b"anything").is_none());
948    }
949
950    #[test]
951    fn test_hash_ring_single_node_owns_all() {
952        let mut ring = ConsistentHashRing::new(10);
953        ring.add_node(42);
954        assert_eq!(ring.get_node(b"k1"), Some(42));
955        assert_eq!(ring.get_node(b"k2"), Some(42));
956    }
957
958    #[test]
959    fn test_hash_ring_remove_node() {
960        let mut ring = ConsistentHashRing::new(10);
961        ring.add_node(1);
962        ring.add_node(2);
963        ring.remove_node(1);
964        assert_eq!(ring.node_count(), 1);
965        assert_eq!(ring.get_node(b"any"), Some(2));
966    }
967
968    // ── DistributedCircuitBreaker ─────────────────────────────────────────
969
970    #[test]
971    fn test_circuit_breaker_opens_after_threshold() {
972        let cb = DistributedCircuitBreaker::new(3, 60_000);
973        cb.call_failed();
974        assert!(!cb.is_open());
975        cb.call_failed();
976        assert!(!cb.is_open());
977        cb.call_failed(); // threshold reached
978        assert!(cb.is_open());
979    }
980
981    #[test]
982    fn test_circuit_breaker_reset() {
983        let cb = DistributedCircuitBreaker::new(1, 60_000);
984        cb.call_failed();
985        assert!(cb.is_open());
986        cb.reset();
987        assert!(!cb.is_open());
988    }
989
990    #[test]
991    fn test_circuit_breaker_success_resets_count() {
992        let cb = DistributedCircuitBreaker::new(3, 60_000);
993        cb.call_failed();
994        cb.call_succeeded(); // resets count
995        cb.call_failed();
996        assert!(!cb.is_open()); // only 1 failure after reset, not yet at 3
997    }
998
999    // ── SimpleShardMap ────────────────────────────────────────────────────
1000
1001    #[test]
1002    fn test_shard_map_insert_get() {
1003        let mut sm = SimpleShardMap::new(4);
1004        sm.insert(b"key1", b"value1".to_vec());
1005        assert_eq!(sm.get(b"key1"), Some(b"value1".as_slice()));
1006        assert_eq!(sm.get(b"missing"), None);
1007    }
1008
1009    #[test]
1010    fn test_shard_map_uniform_distribution() {
1011        let num_shards = 8u32;
1012        let mut sm = SimpleShardMap::new(num_shards);
1013
1014        // Insert 1000 keys
1015        for i in 0u32..1000 {
1016            let key = i.to_le_bytes();
1017            sm.insert(&key, key.to_vec());
1018        }
1019
1020        let counts = sm.shard_counts();
1021        let max = *counts.iter().max().expect("should have max");
1022        let min = *counts.iter().min().expect("should have min");
1023        // With FNV-1a and 1000 keys, max/min ratio should be within 2x
1024        assert!(
1025            max <= min * 2 + 1,
1026            "distribution too uneven: max={max} min={min}"
1027        );
1028    }
1029
1030    #[test]
1031    fn test_shard_assign_basic() {
1032        assert_eq!(shard_assign(0, 4), 0);
1033        assert_eq!(shard_assign(4, 4), 0);
1034        assert_eq!(shard_assign(5, 4), 1);
1035        assert_eq!(shard_assign(7, 4), 3);
1036    }
1037
1038    // ── ServiceRegistry ───────────────────────────────────────────────────
1039
1040    #[test]
1041    fn test_service_registry_register_and_discover() {
1042        let reg = ServiceRegistry::with_default_ttl();
1043        reg.register(1, "10.0.0.1:50052");
1044        assert_eq!(reg.discover(1), Some("10.0.0.1:50052".to_string()));
1045    }
1046
1047    #[test]
1048    fn test_service_registry_missing_returns_none() {
1049        let reg = ServiceRegistry::with_default_ttl();
1050        assert!(reg.discover(99).is_none());
1051    }
1052
1053    #[test]
1054    fn test_service_registry_expired() {
1055        // TTL of 1 nanosecond so it expires immediately.
1056        let reg = ServiceRegistry::new(Duration::from_nanos(1));
1057        reg.register(1, "10.0.0.1:50052");
1058        // Spin until the entry expires (should be near-instant).
1059        std::thread::sleep(Duration::from_millis(2));
1060        assert!(
1061            reg.discover(1).is_none(),
1062            "expired entry should return None"
1063        );
1064    }
1065
1066    // ── ReplicationManager ────────────────────────────────────────────────
1067
1068    #[test]
1069    fn test_replication_manager_selects_factor_nodes() {
1070        let mut rm = ReplicationManager::new();
1071        let nodes = [1u64, 2, 3, 4, 5];
1072        let selected = rm.replicate(b"my-data", 3, &nodes);
1073        assert_eq!(selected.len(), 3);
1074        assert_eq!(selected, vec![1, 2, 3]);
1075    }
1076
1077    #[test]
1078    fn test_replication_manager_fewer_nodes_than_factor() {
1079        let mut rm = ReplicationManager::new();
1080        let nodes = [1u64, 2];
1081        let selected = rm.replicate(b"data", 5, &nodes);
1082        assert_eq!(selected.len(), 2, "should use all available nodes");
1083    }
1084
1085    #[test]
1086    fn test_replication_manager_lookup() {
1087        let mut rm = ReplicationManager::new();
1088        let nodes = [10u64, 20, 30];
1089        rm.replicate(b"key-data", 2, &nodes);
1090        let replicas = rm.replica_nodes(b"key-data").expect("should have replicas");
1091        assert_eq!(replicas, [10, 20]);
1092    }
1093}