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

7.3 KiB
Raw Blame History

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。依赖方向固定为:

runtime-core <- local-process <- local-execution-control <- local-run-recovery
                                            ^
                                            |
                                   local-application

local-run-recovery 与在线 lifecycle 共享同一个 LocalCompletionReceiptProcessorRun/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 写入 cancelRequestedAtMscancelReason=timeoutrun.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 扩展为第二十一个。