插件开发范式

生命周期与资源清理

使用 Core 自带的 effects 管理初始化、失败回滚、停止与后台任务。

ctx.effects 随 Context 提供,不需要安装 Host 服务。插件声明“资源由谁持有、什么时候释放”,框架按依赖顺序启动,并在失败、停止或替换时清理。

新增连接/订阅先读资源边界;选择 cleanup API 看effects;排查失败看init 职责与失败范围。

Generation 是资源所有权边界

一次插件运行称为一个 generation,表示这一次运行中的实例和资源。停止、重启、热替换或可选依赖变化都会结束旧的一次运行。

创建资源后立即登记清理。这样后续初始化失败或插件停止时,框架仍能释放已创建的部分:

protected override async init(signal: AbortSignal) {
	const client = createClient(this.config)
	this.ctx.effects.defer(() => client.close(), { tag: 'client' })

	await client.connect({ signal })
}

选择 effects primitive

需求API
登记一个 cleanup functioneffects.defer(cleanup)
持有带 dispose() 的对象effects.own(disposable)
成对 acquire/releaseeffects.acquire(acquire, release)
给简单 helper 单独建立子作用域effects.scope(meta)
自动派生 Context/config/scopethis.parts.use(CachePart)
一组登记要么全部提交、要么回滚effects.transaction()
protected override init() {
	const scope = this.ctx.effects.scope({ tag: 'sync-loop' })
	const loop = new SyncLoop(this.ctx.logger.with({ component: 'sync-loop' }))
	scope.own(loop)
	loop.start()
}

effects.transaction(async tx => ...) 只回滚本事务登记的资源。事务中通过 tx 登记并 await acquire;嵌套使用 tx.transaction(),同级并发会拒绝。callback 结束后 tx 不再可用;tx.dispose() 只撤回本事务,不关闭父 scope。未等待的 acquire 若晚到,会释放资源并 reject;父 scope 的 dispose 不代表这些未登记 Promise 已退出。

cleanup 必须幂等,并在 Promise resolve 前真正停止底层工作。只调用 abort() 却不等待 worker、watcher 或 queue consumer 退出,会让旧 generation 与新 generation 重叠。

init() 的职责

校验必要上游与凭据,注册能力,启动长期资源。无法提供能力时直接抛错,不捕获后只写日志继续运行。 init() 可以返回 cleanup/disposable;有多步资源获取时,在每次获取成功后立即登记,确保部分初始化失败也能清理。

调用失败和生命周期失败

两者影响范围不同:

  • 插件无法提供能力:让 init() 失败,或由宿主执行 restart/replacement;
  • 单个 HTTP/command 调用参数错误或上游超时:返回请求级错误,不改变 Plugin 状态;
  • timer、watcher、queue consumer 的单次失败:记录结构化错误,按领域规则重试、暂停或触发明确 shutdown。

后台循环需要同时处理“停止调度”和“等待在途任务退出”。clearInterval() 只阻止后续 tick,不能取消或等待已经开始的异步调用;会重叠的任务应明确并发策略,清理时发出取消信号并等待任务结束。具体组合见异步任务契约。

事件订阅已有 owner cleanup,见事件。只有需要 Context、局部配置和作用域时才拆 Part;普通 helper 不必成为 Part。

验证初始化中途失败和正常停止两条路径,确认已获取资源释放、后台工作真正退出。具体测试接线见隔离测试。

最后更新于

本页目录