Skip to main content

nagisa_core/
service.rs

1//! 服务生命周期 Supervisor + 依赖 DAG。
2//!
3//! 一个 [`Service`] 有三段生命周期:
4//! 1. [`prepare`](Service::prepare):按依赖 DAG 的**拓扑序**逐层 await(依赖先就绪);
5//! 2. [`run`](Service::run):全部 spawn 进一个 [`JoinSet`],各持 supervisor 子树派生的子
6//!    [`ShutdownToken`];任一 `run` 返回 `Err`、或宿主 shutdown 触发即收束;
7//! 3. [`cleanup`](Service::cleanup):取消子树后,按**逆拓扑序**清理(宿主的 token 只听不动)。
8//!
9//! `prepare` 中途失败 → 中止,并对已 `prepare` 成功者逆序 cleanup。
10//!
11//! 服务之间通过 [`ServiceBus`] 共享句柄(按类型存取):例如 OneBot adapter 在
12//! `prepare`/`run` 里把它建好的 [`Bot`](crate::bot::Bot) `insert` 进 bus,dispatch
13//! 服务再 `get` 出来消费。这是门面 builder 据以搭线的脊柱。
14use crate::ShutdownToken;
15use async_trait::async_trait;
16use nagisa_types::error::{Error, Result};
17use std::any::{Any, TypeId};
18use std::collections::HashMap;
19use std::sync::{Arc, RwLock};
20use tokio::task::JoinSet;
21
22/// 服务间共享句柄袋:按类型(`TypeId`)存取 `Arc<T>`。廉价克隆(内部 `Arc`)。
23///
24/// 用于服务之间发布/查找共享句柄,典型场景:adapter 发布 `Bot`,dispatch 取用。
25#[derive(Clone, Default)]
26pub struct ServiceBus {
27    inner: Arc<RwLock<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>>,
28}
29
30impl ServiceBus {
31    /// 空 bus。
32    pub fn new() -> Self {
33        Self::default()
34    }
35
36    /// 发布一个 `T`(同类型覆盖)。以 `Arc<T>` 存储,`get::<T>()` 取回同一份。
37    /// 锁中毒时取用 guard 内数据,而非 panic(保持无 panic 合约)。
38    pub fn insert<T: Send + Sync + 'static>(&self, value: T) {
39        let mut map = self.inner.write().unwrap_or_else(|e| e.into_inner());
40        map.insert(TypeId::of::<T>(), Arc::new(value));
41    }
42
43    /// 取回先前 `insert::<T>` 的共享句柄;从未发布过则 `None`。
44    /// 锁中毒时取用 guard 内数据,而非 panic(保持无 panic 合约)。
45    pub fn get<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
46        let map = self.inner.read().unwrap_or_else(|e| e.into_inner());
47        map.get(&TypeId::of::<T>()).and_then(|v| Arc::clone(v).downcast::<T>().ok())
48    }
49}
50
51/// 受 Supervisor 管理的服务。三段式生命周期 + 依赖声明。
52///
53/// 默认方法允许只实现关心的阶段(如纯 `run` 的传输服务无需 `prepare`/`cleanup`)。
54///
55/// 因为该 trait 用 `#[async_trait]` 定义,自定义实现的 `impl` 块也必须标注它——
56/// 否则会撞上晦涩的 `E0195`。框架从门面 re-export 了它,直接用 `#[nagisa::async_trait]`:
57///
58/// ```ignore
59/// use nagisa::prelude::*;
60///
61/// struct MyService;
62///
63/// #[nagisa::async_trait]
64/// impl Service for MyService {
65///     fn id(&self) -> &'static str { "my-service" }
66///     async fn run(self: std::sync::Arc<Self>, _bus: ServiceBus, shutdown: ShutdownToken) -> Result<()> {
67///         shutdown.cancelled().await;
68///         Ok(())
69///     }
70/// }
71/// ```
72#[async_trait]
73pub trait Service: Send + Sync + 'static {
74    /// 全局唯一 id;`deps()` 用它引用其他服务。
75    fn id(&self) -> &'static str;
76
77    /// 本服务依赖的其他服务 id(须在它们 `prepare` 之后才能 `prepare`)。
78    fn deps(&self) -> &'static [&'static str] {
79        &[]
80    }
81
82    /// 准备阶段:按依赖拓扑序调用。发布共享句柄到 `bus`、建连接、读配置等。
83    async fn prepare(&self, bus: &ServiceBus) -> Result<()> {
84        let _ = bus;
85        Ok(())
86    }
87
88    /// 运行阶段:长生命周期任务,spawn 进 `JoinSet`。`select!` 监听 `shutdown`。
89    /// 正常应在 `shutdown` 触发后返回 `Ok(())`;返回 `Err` 会触发整体收束。
90    async fn run(self: Arc<Self>, bus: ServiceBus, shutdown: ShutdownToken) -> Result<()> {
91        let _ = (bus, shutdown);
92        Ok(())
93    }
94
95    /// 清理阶段:按逆拓扑序调用。关闭连接、刷盘、释放资源。
96    async fn cleanup(&self, bus: &ServiceBus) -> Result<()> {
97        let _ = bus;
98        Ok(())
99    }
100}
101
102/// 服务生命周期 Supervisor:注册一批服务,按依赖 DAG 编排 prepare→run→cleanup。
103#[derive(Default)]
104pub struct Supervisor {
105    services: Vec<Arc<dyn Service>>,
106    /// 与 `services` 平行:第 i 个服务是否为「可选」。可选服务的 `prepare`/`run` 失败只记
107    /// 日志、不回滚其余、不触发整体收束——用于「附加能力」型服务(如 web 后台),起不来
108    /// 不该拖垮 bot 主体。
109    optional: Vec<bool>,
110    bus: ServiceBus,
111}
112
113impl Supervisor {
114    /// 空 supervisor(自带一个空 [`ServiceBus`])。
115    pub fn new() -> Self {
116        Self::default()
117    }
118
119    /// 注册一个(必选)服务。其 `prepare`/`run` 失败会触发整体回滚/收束。
120    ///
121    /// 名为 `add` 是既定的 builder API(非 `std::ops::Add`)。
122    #[allow(clippy::should_implement_trait)]
123    pub fn add(mut self, service: Arc<dyn Service>) -> Self {
124        self.services.push(service);
125        self.optional.push(false);
126        self
127    }
128
129    /// 注册一个**可选**服务:它的 `prepare`/`run` 失败只记日志,**不**回滚其他服务、**不**
130    /// 触发整体收束。用于附加能力型服务(如 web 后台)。
131    ///
132    /// 约定:可选服务应是依赖图的**叶子**(不被其他服务 `deps()` 引用);依赖一个被跳过的
133    /// 可选服务属配置错误。
134    pub fn add_optional(mut self, service: Arc<dyn Service>) -> Self {
135        self.services.push(service);
136        self.optional.push(true);
137        self
138    }
139
140    /// 共享 [`ServiceBus`] 的克隆(廉价)。供外部预置句柄或事后查询。
141    pub fn bus(&self) -> ServiceBus {
142        self.bus.clone()
143    }
144
145    /// 编排整套生命周期。`shutdown` 是宿主的关停信号——supervisor 只监听、从不取消它
146    /// (收束时取消的是自己派生的子树 token),服务全部自然结束不影响宿主。
147    ///
148    /// 1. 按依赖拓扑序逐层 `prepare`(任一失败 → 对已就绪者逆序 cleanup 后返回 Err);
149    /// 2. 全部 `run` spawn 进 `JoinSet`,各持子树的子 token;
150    ///    await 至 shutdown 触发或某 `run` 返回 Err;
151    /// 3. 取消子树,按逆拓扑序 `cleanup`。
152    ///
153    /// 返回:若某 `run` 提前以 `Err` 收束则透传该错误,否则 `Ok(())`。
154    pub async fn run(self, shutdown: ShutdownToken) -> Result<()> {
155        let Supervisor { services, optional, bus } = self;
156
157        // —— 拓扑排序(Kahn):得到 prepare/run 顺序的下标序列。——
158        let order = toposort(&services)?;
159
160        // —— 1. prepare:按拓扑序逐个 await。
161        //    必选失败 → 逆序 cleanup 已就绪者后返回 Err;
162        //    可选失败 → 记 warn、跳过(不进 prepared、不 spawn 其 run)。——
163        let mut prepared: Vec<usize> = Vec::with_capacity(order.len());
164        for &idx in &order {
165            match services[idx].prepare(&bus).await {
166                Ok(()) => prepared.push(idx),
167                Err(e) if optional[idx] => {
168                    tracing::warn!(service = services[idx].id(), error = %e, "可选服务 prepare 失败;已跳过");
169                }
170                Err(e) => {
171                    tracing::error!(service = services[idx].id(), error = %e, "必选服务 prepare 失败,清理已就绪者");
172                    cleanup_reverse(&services, &prepared, &bus).await;
173                    return Err(e);
174                }
175            }
176        }
177
178        // —— 2. run:只 spawn 成功 prepare 的服务;任务携带其下标以便区分可选与否。——
179        let local = shutdown.child_token();
180        let mut set: JoinSet<(usize, Result<()>)> = JoinSet::new();
181        for &idx in &prepared {
182            let svc = Arc::clone(&services[idx]);
183            let bus = bus.clone();
184            let child = local.child_token();
185            set.spawn(async move { (idx, svc.run(bus, child).await) });
186        }
187
188        // await 至:shutdown 触发,或某 run 任务返回。
189        // 必选 run Err → 收束;可选 run Err → 记 warn、隔离,继续等其余。
190        let mut run_result: Result<()> = Ok(());
191        loop {
192            tokio::select! {
193                biased;
194                _ = shutdown.cancelled() => break,
195                joined = set.join_next() => {
196                    match joined {
197                        None => break,
198                        Some(Ok((_idx, Ok(())))) => continue,
199                        Some(Ok((idx, Err(e)))) if optional[idx] => {
200                            tracing::warn!(service = services[idx].id(), error = %e, "可选服务 run 出错;已隔离,不影响其余");
201                            continue;
202                        }
203                        Some(Ok((idx, Err(e)))) => {
204                            tracing::error!(service = services[idx].id(), error = %e, "必选服务 run 出错,收束");
205                            run_result = Err(e);
206                            break;
207                        }
208                        Some(Err(join_err)) => {
209                            tracing::error!(error = %join_err, "服务 run 任务 panic,收束");
210                            run_result = Err(internal_err(format!("服务 run 任务 panic:{join_err}")));
211                            break;
212                        }
213                    }
214                }
215            }
216        }
217
218        // —— 3. cleanup:取消子树,等剩余 run 任务收束,再对**已就绪**服务逆拓扑序 cleanup。——
219        local.cancel();
220        set.shutdown().await;
221        cleanup_reverse(&services, &prepared, &bus).await;
222
223        run_result
224    }
225}
226
227/// 对 `indices`(拓扑序的子序列)逆序调用 `cleanup`;逐个记录错误但不中断,
228/// 确保每个已就绪服务都有机会清理。
229async fn cleanup_reverse(services: &[Arc<dyn Service>], indices: &[usize], bus: &ServiceBus) {
230    for &idx in indices.iter().rev() {
231        if let Err(e) = services[idx].cleanup(bus).await {
232            tracing::error!(service = services[idx].id(), error = %e, "服务清理失败");
233        }
234    }
235}
236
237/// Kahn 拓扑排序:返回服务下标的拓扑序(依赖在前)。
238///
239/// 错误(经 [`internal_err`]):重复 id、依赖了不存在的 id、或存在环。
240fn toposort(services: &[Arc<dyn Service>]) -> Result<Vec<usize>> {
241    // id → 下标。重复 id 视为配置错误。
242    let mut index: HashMap<&'static str, usize> = HashMap::with_capacity(services.len());
243    for (i, svc) in services.iter().enumerate() {
244        if index.insert(svc.id(), i).is_some() {
245            return Err(internal_err(format!("duplicate service id `{}`", svc.id())));
246        }
247    }
248
249    // 邻接:dep → dependents(edge dep→node),并统计每个 node 的入度(= deps 数)。
250    let n = services.len();
251    let mut dependents: Vec<Vec<usize>> = vec![Vec::new(); n];
252    let mut indegree: Vec<usize> = vec![0usize; n];
253    for (i, svc) in services.iter().enumerate() {
254        for dep in svc.deps() {
255            let Some(&d) = index.get(dep) else {
256                return Err(internal_err(format!("service `{}` depends on unknown service `{dep}`", svc.id())));
257            };
258            dependents[d].push(i);
259            indegree[i] += 1;
260        }
261    }
262
263    // 入度为 0 的入队(按注册下标顺序,给出确定性输出)。
264    let mut queue: std::collections::VecDeque<usize> = (0..n).filter(|&i| indegree[i] == 0).collect();
265    let mut order: Vec<usize> = Vec::with_capacity(n);
266    while let Some(node) = queue.pop_front() {
267        order.push(node);
268        for &m in &dependents[node] {
269            indegree[m] -= 1;
270            if indegree[m] == 0 {
271                queue.push_back(m);
272            }
273        }
274    }
275
276    if order.len() != n {
277        // 未能排完 → 剩余节点构成至少一个环。
278        let cyclic: Vec<&str> = (0..n).filter(|&i| indegree[i] > 0).map(|i| services[i].id()).collect();
279        return Err(internal_err(format!("dependency cycle among services: {cyclic:?}")));
280    }
281
282    Ok(order)
283}
284
285/// 把 Supervisor 内部失败(依赖配置错误 / 环 / run 任务 panic)包成统一 `Error`。
286///
287/// `nagisa-types` 当前无「框架内部错误」专用变体;归入 `Action{kind: Internal}`,
288/// 经 [`Error::action`](retcode 取哨兵 `NON_PROTOCOL_RETCODE` = 非协议、属框架编排层)。
289fn internal_err(msg: String) -> Error {
290    Error::action(msg)
291}