use super::swim::{MembershipEvent, MembershipKind};
use crate::remote::NodeId;
use std::collections::BTreeMap;
#[derive(Debug, Clone, Default)]
pub struct MembershipView {
states: BTreeMap<NodeId, MembershipKind>,
log: Vec<MembershipEvent>,
base: usize,
}
impl MembershipView {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn apply(&mut self, event: MembershipEvent) {
self.states.insert(event.node.clone(), event.kind);
self.log.push(event);
}
pub fn apply_all(&mut self, events: impl IntoIterator<Item = MembershipEvent>) {
for event in events {
self.apply(event);
}
}
#[must_use]
pub fn kind_of(&self, node: &NodeId) -> Option<MembershipKind> {
self.states.get(node).copied()
}
#[must_use]
pub fn alive_peers(&self) -> Vec<NodeId> {
self.states
.iter()
.filter(|&(_, kind)| *kind == MembershipKind::Alive)
.map(|(id, _)| id.clone())
.collect()
}
#[must_use]
pub fn event_count(&self) -> usize {
self.base + self.log.len()
}
#[must_use]
pub fn compact_base(&self) -> usize {
self.base
}
#[must_use]
pub fn events_since(&self, cursor: usize) -> &[MembershipEvent] {
let start = cursor.saturating_sub(self.base).min(self.log.len());
&self.log[start..]
}
pub fn compact(&mut self, low_watermark: usize) {
let drop = low_watermark.saturating_sub(self.base).min(self.log.len());
if drop > 0 {
self.log.drain(0..drop);
self.base += drop;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn node(s: &str) -> NodeId {
NodeId::new(s)
}
fn event(n: &str, kind: MembershipKind, incarnation: u64) -> MembershipEvent {
MembershipEvent {
node: node(n),
kind,
incarnation,
}
}
#[test]
fn tracks_latest_state_and_process_group() {
let mut view = MembershipView::new();
view.apply(event("a", MembershipKind::Alive, 0));
view.apply(event("b", MembershipKind::Alive, 0));
view.apply(event("c", MembershipKind::Alive, 0));
assert_eq!(view.alive_peers(), vec![node("a"), node("b"), node("c")]);
view.apply(event("b", MembershipKind::Suspect, 0));
assert_eq!(view.kind_of(&node("b")), Some(MembershipKind::Suspect));
assert_eq!(view.alive_peers(), vec![node("a"), node("c")]);
view.apply(event("b", MembershipKind::Dead, 0));
assert_eq!(view.kind_of(&node("b")), Some(MembershipKind::Dead));
assert_eq!(view.alive_peers(), vec![node("a"), node("c")]);
}
#[test]
fn cursor_stream_delivers_each_event_once() {
let mut view = MembershipView::new();
let mut cursor = 0;
view.apply(event("a", MembershipKind::Alive, 0));
view.apply(event("a", MembershipKind::Suspect, 0));
let fresh = view.events_since(cursor);
assert_eq!(fresh.len(), 2);
cursor = view.event_count();
assert!(view.events_since(cursor).is_empty());
view.apply(event("a", MembershipKind::Dead, 1));
let fresh = view.events_since(cursor);
assert_eq!(fresh.len(), 1);
assert_eq!(fresh[0].kind, MembershipKind::Dead);
cursor = view.event_count();
assert!(view.events_since(cursor).is_empty());
}
#[test]
fn cursor_past_end_is_empty() {
let view = MembershipView::new();
assert!(view.events_since(99).is_empty());
}
#[test]
fn compact_bounds_log_and_keeps_absolute_cursors() {
let mut view = MembershipView::new();
for kind in [
MembershipKind::Alive,
MembershipKind::Suspect,
MembershipKind::Dead,
] {
view.apply(event("a", kind, 0));
}
view.apply(event("b", MembershipKind::Alive, 0));
assert_eq!(view.event_count(), 4);
view.compact(2);
assert_eq!(view.compact_base(), 2, "two events dropped from the front");
assert_eq!(view.event_count(), 4);
let tail = view.events_since(2);
assert_eq!(tail.len(), 2);
assert_eq!(tail[0].kind, MembershipKind::Dead);
assert_eq!(view.events_since(0).len(), 2);
view.compact(2);
assert_eq!(view.compact_base(), 2);
assert_eq!(view.kind_of(&node("a")), Some(MembershipKind::Dead));
assert_eq!(view.kind_of(&node("b")), Some(MembershipKind::Alive));
}
}