生命周期:fiber 状态机

专栏:Cordis 插件框架 · 第 9 / 12 篇
Cordis生命周期

:::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)正在执行
}

状态机全景

图表(cordis-fiber-lifecycle.md)

_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/update waterfall → restart(dispose 后立即 _reload);
  • dshagent-loop 的加载/卸载、SyncChannelCache 式的配置热更,全部建立在这套状态机上。

常见踩坑

  1. FAILED 后束手无策——修好配置后 fiber.update() 或让 Loader 重载即可复活;
  2. 在 disposer 里 await 其他 fiber 的方法——对方可能正在 UNLOADING,做好判空;
  3. PENDING 不占事件循环——没有活跃任务时进程会以 0 退出(“插件没输出就退出了”的最常见原因)。

随堂练习(带验收标准)

  1. 给插件加 internal/status 监听,打印完整生命周期日志。验收:能观察到 PENDING→LOADING→ACTIVE 与卸载序列;
  2. 构造”依赖消失又恢复”的场景(提供方 dispose 后重新 provide)。验收:消费方经历 ACTIVE→UNLOADING→LOADING→ACTIVE;
  3. 配置校验失败触发 FAILED。验收:错误信息包含 schema issue 列表。

← 返回文章列表