use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use tokio::sync::Notify;
#[derive(Clone, Debug)]
pub struct ShutdownConfig {
inner: Arc<ShutdownInner>,
}
#[derive(Debug)]
struct ShutdownInner {
draining: AtomicBool,
drain_notify: Notify,
max_connections: usize,
}
impl ShutdownConfig {
#[must_use]
pub fn new(max_connections: usize) -> Self {
Self {
inner: Arc::new(ShutdownInner {
draining: AtomicBool::new(false),
drain_notify: Notify::new(),
max_connections,
}),
}
}
pub fn begin_drain(&self) {
self.inner.draining.store(true, Ordering::Relaxed);
self.inner.drain_notify.notify_waiters();
}
#[must_use]
pub fn is_draining(&self) -> bool {
self.inner.draining.load(Ordering::Relaxed)
}
#[must_use]
pub fn max_connections(&self) -> usize {
self.inner.max_connections
}
pub fn drain_notified(&self) -> impl std::future::Future<Output = ()> + '_ {
self.inner.drain_notify.notified()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn drain_signal_is_set_and_idempotent() {
let s = ShutdownConfig::new(8);
assert!(!s.is_draining());
s.begin_drain();
assert!(s.is_draining());
s.begin_drain();
assert!(s.is_draining());
}
#[test]
fn clones_share_the_signal() {
let s = ShutdownConfig::new(8);
let s2 = s.clone();
s.begin_drain();
assert!(s2.is_draining(), "clones observe the same atomic");
}
#[test]
fn max_connections_is_recorded() {
let s = ShutdownConfig::new(256);
assert_eq!(s.max_connections(), 256);
}
#[tokio::test]
async fn drain_notified_completes_after_begin_drain() {
let s = ShutdownConfig::new(8);
let s2 = s.clone();
let waiter = tokio::spawn(async move {
s2.drain_notified().await;
});
tokio::task::yield_now().await;
s.begin_drain();
tokio::time::timeout(std::time::Duration::from_secs(2), waiter)
.await
.expect("drain_notified did not hang")
.expect("waiter task did not panic");
}
}