use std::collections::HashMap; use std::sync::Arc; use tokio::sync::{Mutex, watch}; /// 共享的 Agent 取消注册表。 /// /// 每个正在执行的 Agent 在启动前按 topic_id 注册一个 watch::Sender, /// 外部(如 /stop 命令)通过 cancel_by_topic() 发送取消信号。 /// Agent 循环内部通过 watch::Receiver::has_changed() 检测取消。 /// /// key 使用 topic_id(UUID),全局唯一,精确到话题级别。 #[derive(Clone)] pub struct CancelManager { tokens: Arc>>>, } impl CancelManager { pub fn new() -> Self { Self { tokens: Arc::new(Mutex::new(HashMap::new())), } } /// 按 topic_id 注册一个取消通道,返回 receiver 供 Agent 持有。 /// /// 如果同 topic_id 已有注册,旧 sender 被覆盖并 drop, /// 旧 receiver 将收到通道关闭信号。 pub async fn register(&self, topic_id: &str) -> watch::Receiver<()> { let (tx, rx) = watch::channel(()); self.tokens.lock().await.insert(topic_id.to_string(), tx); rx } /// 按 topic_id 发送取消信号并移除注册条目。 /// /// 返回 `true` 表示找到了对应的任务并发送了取消信号, /// 返回 `false` 表示没有找到对应的任务(可能已经完成或从未注册)。 pub async fn cancel_by_topic(&self, topic_id: &str) -> bool { if let Some(tx) = self.tokens.lock().await.remove(topic_id) { // send 可能失败(receiver 已被 drop),这不影响语义 let _ = tx.send(()); true } else { false } } /// 按 topic_id 正常完成后清理注册条目(幂等)。 /// /// 与 cancel_by_topic() 不同,此方法不发送取消信号,仅移除条目。 /// 如果条目已被 cancel_by_topic() 移除,此调用为 no-op。 pub async fn remove_by_topic(&self, topic_id: &str) { self.tokens.lock().await.remove(topic_id); } } impl Default for CancelManager { fn default() -> Self { Self::new() } }