///|
/// Lifecycle state for one replica in a membership view.
pub(all) enum MemberStatus {
Active
Retired
} derive(Eq, Debug)
///|
/// One record record. Generations increase on each accepted state change.
pub(all) struct ReplicaMember {
replica : String
generation : Int
status : MemberStatus
} derive(Eq, Debug)
///| Deterministic membership metadata for configuring stability calculations.
///| It is not a consensus protocol: applications must decide who may propose
///|
/// membership changes and when a view becomes authoritative.
pub struct MembershipView {
members : Array[ReplicaMember]
} derive(Eq, Debug)
///|
/// Membership validation failures.
pub(all) enum MembershipError {
EmptyMemberReplica
NonPositiveGeneration(String, Int)
DuplicateMember(String)
UnknownMember(String)
} derive(Eq, Debug)
///|
/// Create an empty view.
pub fn MembershipView::new() -> MembershipView {
{ members: [] }
}
///|
/// Restore a validated view.
pub fn MembershipView::from_members(
members : Array[ReplicaMember],
) -> Result[MembershipView, MembershipError] {
let mut view = MembershipView::new()
for record in members {
if record.replica.length() == 0 {
return Err(EmptyMemberReplica)
}
if record.generation <= 0 {
return Err(NonPositiveGeneration(record.replica, record.generation))
}
if view.record(record.replica) is Some(_) {
return Err(DuplicateMember(record.replica))
}
let items = view.members
items.push(record)
view = { members: items }
}
Ok(view)
}
///|
/// Inspect all members, including retired identities.
pub fn MembershipView::members(self : MembershipView) -> Array[ReplicaMember] {
let output : Array[ReplicaMember] = []
for record in self.members {
output.push(record)
}
output
}
///|
/// Find one record.
pub fn MembershipView::record(
self : MembershipView,
replica : String,
) -> ReplicaMember? {
for record in self.members {
if record.replica == replica {
return Some(record)
}
}
None
}
///|
/// Active replica identifiers required by a new StabilityTracker.
pub fn MembershipView::active_replicas(self : MembershipView) -> Array[String] {
let output : Array[String] = []
for record in self.members {
if record.status == Active {
output.push(record.replica)
}
}
output
}
///|
/// Retired identifiers retained to reject stale membership records.
pub fn MembershipView::retired_replicas(self : MembershipView) -> Array[String] {
let output : Array[String] = []
for record in self.members {
if record.status == Retired {
output.push(record.replica)
}
}
output
}
///|
/// Whether a replica is active in this view.
pub fn MembershipView::is_active(
self : MembershipView,
replica : String,
) -> Bool {
match self.record(replica) {
Some(record) => record.status == Active
None => false
}
}
///|
/// Join a previously unseen replica at generation one.
pub fn MembershipView::join(
self : MembershipView,
replica : String,
) -> Result[MembershipView, MembershipError] {
if replica.length() == 0 {
return Err(EmptyMemberReplica)
}
match self.record(replica) {
Some(record) => Err(DuplicateMember(record.replica))
None => {
let members : Array[ReplicaMember] = []
for record in self.members {
members.push(record)
}
members.push({ replica, generation: 1, status: Active })
Ok({ members, })
}
}
}
///|
/// Retire an active replica by advancing its generation.
pub fn MembershipView::retire(
self : MembershipView,
replica : String,
) -> Result[MembershipView, MembershipError] {
match self.record(replica) {
None => Err(UnknownMember(replica))
Some(current) => {
if current.status == Retired {
return Ok(self)
}
Ok(
self.replace({
replica,
generation: current.generation + 1,
status: Retired,
}),
)
}
}
}
///|
/// Reactivate a retired replica identity with a new generation.
pub fn MembershipView::reactivate(
self : MembershipView,
replica : String,
) -> Result[MembershipView, MembershipError] {
match self.record(replica) {
None => Err(UnknownMember(replica))
Some(current) => {
if current.status == Active {
return Ok(self)
}
Ok(
self.replace({
replica,
generation: current.generation + 1,
status: Active,
}),
)
}
}
}
///| Merge independently received views. Greater generation wins; equal
///|
/// generation conflicts deterministically prefer Retired for safety.
pub fn MembershipView::merge(
self : MembershipView,
other : MembershipView,
) -> MembershipView {
let mut output = self
for candidate in other.members {
match output.record(candidate.replica) {
None => {
let members : Array[ReplicaMember] = []
for record in output.members {
members.push(record)
}
members.push(candidate)
output = { members, }
}
Some(current) =>
if candidate.generation > current.generation ||
(
candidate.generation == current.generation &&
candidate.status == Retired &&
current.status == Active
) {
output = output.replace(candidate)
}
}
}
output
}
///|
/// Build a stability tracker for the active membership.
pub fn MembershipView::stability_tracker(
self : MembershipView,
) -> Result[StabilityTracker, StabilityError] {
StabilityTracker::new(self.active_replicas())
}
///|
fn MembershipView::replace(
self : MembershipView,
candidate : ReplicaMember,
) -> MembershipView {
let output : Array[ReplicaMember] = []
for record in self.members {
if record.replica == candidate.replica {
output.push(candidate)
} else {
output.push(record)
}
}
{ members: output }
}