Files
qinglong/docs/adr/ADR-0072-unified-local-execution-completion-and-control-lifecycle.md
T

95 lines
7.3 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-0072:统一的本机执行完成、取消、超时与停机生命周期
- 状态:Proposed
- 日期:2026-07-20
- 关联:D-18、D-21、D-65、D-67 至 D-70、ADR-0005、ADR-0007、ADR-0066、ADR-0068 至 ADR-0071
> ADR-0087 现行增量:本文 control lifecycle 已迁入 `@qinglong/local-execution/control``/recovery` 可单向消费其 completion processor,其他 subpath 不得反向导入 control/recovery。
## 上下文
ADR-0068 至 ADR-0071 已分别建立启动恢复、持久化进程、原子启动协调和有界调度,但完成 Promise、启动恢复回执、取消、Attempt deadline、回执清理和应用停止仍可能由不同 lifecycle 推进。若每条路径各自实现终态映射或拥有 timer,会产生迟到成功覆盖取消、同一 Attempt 重复完成、停机后孤儿进程,以及低配路由器空闲唤醒和内存状态随任务数增长的问题。
## 决策
### 1. 新增独立 Profile-neutral 控制包
新增 `@qinglong/local-execution-control`。生产只依赖 `@qinglong/runtime-core``@qinglong/local-process`,不得导入 SQLite adapter、Profile importer、legacy、cluster、ORM 或 HTTP。依赖方向固定为:
```text
runtime-core <- local-process <- local-execution-control <- local-run-recovery
^
|
local-application
```
local-run-recovery 与在线 lifecycle 共享同一个 `LocalCompletionReceiptProcessor`Run/Attempt 的成功、失败、取消和超时终态只能由该处理器或同包内的控制协调器提交,不再保留两套回执认证和终态事务实现。
### 2. 完成回执是唯一正常完成事实
dispatcher 不向 application stack 返回进程 completion Promise。它只在 Promise settle 后向组合根发送 Attempt ID;通知采用最多 64 个 identity 的进程内合并集合,不携带 token、exit code、命令或日志内容,也不是耐久事实。通知丢失时,周期性数据库候选与启动恢复仍能收敛。
处理器先从唯一 RunRepository 重读 Attempt/Run,再读取 exact receipt,并校验 Run ID、Attempt ID、callback sequence、token digest 和时间边界。可信回执在一个 Repository transaction 中按 Attempt Event、Run Event 顺序 CAS 终态;终态提交后才删除 receipt 并解除 journal。已终态 Attempt 的同一或下一 callback sequence 回执只做认证和清理,不得改写终态。非法回执必须先保存 quarantine intent,再移动文件。
### 3. deadline 先转为 durable cancellation intent
SQLite source 只使用已有 cancellation/deadline 索引,以 `(dueAtMs, kind, attemptId)` 稳定 keyset 返回最多 64 条候选。deadline 到期后,协调器先 CAS 写入 `cancelRequestedAtMs``cancelReason=timeout``run.cancel_requested`,随后才能停止进程;数据库失败不得 signal。
所有取消先尝试消费可信 completion。仍 active 时,只能把最新 runtime-owned local-process Attempt 的完整 durable handle 交给 `LocalProcessController`;禁止裸 PID signal。`claimed` 可直接终结,`starting/running` 只有 exact stop 返回 `stopped/already_exited` 或可信回执时才能终结。unknown、identity/provider 不可用和 stop timeout 保持待收敛,不能猜测退出。
### 4. application 只拥有一个周期 timer
`LocalExecutionControlLifecycle` 同时驱动完成通知、deadline/cancellation 单页扫描和低频 receipt cleanup。每个 application 只有一个 `unref` 周期 timer,没有每任务 timer、watcher、目录扫描或无界队列:
| Profile | 控制周期 | 控制页 | cleanup 周期 | cleanup 页 | drain 页预算 |
| --- | ---: | ---: | ---: | ---: | ---: |
| edge | 5 秒 | 4 | 5 分钟 | 8 | 2 |
| standalone | 1 秒 | 32 | 1 分钟 | 32 | 8 |
每次工作串行且不重叠;页、通知、停止等待和 drain 页数均有硬上限。cluster-control/worker 不装配该本机 lifecycle,其远端 Session/Lease/Attestation 继续使用独立 authority。
### 5. 停机顺序包含 execution drain
application 停止顺序固定为 admission close/drain → local execution stop/drain → injected stack stop → storage close。execution drain 只读取有界 active keyset,为尚无取消意图的 Attempt 先提交 `shutdown` cancellation,再按 exact handle 停止并终态化。已有 user/policy/timeout 意图必须保留,不能被 shutdown 覆盖。生命周期 stop 自身幂等;页预算、stop 证据或时间预算未收敛时返回 `timed_out`,但仍继续关闭后续 owner。
## 替代方案
1. **每个任务一个 timeout timer**:任务数直接决定 timer/closure 数和停机竞态,拒绝。
2. **Promise settle 直接写终态**:内存事实无法跨重启,且绕过 receipt capability,拒绝。
3. **取消先 signal 后写数据库**:数据库失败后无法证明意图与审计,拒绝。
4. **扫描 receipt 目录发现完成**:空闲和大目录成本不可控,也无法证明 active authority,拒绝。
5. **复用 cluster CancellationDispatch/Lease**:让路由设备携带多副本 authority 和驱动,拒绝。
6. **停机直接关闭 SQLite**:仍运行的进程失去控制面与终态收敛路径,拒绝。
## 影响与未完成项
已完成:
- 共享完成处理器、回执认证、原子 Attempt/Run 双终态和迟到回执幂等清理;
- SQLite cancellation/deadline/active stable keyset source,无新 migration 或第二连接;
- deadline→durable timeout intent、user/policy/shutdown cancellation 与 exact process stop
- dispatcher 内部完成通知、单 timer Profile lifecycle 和 application admission-first shutdown drain
- 启动恢复复用同一个完成处理器;
- 真实 SQLite completion、deadline、user cancellation、shutdown drain、通知合并和幂等停止测试。
仍未完成:
- retry policy 的产品入口与安全重试协调;
- 加密 Secret provider、Artifact retention/read API 与可观测指标/告警;
- target executable、systemd/Docker/s6 controller 和 2.x cutover 产品流程;
- Linux x64/arm64、PID namespace、强杀/断电/ENOSPC 与固定物理路由器实机门禁;
- PostgreSQL/Remote Worker completion、expiry、cancellation 和 retry 对等生命周期。
因此本 ADR 关闭本机单次执行的 completion/cancellation/timeout/shutdown 纵向缺口,但不表示整个 3.0 target 已可默认接管存量任务。
## 验证
1. completion receipt 认证后原子写入 Attempt/Run 双终态和顺序事件,清理发生在提交之后。
2. cancellation 与 deadline 在 signal 前已有 durable intent;数据库失败、无 exact handle 或 stop unknown 时不写假终态。
3. due source 使用既有索引、稳定 keyset、最多 64 条;edge/standalone 只读取 4/32 条。
4. shutdown drain 先写 `shutdown`,保留已有 reason,并在 storage close 前停止 exact active execution。
5. dispatcher 不暴露 completion Promise,重复完成通知只消费一次 identity。
6. startup recovery 与在线 completion 使用同一处理器,迟到回执不能覆盖 cancelled/timed_out。
7. lifecycle 只拥有一个周期 timer、无任务级 timer/watcherstop 调用幂等且有硬超时。
8. 本 ADR 落地时 dependency/source/lock、Profile vulnerability 和 application artifact 门禁包含第二十个 importerADR-0073 已将 local-secret 扩展为第二十一个。