模型: claude-opus-4-6 (anthropic/claude-opus-4-6)
生成日期: 2026-02-17
除了事件驱动的实时通信,OpenCode 还需要一些周期性的后台任务——例如定时清理过期的快照数据、删除过时的工具输出文件。这些任务不需要立即执行,但需要定期运行以维护系统健康。Scheduler 模块为此提供了一个轻量级的定时任务调度器。
11.3.1 Scheduler.register()——任务注册
Scheduler 的完整实现只有 62 行代码,但涵盖了任务调度的核心功能:
// scheduler/index.ts
export namespace Scheduler {
const log = Log.create({ service: "scheduler" })
export type Task = {
id: string // 任务唯一标识
interval: number // 执行间隔(毫秒)
run: () => Promise<void> // 任务执行函数
scope?: "instance" | "global" // 作用域
}
export function register(task: Task) {
const scope = task.scope ?? "instance"
const entry = scope === "global" ? shared : state()
// 全局任务只注册一次
const current = entry.timers.get(task.id)
if (current && scope === "global") return
// 实例任务则替换旧的定时器
if (current) clearInterval(current)
entry.tasks.set(task.id, task)
// 立即执行一次
void run(task)
// 设置周期定时器
const timer = setInterval(() => {
void run(task)
}, task.interval)
timer.unref() // 关键:不阻止进程退出
entry.timers.set(task.id, timer)
}
}
register() 的行为有以下特点:
1. 即时首次执行:任务注册后会立即执行一次(void run(task)),而不是等到第一个 interval 周期结束。这确保了初始清理在系统启动时就执行。
2. 幂等注册:对于全局任务,如果已经注册过相同 ID 的任务,register() 会直接返回(if (current && scope === "global") return)。对于实例任务,则会清除旧定时器并重新注册。
3. timer.unref():这是 Node.js 中一个重要的 API。默认情况下,活跃的定时器会阻止 Node.js 进程退出——进程会一直等待,直到所有定时器被清除。unref() 告诉运行时"这个定时器不重要,不要因为它而保持进程存活"。这对于 CLI 工具尤为重要——当用户按 Ctrl+C 退出时,清理任务的定时器不应该阻止进程终止。
衍生解释:timer.unref() 与事件循环
Node.js(和 Bun)的事件循环(Event Loop)会持续运行,只要还有"待处理的异步操作"——包括活跃的定时器、未完成的 I/O、待处理的 Promise 等。当所有操作完成后,事件循环退出,进程终止。
setInterval() 创建的定时器默认会让事件循环保持活跃。如果一个定时器每小时执行一次,那么进程会至少运行一个小时才会退出。
timer.unref() 标记这个定时器为"不重要的"——事件循环在决定是否退出时会忽略它。如果除了 unref() 的定时器之外没有其他待处理的操作,进程可以立即退出。
11.3.2 实例级(instance)vs 全局级(global)任务
Scheduler 支持两种作用域,对应不同的存储位置和生命周期:
Instance 销毁时自动 clearInterval
选择哪种作用域取决于任务的性质:
Instance 级适用于与特定项目绑定的任务(如快照清理——每个项目有独立的快照仓库)。
Global 级适用于系统级的公共任务(如工具输出清理——所有项目共享同一个输出目录)。
11.3.3 错误处理:静默容错
任务执行函数被一个 catch 包裹,确保单次执行失败不会影响后续调度:
这种"记录错误但继续运行"的策略适合清理类任务——如果某次快照清理因为磁盘临时不可用而失败,一小时后再试很可能就成功了。
截至当前版本,Scheduler 有两个实际注册的任务:
快照清理(Instance 级)
快照清理使用 git gc --prune=7.days 清除超过 7 天的 Git 对象。这个任务是 Instance 级的,因为每个项目有独立的快照仓库(参见 10.1 节),需要各自清理。
工具输出清理(Global 级)
工具输出清理扫描 ~/.local/share/opencode/tool-output/ 目录,删除超过 7 天的输出文件。回顾第 5 章,当工具的输出超过 2000 行或 50KB 时,完整输出会被保存到文件中,并向 LLM 返回截断版本和文件路径。这些临时文件需要定期清理。
这个任务是 Global 级的——所有项目的工具输出都存储在同一个目录中,只需要一个清理任务即可。
Scheduler 模块以极简的代码量(62 行)实现了一个功能完备的定时任务调度器:
setInterval(fn, interval)
Instance.state() 销毁回调中 clearInterval
与更复杂的任务调度框架(如 node-cron)相比,Scheduler 没有 cron 表达式、优先级队列、依赖关系等高级功能。但对于 OpenCode 当前的需求——两个每小时执行一次的清理任务——这种极简设计正好足够,没有引入不必要的复杂性。这体现了 OpenCode 代码库中反复出现的设计哲学:够用即可,不过度设计。