use std::sync::atomic::{AtomicU64, AtomicU8, Ordering};
pub struct FlushSignal {
targets: Box<[AtomicU64]>,
completed: Box<[AtomicU64]>,
}
impl FlushSignal {
pub fn new(num_shards: usize) -> Self {
Self {
targets: (0..num_shards)
.map(|_| AtomicU64::new(u64::MAX))
.collect::<Vec<_>>()
.into_boxed_slice(),
completed: (0..num_shards)
.map(|_| AtomicU64::new(0))
.collect::<Vec<_>>()
.into_boxed_slice(),
}
}
pub fn request(&self, per_shard: &[u64]) {
for (s, &t) in per_shard.iter().enumerate() {
if s >= self.targets.len() {
break;
}
let mut cur = self.targets[s].load(Ordering::Acquire);
loop {
let new = if cur == u64::MAX { t } else { cur.max(t) };
match self.targets[s].compare_exchange_weak(
cur,
new,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => break,
Err(v) => cur = v,
}
}
}
}
pub fn complete(&self, shard: usize, durable: u64) {
let mut cur = self.completed[shard].load(Ordering::Acquire);
while cur < durable {
match self.completed[shard].compare_exchange_weak(
cur,
durable,
Ordering::Release,
Ordering::Acquire,
) {
Ok(_) => break,
Err(v) => cur = v,
}
}
}
pub fn is_done(&self, per_shard: &[u64]) -> bool {
per_shard.iter().enumerate().all(|(s, &t)| {
s >= self.completed.len() || self.completed[s].load(Ordering::Acquire) >= t
})
}
pub fn target(&self, shard: usize) -> u64 {
self.targets[shard].load(Ordering::Acquire)
}
pub fn any_pending(&self) -> bool {
self.targets
.iter()
.any(|t| t.load(Ordering::Acquire) != u64::MAX)
}
}
pub struct ShutdownState {
phase: AtomicU8,
pub(crate) drain_target: AtomicU64,
pub(crate) in_flight: AtomicU64,
}
impl Default for ShutdownState {
fn default() -> Self {
Self {
phase: AtomicU8::new(0),
drain_target: AtomicU64::new(u64::MAX),
in_flight: AtomicU64::new(0),
}
}
}
impl ShutdownState {
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn enter(&self) -> bool {
self.in_flight.fetch_add(1, Ordering::AcqRel);
if self.phase.load(Ordering::Acquire) >= 1 {
self.in_flight.fetch_sub(1, Ordering::AcqRel);
return false;
}
true
}
#[inline]
pub fn leave(&self) {
self.in_flight.fetch_sub(1, Ordering::AcqRel);
}
#[inline]
pub fn draining(&self) -> bool {
self.phase.load(Ordering::Acquire) >= 1
}
#[inline]
pub fn aborted(&self) -> bool {
self.phase.load(Ordering::Acquire) >= 2
}
#[inline]
pub fn should_stop(&self, processed_cursor: u64) -> bool {
if self.aborted() {
return true;
}
if self.draining() {
let t = self.drain_target.load(Ordering::Acquire);
return t != u64::MAX && processed_cursor >= t;
}
false
}
pub fn start_drain(&self) {
self.phase.store(1, Ordering::Release);
}
pub fn abort(&self) {
self.phase.store(2, Ordering::Release);
}
pub fn phase(&self) -> u8 {
self.phase.load(Ordering::Acquire)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn flush_signal_initial_state() {
let sig = FlushSignal::new(1);
assert_eq!(sig.target(0), u64::MAX);
assert!(!sig.any_pending());
assert!(!sig.is_done(&[1]));
}
#[test]
fn flush_signal_request_and_complete() {
let sig = FlushSignal::new(1);
sig.request(&[10]);
assert_eq!(sig.target(0), 10);
assert!(sig.any_pending());
assert!(!sig.is_done(&[10]));
sig.complete(0, 10);
assert!(sig.is_done(&[10]));
assert!(!sig.is_done(&[11]));
}
#[test]
fn flush_signal_cas_max_request() {
let sig = FlushSignal::new(1);
sig.request(&[5]);
sig.request(&[15]); sig.request(&[10]); assert_eq!(sig.target(0), 15);
}
#[test]
fn flush_signal_complete_is_monotonic() {
let sig = FlushSignal::new(1);
sig.complete(0, 20);
sig.complete(0, 10); assert!(sig.is_done(&[10]));
assert!(sig.is_done(&[20]));
}
#[test]
fn flush_signal_multi_shard_all_must_reach() {
let sig = FlushSignal::new(2);
sig.request(&[10, 20]);
assert!(!sig.is_done(&[10, 20]));
sig.complete(0, 10); assert!(!sig.is_done(&[10, 20])); sig.complete(1, 20); assert!(sig.is_done(&[10, 20]));
}
#[test]
fn shutdown_initial_running() {
let s = ShutdownState::new();
assert_eq!(s.phase(), 0);
assert!(!s.draining());
assert!(!s.aborted());
}
#[test]
fn shutdown_enter_leave_in_flight() {
let s = ShutdownState::new();
assert!(s.enter());
assert!(s.enter());
s.leave();
s.leave();
assert_eq!(s.in_flight.load(Ordering::Acquire), 0);
}
#[test]
fn shutdown_enter_rejected_after_drain() {
let s = ShutdownState::new();
s.start_drain();
assert!(!s.enter());
}
#[test]
fn shutdown_enter_rejected_after_abort() {
let s = ShutdownState::new();
s.abort();
assert!(!s.enter());
}
#[test]
fn shutdown_should_stop() {
let s = ShutdownState::new();
assert!(!s.should_stop(0));
s.start_drain();
s.drain_target.store(100, Ordering::Release);
assert!(!s.should_stop(50)); assert!(s.should_stop(100)); assert!(s.should_stop(150)); }
}