use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
use std::time::Duration;
use tokio::sync::watch;
use tokio::time::Instant;
const DRAIN_WAIT_POLL_INTERVAL_MS: u64 = 25;
#[derive(Debug)]
pub struct ConnectionDrainCoordinator {
signal: watch::Sender<bool>,
active_worker_count: AtomicUsize,
serving_worker_count: AtomicUsize,
drained_worker_count: AtomicUsize,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ConnectionDrainReport {
pub drained_worker_count: usize,
pub remaining_worker_count: usize,
pub remaining_serving_count: usize,
pub timed_out: bool,
}
impl ConnectionDrainCoordinator {
pub fn new() -> Arc<Self> {
let (signal, _) = watch::channel(false);
Arc::new(Self {
signal,
active_worker_count: AtomicUsize::new(0),
serving_worker_count: AtomicUsize::new(0),
drained_worker_count: AtomicUsize::new(0),
})
}
pub fn register_worker(self: &Arc<Self>) -> ConnectionWorkerSlot {
self.active_worker_count.fetch_add(1, Ordering::SeqCst);
ConnectionWorkerSlot {
coordinator: Arc::clone(self),
receiver: self.signal.subscribe(),
}
}
pub fn signal_shutdown(&self) {
self.signal.send_replace(true);
}
pub async fn wait_for_drain(&self, timeout: Duration) -> ConnectionDrainReport {
let deadline = Instant::now() + timeout;
loop {
let remaining = self.active_worker_count.load(Ordering::SeqCst);
if remaining == 0 {
return self.drain_report(false);
}
let now = Instant::now();
if now >= deadline {
return self.drain_report(true);
}
let poll = (deadline - now).min(Duration::from_millis(DRAIN_WAIT_POLL_INTERVAL_MS));
tokio::time::sleep(poll).await;
}
}
fn drain_report(&self, timed_out: bool) -> ConnectionDrainReport {
ConnectionDrainReport {
drained_worker_count: self.drained_worker_count.load(Ordering::SeqCst),
remaining_worker_count: self.active_worker_count.load(Ordering::SeqCst),
remaining_serving_count: self.serving_worker_count.load(Ordering::SeqCst),
timed_out,
}
}
}
#[derive(Debug)]
pub struct ConnectionWorkerSlot {
coordinator: Arc<ConnectionDrainCoordinator>,
receiver: watch::Receiver<bool>,
}
impl ConnectionWorkerSlot {
pub fn shutdown_signaled(&self) -> bool {
*self.receiver.borrow()
}
pub async fn shutdown_signal(&mut self) {
while !*self.receiver.borrow() {
if self.receiver.changed().await.is_err() {
return;
}
}
}
pub fn begin_serving(&self) -> WorkerServingGuard<'_> {
self.coordinator
.serving_worker_count
.fetch_add(1, Ordering::SeqCst);
WorkerServingGuard {
coordinator: &self.coordinator,
}
}
}
impl Drop for ConnectionWorkerSlot {
fn drop(&mut self) {
self.coordinator
.active_worker_count
.fetch_sub(1, Ordering::SeqCst);
if *self.receiver.borrow() {
self.coordinator
.drained_worker_count
.fetch_add(1, Ordering::SeqCst);
}
}
}
#[derive(Debug)]
pub struct WorkerServingGuard<'a> {
coordinator: &'a ConnectionDrainCoordinator,
}
impl Drop for WorkerServingGuard<'_> {
fn drop(&mut self) {
self.coordinator
.serving_worker_count
.fetch_sub(1, Ordering::SeqCst);
}
}