use std::sync::Arc;
use tokio::sync::watch;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CancelState {
Active,
Requested,
Draining {
in_flight: usize,
},
Confirmed,
}
impl CancelState {
pub fn is_cancelled(&self) -> bool {
!matches!(self, CancelState::Active)
}
pub fn is_draining(&self) -> bool {
matches!(self, CancelState::Draining { .. })
}
pub fn is_confirmed(&self) -> bool {
matches!(self, CancelState::Confirmed)
}
}
#[derive(Clone)]
pub struct CancellationToken {
request_tx: Arc<watch::Sender<bool>>,
state_rx: watch::Receiver<CancelState>,
}
impl CancellationToken {
pub fn new() -> (Self, CancelStateUpdater) {
let (request_tx, request_rx) = watch::channel(false);
let (state_tx, state_rx) = watch::channel(CancelState::Active);
let token = CancellationToken {
request_tx: Arc::new(request_tx),
state_rx,
};
let updater = CancelStateUpdater {
request_rx,
state_tx,
};
(token, updater)
}
pub fn request(&self) {
let _ = self.request_tx.send(true);
}
pub fn is_requested(&self) -> bool {
*self.request_tx.borrow()
}
pub fn state(&self) -> CancelState {
*self.state_rx.borrow()
}
pub fn is_confirmed(&self) -> bool {
self.state().is_confirmed()
}
pub fn wait_confirmed(&self) -> CancelConfirmation {
CancelConfirmation {
state_rx: self.state_rx.clone(),
}
}
}
pub struct CancelStateUpdater {
request_rx: watch::Receiver<bool>,
state_tx: watch::Sender<CancelState>,
}
impl CancelStateUpdater {
pub fn is_requested(&self) -> bool {
*self.request_rx.borrow()
}
pub async fn wait_for_request(&mut self) {
while !*self.request_rx.borrow() {
if self.request_rx.changed().await.is_err() {
break;
}
}
}
pub fn state(&self) -> CancelState {
*self.state_tx.borrow()
}
pub fn set_requested(&self) {
let _ = self.state_tx.send(CancelState::Requested);
}
pub fn set_draining(&self, in_flight: usize) {
let _ = self.state_tx.send(CancelState::Draining { in_flight });
}
pub fn update_draining(&self, in_flight: usize) {
if in_flight == 0 {
self.set_confirmed();
} else {
let _ = self.state_tx.send(CancelState::Draining { in_flight });
}
}
pub fn set_confirmed(&self) {
let _ = self.state_tx.send(CancelState::Confirmed);
}
pub fn subscribe(&self) -> watch::Receiver<CancelState> {
self.state_tx.subscribe()
}
}
pub struct CancelConfirmation {
state_rx: watch::Receiver<CancelState>,
}
impl CancelConfirmation {
pub async fn wait(mut self) {
loop {
if self.state_rx.borrow().is_confirmed() {
return;
}
if self.state_rx.changed().await.is_err() {
return;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cancel_state_transitions() {
let state = CancelState::Active;
assert!(!state.is_cancelled());
assert!(!state.is_draining());
assert!(!state.is_confirmed());
let state = CancelState::Requested;
assert!(state.is_cancelled());
assert!(!state.is_draining());
assert!(!state.is_confirmed());
let state = CancelState::Draining { in_flight: 5 };
assert!(state.is_cancelled());
assert!(state.is_draining());
assert!(!state.is_confirmed());
let state = CancelState::Confirmed;
assert!(state.is_cancelled());
assert!(!state.is_draining());
assert!(state.is_confirmed());
}
#[test]
fn test_cancellation_token_request() {
let (token, _updater) = CancellationToken::new();
assert!(!token.is_requested());
assert_eq!(token.state(), CancelState::Active);
token.request();
assert!(token.is_requested());
}
#[test]
fn test_cancellation_updater_state() {
let (token, updater) = CancellationToken::new();
assert_eq!(token.state(), CancelState::Active);
updater.set_requested();
assert_eq!(token.state(), CancelState::Requested);
updater.set_draining(3);
assert_eq!(token.state(), CancelState::Draining { in_flight: 3 });
updater.update_draining(1);
assert_eq!(token.state(), CancelState::Draining { in_flight: 1 });
updater.update_draining(0);
assert_eq!(token.state(), CancelState::Confirmed);
}
#[tokio::test]
async fn test_cancel_confirmation_immediate() {
let (token, updater) = CancellationToken::new();
updater.set_confirmed();
token.wait_confirmed().wait().await;
assert!(token.is_confirmed());
}
#[tokio::test]
async fn test_cancel_confirmation_delayed() {
let (token, updater) = CancellationToken::new();
let confirmation = token.wait_confirmed();
let updater_clone = updater.state_tx.clone();
tokio::spawn(async move {
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
let _ = updater_clone.send(CancelState::Confirmed);
});
tokio::time::timeout(tokio::time::Duration::from_millis(100), confirmation.wait())
.await
.expect("Should complete within timeout");
assert!(token.is_confirmed());
}
#[tokio::test]
async fn test_confirmation_blocked_during_draining() {
let (token, updater) = CancellationToken::new();
token.request();
updater.set_draining(2);
let confirmation = token.wait_confirmed();
let result =
tokio::time::timeout(tokio::time::Duration::from_millis(30), confirmation.wait()).await;
assert!(result.is_err(), "Should timeout while in_flight > 0");
assert_eq!(token.state(), CancelState::Draining { in_flight: 2 });
}
#[test]
fn test_draining_zero_confirms() {
let (token, updater) = CancellationToken::new();
token.request();
updater.set_draining(1);
assert_eq!(token.state(), CancelState::Draining { in_flight: 1 });
updater.update_draining(0);
assert_eq!(token.state(), CancelState::Confirmed);
}
#[test]
fn test_full_draining_sequence() {
let (token, updater) = CancellationToken::new();
assert_eq!(token.state(), CancelState::Active);
token.request();
assert!(token.is_requested());
updater.set_draining(3);
assert_eq!(token.state(), CancelState::Draining { in_flight: 3 });
updater.update_draining(2);
assert_eq!(token.state(), CancelState::Draining { in_flight: 2 });
updater.update_draining(1);
assert_eq!(token.state(), CancelState::Draining { in_flight: 1 });
updater.update_draining(0);
assert!(token.is_confirmed());
}
}