DeepSeek Harness: 插件生命周期:Fiber 状态机
最后更新:2026-08-31
每个插件在 Cordis 中不是一个静态的代码块,而是一个有生命周期的实体——Fiber。理解 Fiber 状态机,就是理解插件从"等待加载"到"运行中"到"优雅退出"的全过程,这是编写健壮插件的关键。
📋 前置知识:已完成 13-effect.md,理解自动清理与 ctx.effect()
1. 你将学到
- Fiber 概念:插件的状态容器
- 生命周期:pending → active → disposing → disposed
- Fiber 的创建与销毁
- 父子 Fiber 关系
- 错误处理与状态回退
- Fiber 上下文隔离
2. Fiber 概念
(1) 什么是 Fiber
Fiber 是 Cordis 对插件运行时状态的抽象——每个插件实例对应一个 Fiber 对象,记录插件当前所处的生命周期阶段。
interface Fiber {
id: string
name: string
state: FiberState
context: Context
parent: Fiber | null
children: Fiber[]
}
(2) 为什么需要 Fiber
没有 Fiber 时,插件只有两种状态:"加载"和"未加载"。但实际运行中:
- 依赖未就绪时,插件应该"等待"而非"失败"
- 卸载过程中,插件需要"清理中"而非直接消失
- 出错时,插件可能需要"重试"而非"死亡"
Fiber 为这些中间状态提供了明确的状态机模型。
(3) ▶ 示例 3
Fiber = 插件的"灵魂"(状态信息)
Context = 插件的"身体"(资源与环境)
每个 Fiber 持有一个 Context,Context 的生命周期与 Fiber 绑定。
3. 生命周期状态
(1) ▶ 示例 1
stateDiagram-v2
[*] --> pending: 插件注册
pending --> active: 依赖就绪 + apply 成功
pending --> errored: apply 失败
active --> disposing: 请求卸载
errored --> active: 重试成功
errored --> disposing: 放弃重试
disposing --> disposed: 清理完成
(2) 各状态含义
| 状态 | 含义 | ctx 可用 | 可逆 |
|---|---|---|---|
| pending | 等待依赖就绪 | 有限 | ✅ |
| active | 正常运行 | 完全 | ✅ |
| disposing | 正在清理 | 只读 | ❌ |
| disposed | 已销毁 | 不可用 | ❌ |
| errored | 启动失败 | 不可用 | ✅ |
(3) pending 状态
插件声明了 inject 但依赖尚未注册时,Fiber 进入 pending:
export const inject = ['tools']
// tools 服务尚未注册 → Fiber 处于 pending
// tools 注册完成 → Fiber 转为 active,调用 apply
pending 期间:
- 插件的 apply 尚未被调用
- ctx 上的依赖服务不可用
- 依赖就绪后自动转为 active
(4) active 状态
apply 成功执行后,Fiber 进入 active:
export function apply(ctx: Context) {
// 此时 Fiber 处于 active
ctx.logger.info('I am alive!')
ctx.on('session/created', (s) => {
// 可以正常使用所有服务
})
}
active 期间:
- 所有已声明的服务可用
- 可以注册新资源和监听器
- 可以响应事件和命令
(5) disposing 状态
收到卸载请求后,Fiber 进入 disposing,开始清理资源:
disposing 流程:
1. 停止接受新请求
2. 逆序执行 ctx.effect() 注册的清理函数
3. 自动移除事件监听器
4. 清除定时器
5. 注销命令和服务
(6) disposed 状态
所有资源清理完毕后,Fiber 进入 disposed:
- ctx 不再可用
- 任何对 ctx 的调用都会抛出错误
- Fiber 对象保留用于审计和日志
4. Fiber 的创建与销毁
(1) 创建时机
Fiber 在插件注册时自动创建:
// 框架内部逻辑(伪代码)
function registerPlugin(plugin: PluginDefinition) {
const fiber = new Fiber({
id: generateId(),
name: plugin.name,
state: hasDeps(plugin) ? 'pending' : 'active'
})
if (fiber.state === 'active') {
fiber.context = createContext(fiber)
plugin.apply(fiber.context)
}
}
(2) 销毁触发
Fiber 销毁由以下事件触发:
| 触发方式 | 说明 |
|---|---|
ctx.dispose() |
主动卸载 |
| 父 Fiber 销毁 | 级联卸载 |
| 配置变更重载 | 旧 Fiber 销毁,新 Fiber 创建 |
(3) ▶ 示例 3
graph TD
TRIGGER[卸载触发] --> STOP[停止新请求]
STOP --> CHILD[销毁子 Fiber]
CHILD --> CLEAN[执行清理函数]
CLEAN --> DISPOSED[标记 disposed]
5. 父子 Fiber 关系
(1) 层级结构
Fiber 支持父子关系,形成树形结构:
graph TD
ROOT[Root Fiber<br/>dsh-core] --> A[Plugin A Fiber]
ROOT --> B[Plugin B Fiber]
A --> A1[Sub-plugin A1]
A --> A2[Sub-plugin A2]
(2) 父子关系的建立
通过 ctx.plugin() 创建子 Fiber:
export function apply(ctx: Context) {
ctx.plugin({
name: 'sub-plugin',
apply(subCtx: Context) {
subCtx.logger.info('I am a child fiber')
}
})
}
(3) 级联销毁
父 Fiber 销毁时,所有子 Fiber 自动销毁:
卸载 Plugin A:
→ 先销毁 Sub-plugin A1
→ 再销毁 Sub-plugin A2
→ 最后销毁 Plugin A
这种"先子后父"的销毁顺序确保依赖关系不被破坏。
(4) 作用域继承
子 Fiber 继承父 Fiber 的 ctx:
// 父插件注册的服务,子插件可以直接使用
export function apply(ctx: Context) {
ctx.provide('parent-service', { ... })
ctx.plugin({
name: 'child',
inject: ['parent-service'],
apply(childCtx) {
childCtx['parent-service'] // ✅ 可访问父级服务
}
})
}
6. 错误处理与状态回退
(1) apply 失败
当 apply 抛出异常时,Fiber 进入 errored 状态:
export function apply(ctx: Context) {
throw new Error('initialization failed')
// Fiber → errored
}
(2) 自动重试
Cordis 会对 errored 状态的 Fiber 自动重试:
1st attempt: apply() → throw Error → errored
2nd attempt: (等待 1s) apply() → throw Error → errored
3rd attempt: (等待 2s) apply() → throw Error → errored
4th attempt: (等待 4s) apply() → success → active
重试间隔指数退避:1s → 2s → 4s → 8s → ... → 最大 60s。
(3) 重试策略配置
export const Config = Schema.object({
maxRetries: Schema.number().default(5).description('Max retry attempts'),
retryInterval: Schema.number().default(1000).description('Initial retry interval (ms)')
})
(4) 手动重试
ctx.on('fiber/errored', (fiber) => {
ctx.logger.warn(`plugin ${fiber.name} errored, retrying...`)
fiber.retry()
})
(5) 不可恢复的错误
某些错误不应重试:
export function apply(ctx: Context) {
if (!process.env.REQUIRED_VAR) {
// 配置错误,重试也没用
throw new NonRetryableError('REQUIRED_VAR is not set')
}
}
(6) 清理阶段的错误
disposing 阶段的错误不会阻止 Fiber 转为 disposed,但会被记录:
[warn] cleanup error in plugin my-plugin: Connection already closed
[info] plugin my-plugin disposed (with 1 cleanup warnings)
7. Fiber 上下文隔离
(1) 每个 Fiber 独立 ctx
const fiberA = new Fiber({ name: 'plugin-a' })
const fiberB = new Fiber({ name: 'plugin-b' })
fiberA.context !== fiberB.context // true
(2) 隔离边界
| 资源 | 是否隔离 | 说明 |
|---|---|---|
| 事件监听器 | ✅ | 各 Fiber 独立注册 |
| 定时器 | ✅ | 各 Fiber 独立清理 |
| 命令 | ⚠️ | 全局共享,但有作用域 |
| 服务 | ❌ | 全局共享 |
| 配置 | ✅ | 各插件独立 |
(3) 服务共享与隔离的平衡
服务是全局共享的——这是 Cordis 的设计决策。插件 A 注册的服务,插件 B 可以通过 inject 使用。如果需要隔离,使用 scope 机制(见 20-scope.md)。
(4) 状态查询
// 查询 Fiber 状态
ctx.fiber.state // 'active'
ctx.fiber.id // 'fiber-abc-123'
ctx.fiber.parent // parent Fiber or null
ctx.fiber.children // child Fiber[]
❓ 常见问题
ctx.plugin() 注册子插件时,框架自动创建 Fiber。typescript ctx.on('fiber/created', (fiber) => { ... }) ctx.on('fiber/active', (fiber) => { ... }) ctx.on('fiber/disposing', (fiber) => { ... }) ctx.on('fiber/disposed', (fiber) => { ... }) ctx.on('fiber/errored', (fiber) => { ... }) 📖 小节
- Fiber 是 Cordis 对插件运行时状态的抽象,每个插件实例对应一个 Fiber
- 生命周期:pending(等依赖)→ active(运行中)→ disposing(清理中)→ disposed(已销毁)
- 父子 Fiber 支持级联销毁,销毁顺序为"先子后父"
- apply 失败时 Fiber 进入 errored,自动指数退避重试
- 每个 Fiber 拥有独立的 Context,事件监听器和定时器隔离,服务全局共享
- 通过 Fiber 生命周期事件监控插件状态
📝 作业
1. ⭐ 基础题:编写一个插件,在 apply 中输出当前 Fiber 的 state 和 id。启动后观察日志,确认 Fiber 处于 active 状态。
2. ⭐⭐ 进阶题:编写一个插件,在 apply 中故意抛出错误(模拟初始化失败)。观察 Fiber 的 errored 状态和重试行为。然后修正错误,验证 Fiber 恢复到 active。
3. ⭐⭐⭐ 挑战题:创建一个父子 Fiber 结构:父插件注册一个服务,子插件通过 inject 使用该服务。卸载父插件,验证子插件也被级联销毁。在清理函数中输出销毁顺序,确认"先子后父"。