use super::swim::MembershipKind;
use super::view::MembershipView;
use crate::remote::NodeId;
use std::collections::BTreeSet;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LeaseAction {
Resume,
PauseGrants,
Revoke,
}
#[must_use]
pub fn lease_action_for(kind: MembershipKind) -> LeaseAction {
match kind {
MembershipKind::Alive => LeaseAction::Resume,
MembershipKind::Suspect => LeaseAction::PauseGrants,
MembershipKind::Dead | MembershipKind::Left => LeaseAction::Revoke,
}
}
#[derive(Debug, Clone, Default)]
pub struct MembershipLeaseReactor {
cursor: usize,
paused: BTreeSet<NodeId>,
revoked: BTreeSet<NodeId>,
}
impl MembershipLeaseReactor {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn poll(&mut self, view: &MembershipView) -> Vec<(NodeId, LeaseAction)> {
let mut actions = Vec::new();
for event in view.events_since(self.cursor) {
let node = &event.node;
if self.revoked.contains(node) {
continue; }
match lease_action_for(event.kind) {
LeaseAction::Revoke => {
self.revoked.insert(node.clone());
self.paused.remove(node);
actions.push((node.clone(), LeaseAction::Revoke));
}
LeaseAction::PauseGrants => {
if self.paused.insert(node.clone()) {
actions.push((node.clone(), LeaseAction::PauseGrants));
}
}
LeaseAction::Resume => {
if self.paused.remove(node) {
actions.push((node.clone(), LeaseAction::Resume));
}
}
}
}
self.cursor = view.event_count();
actions
}
#[must_use]
pub fn is_paused(&self, node: &NodeId) -> bool {
self.paused.contains(node)
}
#[must_use]
pub fn is_revoked(&self, node: &NodeId) -> bool {
self.revoked.contains(node)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn node(s: &str) -> NodeId {
NodeId::new(s)
}
fn event(n: &str, kind: MembershipKind) -> super::super::swim::MembershipEvent {
super::super::swim::MembershipEvent {
node: node(n),
kind,
incarnation: 0,
}
}
#[test]
fn kind_maps_to_action() {
assert_eq!(lease_action_for(MembershipKind::Alive), LeaseAction::Resume);
assert_eq!(
lease_action_for(MembershipKind::Suspect),
LeaseAction::PauseGrants
);
assert_eq!(lease_action_for(MembershipKind::Dead), LeaseAction::Revoke);
assert_eq!(lease_action_for(MembershipKind::Left), LeaseAction::Revoke);
}
#[test]
fn suspect_pauses_then_dead_revokes() {
let mut view = MembershipView::new();
let mut reactor = MembershipLeaseReactor::new();
view.apply(event("a", MembershipKind::Alive));
view.apply(event("a", MembershipKind::Suspect));
let actions = reactor.poll(&view);
assert_eq!(actions, vec![(node("a"), LeaseAction::PauseGrants)]);
assert!(reactor.is_paused(&node("a")));
view.apply(event("a", MembershipKind::Dead));
let actions = reactor.poll(&view);
assert_eq!(actions, vec![(node("a"), LeaseAction::Revoke)]);
assert!(reactor.is_revoked(&node("a")));
assert!(!reactor.is_paused(&node("a")));
}
#[test]
fn refutation_resumes_grants() {
let mut view = MembershipView::new();
let mut reactor = MembershipLeaseReactor::new();
view.apply(event("a", MembershipKind::Suspect));
let _ = reactor.poll(&view);
assert!(reactor.is_paused(&node("a")));
view.apply(event("a", MembershipKind::Alive));
let actions = reactor.poll(&view);
assert_eq!(actions, vec![(node("a"), LeaseAction::Resume)]);
assert!(!reactor.is_paused(&node("a")));
}
#[test]
fn revocation_is_terminal() {
let mut view = MembershipView::new();
let mut reactor = MembershipLeaseReactor::new();
view.apply(event("a", MembershipKind::Dead));
assert_eq!(reactor.poll(&view), vec![(node("a"), LeaseAction::Revoke)]);
view.apply(event("a", MembershipKind::Alive));
assert!(reactor.poll(&view).is_empty());
assert!(reactor.is_revoked(&node("a")));
}
#[test]
fn repeated_suspect_is_idempotent() {
let mut view = MembershipView::new();
let mut reactor = MembershipLeaseReactor::new();
view.apply(event("a", MembershipKind::Suspect));
assert_eq!(
reactor.poll(&view),
vec![(node("a"), LeaseAction::PauseGrants)]
);
view.apply(event("a", MembershipKind::Suspect));
assert!(reactor.poll(&view).is_empty());
}
}