use super::{GatewayRegistryError, GatewayRegistryResult};
use std::sync::atomic::{AtomicU64, AtomicU8, Ordering};
const MODE_STOPPED: u8 = 0;
const MODE_RECOVERING: u8 = 1;
const MODE_HEALTHY: u8 = 2;
const MODE_ISOLATED: u8 = 3;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GatewayHaMode {
Stopped,
Recovering,
Healthy,
Isolated,
}
impl GatewayHaMode {
const fn from_raw(value: u8) -> Self {
match value {
MODE_RECOVERING => Self::Recovering,
MODE_HEALTHY => Self::Healthy,
MODE_ISOLATED => Self::Isolated,
_ => Self::Stopped,
}
}
const fn raw(self) -> u8 {
match self {
Self::Stopped => MODE_STOPPED,
Self::Recovering => MODE_RECOVERING,
Self::Healthy => MODE_HEALTHY,
Self::Isolated => MODE_ISOLATED,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GatewayHaLifecycleSnapshot {
pub mode: GatewayHaMode,
pub transitions: u64,
pub recoveries_started: u64,
pub isolations: u64,
pub fencing_rejections: u64,
pub last_recovery_duration_ms: u64,
}
pub struct GatewayHaLifecycle {
mode: AtomicU8,
transitions: AtomicU64,
recoveries_started: AtomicU64,
isolations: AtomicU64,
fencing_rejections: AtomicU64,
recovery_started_ms: AtomicU64,
last_recovery_duration_ms: AtomicU64,
}
impl Default for GatewayHaLifecycle {
fn default() -> Self {
Self::new()
}
}
impl GatewayHaLifecycle {
pub const fn new() -> Self {
Self {
mode: AtomicU8::new(MODE_STOPPED),
transitions: AtomicU64::new(0),
recoveries_started: AtomicU64::new(0),
isolations: AtomicU64::new(0),
fencing_rejections: AtomicU64::new(0),
recovery_started_ms: AtomicU64::new(0),
last_recovery_duration_ms: AtomicU64::new(0),
}
}
pub fn begin_recovery(&self, now_ms: u64) -> GatewayRegistryResult<()> {
loop {
let current = self.mode.load(Ordering::Acquire);
match GatewayHaMode::from_raw(current) {
GatewayHaMode::Recovering => return Ok(()),
GatewayHaMode::Healthy => return Err(GatewayRegistryError::InvalidContract),
GatewayHaMode::Stopped | GatewayHaMode::Isolated => {}
}
if self
.mode
.compare_exchange(
current,
MODE_RECOVERING,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
{
self.recovery_started_ms.store(now_ms, Ordering::Release);
increment(&self.recoveries_started);
increment(&self.transitions);
return Ok(());
}
}
}
pub fn mark_healthy(&self, now_ms: u64) -> GatewayRegistryResult<()> {
if self
.mode
.compare_exchange(
MODE_RECOVERING,
MODE_HEALTHY,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_err()
{
return Err(GatewayRegistryError::InvalidContract);
}
let started_ms = self.recovery_started_ms.swap(0, Ordering::AcqRel);
self.last_recovery_duration_ms
.store(now_ms.saturating_sub(started_ms), Ordering::Release);
increment(&self.transitions);
Ok(())
}
pub fn isolate(&self) -> GatewayRegistryResult<()> {
loop {
let current = self.mode.load(Ordering::Acquire);
match GatewayHaMode::from_raw(current) {
GatewayHaMode::Isolated => return Ok(()),
GatewayHaMode::Stopped => return Err(GatewayRegistryError::InvalidContract),
GatewayHaMode::Recovering | GatewayHaMode::Healthy => {}
}
if self
.mode
.compare_exchange(current, MODE_ISOLATED, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
self.recovery_started_ms.store(0, Ordering::Release);
increment(&self.isolations);
increment(&self.transitions);
return Ok(());
}
}
}
pub fn stop(&self) {
let previous = self.mode.swap(MODE_STOPPED, Ordering::AcqRel);
self.recovery_started_ms.store(0, Ordering::Release);
if previous != MODE_STOPPED {
increment(&self.transitions);
}
}
pub fn admit(&self) -> GatewayRegistryResult<()> {
if self.mode.load(Ordering::Acquire) == GatewayHaMode::Healthy.raw() {
return Ok(());
}
Err(GatewayRegistryError::Unavailable)
}
pub fn record_fencing_rejection(&self) {
increment(&self.fencing_rejections);
}
pub fn snapshot(&self) -> GatewayHaLifecycleSnapshot {
GatewayHaLifecycleSnapshot {
mode: GatewayHaMode::from_raw(self.mode.load(Ordering::Acquire)),
transitions: self.transitions.load(Ordering::Relaxed),
recoveries_started: self.recoveries_started.load(Ordering::Relaxed),
isolations: self.isolations.load(Ordering::Relaxed),
fencing_rejections: self.fencing_rejections.load(Ordering::Relaxed),
last_recovery_duration_ms: self.last_recovery_duration_ms.load(Ordering::Relaxed),
}
}
}
fn increment(counter: &AtomicU64) {
let _ = counter.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |value| {
Some(value.saturating_add(1))
});
}
#[cfg(test)]
#[path = "lifecycle_tests.rs"]
mod tests;