生命周期:fiber 状态机
专栏:Cordis 插件框架 · 第 9 / 12 篇:::info 学习目标
完成本篇后你能够:画出 fiber 状态机并说出每次迁移的触发条件;解释 _reload 与 _unload 的实现;说出级联卸载是怎么”挂在父 fiber 的 effect 上”实现的。
前置:第 5、8 篇完成。预计时长:60 分钟。
:::
每个插件应用就是一个 fiber,FiberState(fiber.ts
// vendor/cordis/src/fiber.ts:147-154
export const enum FiberState {
PENDING, // 等待依赖服务
LOADING, // 插件回调正在执行
ACTIVE, // 加载完成,正在提供服务
FAILED, // 回调或配置抛错
DISPOSED, // 已移除,不可重启
UNLOADING, // 清理(disposers)正在执行
}
状态机全景
_reload:加载的完整序列
// vendor/cordis/src/fiber.ts:646-673(结构还原)
private async _reload() {
const config = resolveConfig(runtime, this._config) // ① schema 校验
...
this._updateState(() => { /* LOADING */ })
// ② 快照依赖实现到 fiber.store
// ③ 执行插件体:类 → new(含 initHooks/[init])
// 函数/对象 → callback(ctx, config)
// ④ apply 期间注册的每个 disposer 进入 _disposables
this._updateState(() => { /* ACTIVE */ })
}
注意①:配置校验发生在每次激活前——热重载改配置同样走校验,失败进 FAILED 而不是带错运行。
_unload:三条铁律的来源
// vendor/cordis/src/fiber.ts:675-696(节选)
private async _unload() {
await Promise.all(this._disposables.clear().map(async (dispose) => {
try {
... await runDisposable(dispose) // 逆序启动,异步并发等待
} catch (reason) {
this.ctx.logger.error(reason) // 单个清理失败不拖累其他
}
}))
this.store = undefined
this._updateState(() => {
if (this._epoch !== INACTIVE) {
this.inertia = this._reload() // 期间依赖/配置又变了 → 重新加载
return FiberState.LOADING
}
this.inertia = undefined
... // 否则 DISPOSED
})
}
三条铁律:逆序启动(后注册的清理先跑)、并发等待(Promise.all——异步清理互不阻塞,要求严格顺序的清理应放进同一个 disposer)、错误隔离(单个 disposer 抛错交 logger,不影响其他)。卸载结束时若 epoch 又变了(HMR/依赖恢复),直接进入下一轮 _reload——卸载即重载。
级联卸载:父 fiber 的 effect
子 fiber 的 dispose 是在构造时注册到父 fiber 的 effect 栈上的(fiber.ts 构造器:this.dispose = parent.fiber.effect(...))——父卸载时自动触发子的 dispose,子的 dispose 又触发孙子的……整棵插件树自底向上有序拆除。这就是”移除一个插件 = 移除它和它的全部子孙”的实现。
状态迁移的观测
每次迁移都 emit internal/status(fiber.ts _updateState)——dsh 的 runtime-diagnostics、健康检查、调试工具都订阅它。你在 dsh 控制台看到的插件状态面板,数据源就是这个事件流。
与其他模块的联系
- 依赖注入(第 5 篇):PENDING↔ACTIVE 的往返就是依赖门控;
- effect(第 8 篇):UNLOADING 消费的就是 effect 栈;
- HMR(第 11 篇):
fiber.update(config)走internal/updatewaterfall → restart(dispose 后立即_reload); - dsh:
agent-loop的加载/卸载、SyncChannelCache式的配置热更,全部建立在这套状态机上。
常见踩坑
- FAILED 后束手无策——修好配置后
fiber.update()或让 Loader 重载即可复活; - 在 disposer 里 await 其他 fiber 的方法——对方可能正在 UNLOADING,做好判空;
- PENDING 不占事件循环——没有活跃任务时进程会以 0 退出(“插件没输出就退出了”的最常见原因)。
随堂练习(带验收标准)
- 给插件加
internal/status监听,打印完整生命周期日志。验收:能观察到 PENDING→LOADING→ACTIVE 与卸载序列; - 构造”依赖消失又恢复”的场景(提供方 dispose 后重新 provide)。验收:消费方经历 ACTIVE→UNLOADING→LOADING→ACTIVE;
- 配置校验失败触发 FAILED。验收:错误信息包含 schema issue 列表。