feat(worker): IO Worker 分道(WK-1)

io worker 进程内按任务声明的 lane 分文件 / 网络 / 维护三条道,各自并发(缺省 2/4/1)、
各自排队;维护道在文件道或网络道有排队/运行时让路(连续 10 分钟放行一个),调度器内联的
清理/回填/保留期裁剪也走维护道。并发在 设置 → 自动化 → 高级 热生效,env 覆盖;旧
WORKER_CONCURRENCY 映射到网络道。运行状态页按道显示运行中/并发/排队。归属表写进
docs/runbook/dev.md,ci 与注册表逐项核对。

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-30 00:24:21 +08:00
1 parent 68f53390f6
commit da33cad81d
38 files changed
+1240 -105

No files matched your search

@@ -48,6 +48,12 @@ export interface ScheduledJobDef {
/** 稳定 key(表主键),如 feeds.poll */
key: string;
kind?: ScheduleKind;
/**
* 执行道(WK-1):"maintenance" = 在调度器里内联干重活的维护任务(清理、回填、保留期裁剪),
* 与 io worker 的维护道共用并发、文件道 / 网络道有活时让路(立即执行不让路)。
* 省略 = 轻量调度(只入队任务或很快结束),任务本身按它的 lane 执行。
*/
lane?: "maintenance";
defaults: ScheduledJobDefaults;
run: JobRunFn;
}
+12 -1
View File
@@ -2,6 +2,7 @@ import { SCHEDULE_CHANGED_EVENT } from "@umt/contracts";
import { type DB, type Kysely, type ScheduledJobRow, sql } from "@umt/db";
import type { Logger } from "pino";
import type { EventBusPort } from "../../http/sse.ts";
import { laneControllerOf } from "../../worker/lanes.ts";
import type { ServerContext } from "../types.ts";
import { runAgentCheck } from "./agent-checks.ts";
import type { JobRunFn, JobRunResult, ScheduleRegistry } from "./registry.ts";
@@ -158,10 +159,20 @@ export class Scheduler {
.limit(50)
.execute();
const started: string[] = [];
const lanes = laneControllerOf(this.ctx);
for (const job of due) {
const fn = this.runFor(job);
if (!fn) continue;
const p = this.execute(job, fn).finally(() => this.inflight.delete(job.id));
// 内联维护任务进维护道:占不到并发或正在让路就留到下一轮(不改 next_run_at);立即执行不让路
let release: (() => void) | null = null;
if (lanes && this.registry.get(job.id)?.lane === "maintenance") {
release = await lanes.enterMaintenance(job.run_requested_at !== null);
if (!release) continue;
}
const p = this.execute(job, fn).finally(() => {
release?.();
this.inflight.delete(job.id);
});
this.inflight.set(job.id, p);
started.push(job.id);
}