use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use turbomcp_core::{CancellationToken, RequestId};
type Key = (String, RequestId);
#[derive(Default)]
pub(crate) struct InFlightRegistry {
map: Mutex<HashMap<Key, CancellationToken>>,
}
impl InFlightRegistry {
pub(crate) fn register(
self: &Arc<Self>,
connection: &str,
id: &RequestId,
token: CancellationToken,
) -> InFlightGuard {
let key = (connection.to_owned(), id.clone());
self.map
.lock()
.expect("inflight lock poisoned")
.insert(key.clone(), token);
InFlightGuard {
registry: Arc::clone(self),
key,
}
}
pub(crate) fn cancel(&self, connection: &str, id: &RequestId) -> bool {
let key = (connection.to_owned(), id.clone());
let found = self
.map
.lock()
.expect("inflight lock poisoned")
.get(&key)
.cloned();
match found {
Some(token) => {
token.cancel();
true
}
None => false,
}
}
#[cfg(test)]
fn len(&self) -> usize {
self.map.lock().expect("inflight lock poisoned").len()
}
}
pub(crate) struct InFlightGuard {
registry: Arc<InFlightRegistry>,
key: Key,
}
impl Drop for InFlightGuard {
fn drop(&mut self) {
self.registry
.map
.lock()
.expect("inflight lock poisoned")
.remove(&self.key);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cancel_is_scoped_to_the_connection() {
let reg = Arc::new(InFlightRegistry::default());
let token_a = CancellationToken::new();
let token_b = CancellationToken::new();
let id = RequestId::from(7i64);
let _guard_a = reg.register("conn-a", &id, token_a.clone());
let _guard_b = reg.register("conn-b", &id, token_b.clone());
assert!(reg.cancel("conn-a", &id));
assert!(token_a.is_cancelled());
assert!(!token_b.is_cancelled());
}
#[test]
fn guard_drop_deregisters_and_late_cancel_is_a_noop() {
let reg = Arc::new(InFlightRegistry::default());
let token = CancellationToken::new();
let id = RequestId::from("r-1");
let guard = reg.register("conn-1", &id, token.clone());
assert_eq!(reg.len(), 1);
drop(guard);
assert_eq!(reg.len(), 0);
assert!(!reg.cancel("conn-1", &id), "late cancel is ignored");
assert!(!token.is_cancelled());
}
}