use std::collections::{HashMap, VecDeque};
use std::sync::{LazyLock, Mutex};
use tokio_util::sync::CancellationToken;
use crate::util::UnwrapPoison;
const FIRED_MARKS: usize = 32;
static CANCELS: LazyLock<CancelRegistry> = LazyLock::new(CancelRegistry::default);
#[derive(Default)]
struct CancelRegistry {
inner: Mutex<CancelState>,
}
#[derive(Default)]
struct CancelState {
rounds: HashMap<String, Entry>,
fired: VecDeque<String>,
}
struct Entry {
token: CancellationToken,
caller: Option<String>,
}
impl CancelRegistry {
fn register(&self, job_id: &str, caller: Option<&str>) -> CancelGuard {
let mut state = self.inner.lock().unwrap_poison();
let pre_fired = match state.fired.iter().position(|id| id == job_id) {
Some(at) => {
state.fired.remove(at);
true
}
None => false,
};
let token = CancellationToken::new();
if pre_fired {
token.cancel();
}
state.rounds.insert(
job_id.to_string(),
Entry {
token,
caller: caller.map(str::to_string),
},
);
CancelGuard {
job_id: job_id.to_string(),
}
}
fn cancel(&self, job_id: &str) {
let mut state = self.inner.lock().unwrap_poison();
if let Some(entry) = state.rounds.get(job_id) {
entry.token.cancel();
return;
}
if !state.fired.iter().any(|id| id == job_id) {
if state.fired.len() == FIRED_MARKS {
let _ = state.fired.pop_front();
}
state.fired.push_back(job_id.to_string());
}
}
fn is_cancelled(&self, job_id: &str) -> bool {
self.inner
.lock()
.unwrap_poison()
.rounds
.get(job_id)
.is_some_and(|entry| entry.token.is_cancelled())
}
fn token(&self, job_id: &str) -> Option<CancellationToken> {
self.inner
.lock()
.unwrap_poison()
.rounds
.get(job_id)
.map(|entry| entry.token.clone())
}
fn is_registered(&self, job_id: &str) -> bool {
self.inner
.lock()
.unwrap_poison()
.rounds
.contains_key(job_id)
}
fn registered_caller(&self, job_id: &str) -> Option<String> {
self.inner
.lock()
.unwrap_poison()
.rounds
.get(job_id)
.and_then(|entry| entry.caller.clone())
}
fn unregister(&self, job_id: &str) {
self.inner.lock().unwrap_poison().rounds.remove(job_id);
}
}
pub(crate) struct CancelGuard {
job_id: String,
}
impl Drop for CancelGuard {
fn drop(&mut self) {
CANCELS.unregister(&self.job_id);
}
}
pub(crate) fn register(job_id: &str, caller: Option<&str>) -> CancelGuard {
CANCELS.register(job_id, caller)
}
pub(crate) fn cancel(job_id: &str) {
CANCELS.cancel(job_id);
}
pub(crate) fn is_cancelled(job_id: &str) -> bool {
CANCELS.is_cancelled(job_id)
}
pub(crate) async fn cancelled(job_id: &str) {
match CANCELS.token(job_id) {
Some(token) => token.cancelled().await,
None => std::future::pending::<()>().await,
}
}
pub(crate) fn is_registered(job_id: &str) -> bool {
CANCELS.is_registered(job_id)
}
pub(crate) fn registered_caller(job_id: &str) -> Option<String> {
CANCELS.registered_caller(job_id)
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn cancel_fires_signal_and_guard_removes_entry_on_drop() {
cancel("nope");
assert!(!is_cancelled("nope"));
assert!(
!is_registered("run_caller_1"),
"nothing is running for an unregistered id"
);
assert_eq!(
registered_caller("run_caller_1"),
None,
"an unregistered id has no recorded dispatching session"
);
let caller_guard = register("run_caller_1", Some("caller_session"));
assert!(
is_registered("run_caller_1"),
"a registered invocation is visible"
);
assert_eq!(
registered_caller("run_caller_1"),
Some("caller_session".to_string()),
"the dispatching session's id is recorded for the cancel action"
);
drop(caller_guard);
assert_eq!(
registered_caller("run_caller_1"),
None,
"guard drop removes the recorded caller too"
);
let guard = register("run_signal_1", None);
assert!(!is_cancelled("run_signal_1"));
cancel("run_signal_1");
assert!(is_cancelled("run_signal_1"), "fired signal stays visible");
cancel("run_signal_1");
assert!(is_cancelled("run_signal_1"));
drop(guard);
assert!(
!is_cancelled("run_signal_1"),
"guard drop removes the entry"
);
assert!(!is_registered("run_late_register"));
cancel("run_late_register");
let late = register("run_late_register", None);
assert!(
is_cancelled("run_late_register"),
"a registration racing a cancel starts pre-fired"
);
drop(late);
assert!(
!is_cancelled("run_late_register"),
"the mark is consumed by the registration it was waiting for"
);
}
}