openraft 0.10.0-alpha.18

Advanced Raft consensus
Documentation
use std::cmp::Ordering;

use crate::vote::RaftLeaderId;

/// Provide comparison functions for [`RaftLeaderId`] implementations.
pub struct LeaderIdCompare;

impl LeaderIdCompare {
    /// Implements [`PartialOrd`] for LeaderId to enforce the standard Raft behavior of at most one
    /// leader per term.
    ///
    /// In standard Raft, each term can have at most one leader. This is enforced by making leader
    /// IDs with the same term incomparable (returning None), unless they refer to the same
    /// node.
    pub fn std<LID>(a: &LID, b: &LID) -> Option<Ordering>
    where LID: RaftLeaderId {
        match a.term().cmp(&b.term()) {
            Ordering::Equal => {
                if a.node_id() == b.node_id() {
                    Some(Ordering::Equal)
                } else {
                    None
                }
            }
            cmp => Some(cmp),
        }
    }

    /// Implements [`PartialOrd`] for LeaderId to allow multiple leaders per term.
    pub fn adv<LID>(a: &LID, b: &LID) -> Option<Ordering>
    where LID: RaftLeaderId {
        let res = (a.term(), a.node_id()).cmp(&(b.term(), b.node_id()));
        Some(res)
    }
}

#[cfg(test)]
mod tests {
    use std::cmp::Ordering;

    use crate::vote::RaftLeaderId;

    #[derive(Debug, PartialEq, Eq, Clone, PartialOrd, derive_more::Display)]
    #[display("T{}-N{}", _0, _1)]
    #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
    struct LeaderId(u64, u64);

    impl PartialEq<u64> for LeaderId {
        fn eq(&self, _other: &u64) -> bool {
            false
        }
    }

    impl PartialOrd<u64> for LeaderId {
        fn partial_cmp(&self, other: &u64) -> Option<Ordering> {
            self.0.partial_cmp(other)
        }
    }

    impl RaftLeaderId for LeaderId {
        type Term = u64;
        type NodeId = u64;
        type Committed = u64;

        fn new(term: u64, node_id: u64) -> Self {
            Self(term, node_id)
        }

        fn term(&self) -> u64 {
            self.0
        }

        fn node_id(&self) -> &u64 {
            &self.1
        }

        fn to_committed(&self) -> Self::Committed {
            self.0
        }
    }

    #[test]
    fn test_std_cmp() {
        use Ordering::*;

        use super::LeaderIdCompare as Cmp;

        let lid = |term, node_id| LeaderId(term, node_id);

        // Compare term first
        assert_eq!(Cmp::std(&lid(2, 2), &lid(1, 2)), Some(Greater));
        assert_eq!(Cmp::std(&lid(1, 2), &lid(2, 2)), Some(Less));

        // Equal
        assert_eq!(Cmp::std(&lid(2, 2), &lid(2, 2)), Some(Equal));

        // Incomparable
        assert_eq!(Cmp::std(&lid(2, 2), &lid(2, 1)), None);
        assert_eq!(Cmp::std(&lid(2, 1), &lid(2, 2)), None);
        assert_eq!(Cmp::std(&lid(2, 2), &lid(2, 3)), None);
    }

    #[test]
    fn test_adv_cmp() {
        use Ordering::*;

        use super::LeaderIdCompare as Cmp;

        let lid = |term, node_id| LeaderId(term, node_id);

        // Compare term first
        assert_eq!(Cmp::adv(&lid(2, 2), &lid(1, 2)), Some(Greater));
        assert_eq!(Cmp::adv(&lid(1, 2), &lid(2, 2)), Some(Less));

        // Equal term
        assert_eq!(Cmp::adv(&lid(2, 2), &lid(2, 1)), Some(Greater));
        assert_eq!(Cmp::adv(&lid(2, 1), &lid(2, 2)), Some(Less));

        // Equal term, node_id
        assert_eq!(Cmp::adv(&lid(2, 2), &lid(2, 2)), Some(Equal));
    }
}