DeepSeek Harness 插件生命周期(Fiber与可逆副作用)
在 Cordis 微内核中,每一次插件加载都会创建一个独立的 Fiber(生命周期控制器),用来管理插件实例的完整状态流转、依赖等待、资源注册与销毁清理。
Harness 依靠 Fiber 状态机 + ctx.effect 可逆副作用栈,实现依赖驱动自动启停、热重载、自动资源回收,从架构层面杜绝内存泄漏、残留定时器、僵尸事件监听器。
本文基于官方文档讲解完整生命周期状态、inject依赖等待、effect自动清理、子Fiber嵌套、HMR热重载原理与开发避坑。
一、Fiber:插件实例的生命周期控制器
插件代码本身是静态模块;Fiber代表一次运行时加载实例。同一个插件可以产生多个Fiber实例(如不同会话隔离域),每个Fiber拥有独立状态、独立副作用栈、独立子插件树。
Fiber完整状态流转
[ PENDING ] ──(依赖全部就绪)──> [ LOADING ] ──(apply执行成功)──> [ ACTIVE ] │ │ │ (依赖缺失挂起) (apply抛异常) (卸载/热重载触发) │ ▼ ▼ └──────────────────────────> [ FAILED ] [ UNLOADING ] │ (全部资源清理完成) ▼ [ DISPOSED ]
| 状态 | 说明 |
|---|---|
PENDING | 插件已注册,但 inject 声明的前置服务尚未就绪,处于等待挂起,不会执行 apply。不是错误,属于正常等待状态。 |
LOADING | 依赖全部满足,内核正在执行插件入口 apply(ctx,config),收集副作用。 |
ACTIVE | 插件正常运行,工具、事件监听、服务全部对外提供能力。 |
FAILED | apply 执行抛出异常;内核隔离故障,已经回滚已注册副作用,不影响其他插件与主进程。 |
UNLOADING | 开始卸载,逆序执行所有effect清理处置器disposer,释放资源。 |
DISPOSED | 插件实例、全部子Fiber、所有资源彻底释放完毕,该实例生命周期终结。 |
二、inject:依赖驱动自动加载与自愈
插件通过导出 inject 数组声明强依赖服务。只有全部被依赖服务就绪,Fiber才会离开PENDING进入LOADING并执行apply。
import type { Context } from '@deepseek‑ai/cordis';export const name = 'my‑advanced‑feature';
// 声明强依赖:必须等待 tools、llm 服务就绪
export const inject = ['tools', 'llm'];
export function apply(ctx: Context) {
// 只有依赖全部就绪,才会走到这里
ctx.logger.info('依赖就绪,插件激活');
}
动态自愈特性(非常关键)
运行时,如果插件依赖的服务被卸载/替换:
- 下游依赖该服务的插件Fiber自动从ACTIVE进入UNLOADING→DISPOSED,安全卸载;
- 当新版本的提供方服务重新就绪后,Fiber自动重新加载、再次进入ACTIVE;
该机制实现服务替换时下游插件自动适配,不需要手动重启插件。
三、ctx.effect 可逆副作用与自动清理
Fiber内部维护一个副作用栈DisposableList。所有注册的副作用都会入栈;卸载时逆序调用对应的清理函数,实现自动撤销,防止内存泄漏。
下面这些API内部已经封装effect,自动托管副作用,不需要手动写清理函数:
ctx.on()事件监听;插件卸载自动解绑监听器ctx.tools.register()注册工具;卸载自动注销工具ctx.llm.registerAdapter()注册模型适配器;卸载自动注销适配器
对于定时器、数据库连接池、TCP句柄等外部原生资源,必须手动使用 ctx.effect(),返回异步/同步disposer清理函数。
import type { Context } from '@deepseek‑ai/cordis';export function apply(ctx: Context) {
// 1. 事件监听自动托管,无需手动解绑
ctx.on('trajectory/step', (step) => {
ctx.logger.info('执行步骤', step.index);
});
// 2. 自定义外部资源,使用 ctx.effect
ctx.effect(() => {
ctx.logger.info('初始化数据库连接池');
const pool = createDatabasePool();
// 返回清理函数:插件卸载时自动执行
return async () => {
ctx.logger.info('关闭数据库连接池');
await pool.end();
};
});
}
⚠️重要:卸载时多个effect的disposer会并发执行Promise.all。如果清理操作存在严格串行先后顺序,不能拆分成多个独立ctx.effect,需要写在同一个effect返回的清理函数内部,手动await串行控制流程。
四、嵌套子Fiber:ctx.plugin() 创建子插件实例
在插件内部可以调用 ctx.plugin(subPlugin) 动态创建子Fiber。子插件继承父上下文的服务,但是拥有独立生命周期与独立副作用栈。
父Fiber卸载的时候,会递归dispose所有子Fiber,级联释放全部子资源。也可以手动调用返回fiber对象的 fiber.dispose() 提前销毁子插件。
import type { Context } from '@deepseek‑ai/cordis';
import { subWorkerPlugin } from './sub‑worker';export function apply(ctx: Context) {
// 动态派生子Fiber
const fiber = ctx.plugin(subWorkerPlugin);
// 监听会话结束,手动提前释放子插件
ctx.on('session/finished', async () => {
ctx.logger.info('会话结束,销毁子任务');
await fiber.dispose();
});
}
await fiber.dispose()会做三件事:
- 撤销该Fiber下全部effect副作用;
- 递归销毁所有嵌套子Fiber;
- Promise等待全部异步清理完成后再resolve。
五、HMR热模块替换底层原理
启用插件 @deepseek‑ai/cordis‑plugin‑hmr 开启热重载。修改插件源码后,HMR执行三步原子操作:
- 卸载旧Fiber:逆序执行全部disposer,旧实例资源完整释放;
- 重新导入新版插件代码、读取最新配置;
- 创建新Fiber实例,执行新版apply,完成激活。
依靠effect可逆副作用,热重载不会残留旧定时器、旧监听器、旧工具注册项,不会内存泄漏。
六、常见问题FAQ
Q:apply函数抛出异常,会不会让整个Harness进程崩溃?
A:不会。Cordis捕获apply执行异常,当前Fiber进入FAILED状态,已经注册的副作用会被全部回滚撤销;其他插件与主进程不受任何影响。
Q:多个ctx.effect的清理函数有先后依赖,直接分开写会发生什么?
A:多个disposer并发执行,不保证完成顺序。必须串行的清理逻辑放到同一个effect返回函数内,内部手动await串行执行。
Q:父插件卸载,动态创建的子Fiber会被释放吗?
A:会自动递归dispose全部子Fiber,不需要手动保存调用dispose。
Q:依赖的服务被热更新替换,下游插件发生什么?
A:下游插件Fiber自动DISPOSED;当新的服务提供方就绪,Fiber自动重新LOADING→ACTIVE,实现自愈重激活。
Q:为什么我卸载插件,定时器还在跑?
A:定时器写在apply外部全局作用域,没有被ctx.effect托管。所有副作用必须在apply内部,通过ctx托管注册。
七、开发最佳实践
- 所有副作用(定时器、监听、连接池)全部写在apply函数内部;禁止全局裸写setInterval、addEventListener,脱离Fiber管理。
- 优先使用ctx.on / ctx.tools.register这类内置effect托管API;外部资源显式使用ctx.effect,返回清理函数。
- 清理逻辑有严格串行顺序时,合并到同一个ctx.effect内部,不要拆成多个effect。
- 利用inject声明强依赖,交给内核做拓扑排序,不要手写启动顺序。
- 子插件使用ctx.plugin()创建;父销毁自动级联清理子Fiber。
- 不要手动修改Fiber内部状态,全部通过内核机制(配置变更、reload、dispose)驱动状态流转。
本篇小结
Fiber是插件运行时实例控制器,拥有完整状态机PENDING→LOADING→ACTIVE→UNLOADING→DISPOSED/FAILED,结合inject实现依赖驱动自动等待与自愈重加载。
ctx.effect实现可逆副作用栈,所有注册行为入栈,卸载逆序清理资源,从架构上解决内存泄漏。
子Fiber嵌套、HMR热重载全部构建在这套生命周期体系之上。理解Fiber与effect,才能写出支持热重载、无残留、生产级稳定的Harness插件。
0 条笔记