Files
task_pilot/docs/decisions/0007-runtime-reliability.md
T
CNWei 815258c19b feat: 加固运行期可靠性并补齐调度与观测接口
- 运行句柄按 run_id 归属,修复取消后重跑时闸门被旧运行删除的竞态
- 调度循环改用取消令牌退出,启动时对账遗留运行,校验 rows_affected
- 三类任务统一参与调度,新增 /api/v1/stats 与 /api/v1/schedule
- SQLite 启用 WAL 与 busy_timeout,日志清理跳过活跃文件,单行上限 256 KiB
- 前端缓存管理令牌并在 403 时重试一次,仪表盘改用统计接口
2026-10-26 00:38:16 +08:00

42 lines
3.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADR-0007:运行期可靠性优化
- 状态:已接受
- 日期:2026-10-25
## 背景
代码评审与端到端自检暴露出五类问题,都会让"看起来正常"的功能在边界条件下失真:
1. 取消任务时执行器直接删除 `running` 句柄,而旧运行的收尾逻辑又无条件删除同任务句柄。取消后立刻重跑时,旧运行会把新运行的闸门删掉,跳过策略失效,同一任务可能并发执行。
2. 调度器用 `JoinHandle::abort()` 取消循环。若恰好卡在 `executor.trigger()` 的数据库 await 中,`running` 表项和 `queued` 运行会永久残留,该任务此后永远被判定为"运行中"并跳过。
3. 进程被强杀后,数据库里的 `queued`/`running` 运行没有对账,前端会一直显示"运行中"并持续轮询。
4. `mark_running`/`complete` 不检查 `rows_affected`,运行记录被级联删除后收尾静默失效。
5. 调度器只注册脚本任务,HTTP 与进程任务"启用"后返回 200 却永远不会被定时触发。
## 决策
1. 句柄带运行 ID,收尾使用 `remove_running_if(task_id, run_id)`:只有仍属于本次运行的句柄才会被移除;取消时依旧立即释放闸门,允许马上重跑。
2. 调度循环只用 `CancellationToken` 退出,不再 `abort`;`ScheduledTask` 的 `JoinHandle` 仅用于保活。
3. 启动时执行 `fail_interrupted_runs`:把遗留的 `queued`/`running` 统一改为 `cancelled`,并写入"进程重启,运行被中断"。
4. `mark_running`/`complete` 返回是否更新成功,执行器对 0 行更新记警告并同步清理内存句柄。
5. 三类 Action 统一参与调度:`list_enabled_tasks` 不再按 `action_type` 过滤,`Scheduler::refresh` 只判断 `enabled`。
6. SQLite 启用 WAL + `busy_timeout(5s)` + `synchronous=NORMAL`,避免执行器写日志时前端查询撞上 `database is locked`。
7. 日志清理跳过正在写入的文件;单行输出上限 256 KiB,超出后截断并继续读行尾。
8. 请求体上限 2 MiB;新增 `/api/v1/stats`(一次查询拿到全部计数)与 `/api/v1/schedule`(下次触发时间)。
9. 前端缓存会话令牌,写请求收到 403 时丢弃缓存并重试一次(覆盖后端重启轮换令牌)。
10. Python 任务默认注入 `PYTHONIOENCODING=utf-8`(用户显式设置优先),`PYTHONUNBUFFERED=1` 保持强制。
## 原因
- 句柄竞态与 `abort` 泄漏都会造成"任务不再执行"且无任何报错,是排障成本最高的一类缺陷。
- 对账让崩溃恢复变成确定行为,前端轮询也能正常收敛。
- 统一调度消除了"启用但永不触发"的假能力。
- 统计与调度接口把仪表盘从 4 次请求降到 2 次,并让"下次执行时间"成为可见信息。
## 边界
- 取消后立即可重跑,意味着旧进程的终止与新进程的启动可能短暂重叠(取决于 Job Object 终止速度)。
- 没有全局并发上限:多个任务在同一时刻触发会各自启动一个进程。
- 对账只针对 `queued`/`running`,不会尝试恢复重试或补偿错过的调度。
- 单行截断后该行内容不完整,日志里会多一条 `[system]` 说明。