use std::sync::Arc;
use tokio::sync::watch;
#[derive(Clone)]
pub struct ShutdownSignal {
receiver: watch::Receiver<bool>,
}
impl std::fmt::Debug for ShutdownSignal {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("ShutdownSignal")
.field("is_shutting_down", &self.is_shutting_down())
.finish()
}
}
impl ShutdownSignal {
pub fn is_shutting_down(&self) -> bool {
*self.receiver.borrow()
}
pub async fn cancelled(&self) {
let mut receiver = self.receiver.clone();
if *receiver.borrow_and_update() {
return;
}
let _ = receiver.changed().await;
}
}
pub struct ShutdownTrigger {
sender: Arc<watch::Sender<bool>>,
_keepalive: Arc<watch::Receiver<bool>>,
}
impl std::fmt::Debug for ShutdownTrigger {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("ShutdownTrigger")
.finish_non_exhaustive()
}
}
impl Default for ShutdownTrigger {
fn default() -> Self {
Self::new()
}
}
impl ShutdownTrigger {
pub fn new() -> Self {
let (sender, keepalive) = watch::channel(false);
Self {
sender: Arc::new(sender),
_keepalive: Arc::new(keepalive),
}
}
pub fn signal(&self) -> ShutdownSignal {
ShutdownSignal {
receiver: self.sender.subscribe(),
}
}
pub fn shutdown(&self) {
self.sender.send_replace(true);
}
}
impl Clone for ShutdownTrigger {
fn clone(&self) -> Self {
Self {
sender: Arc::clone(&self.sender),
_keepalive: Arc::clone(&self._keepalive),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn waiting_workers_are_released_on_shutdown() {
let trigger = ShutdownTrigger::new();
let signal = trigger.signal();
assert!(!signal.is_shutting_down());
let waiter = tokio::spawn({
let signal = signal.clone();
async move {
signal.cancelled().await;
true
}
});
trigger.shutdown();
assert!(waiter.await.expect("waiter task"));
assert!(signal.is_shutting_down());
}
#[tokio::test]
async fn a_signal_taken_after_shutdown_resolves_immediately() {
let trigger = ShutdownTrigger::new();
trigger.shutdown();
let signal = trigger.signal();
assert!(signal.is_shutting_down());
signal.cancelled().await;
}
}