Files
qinglong/docs/adr/ADR-0228-bounded-plugin-package-workflow-cancellation-convergence.md
T

7.9 KiB
Raw Blame History

ADR-0228:有界 Plugin Package Workflow 整体取消收敛

  • 状态:Proposed
  • 日期:2026-07-30
  • 关联 RFCQL-RFC-0001 D-18、D-19、D-71、D-117、D-207、D-212、D-213
  • 关联 ADRADR-0072、ADR-0118、ADR-0121、ADR-0226、ADR-0227

背景

Workflow Task 已有 generation-bound admission、StepRun-aware activation/completion 和专用 recovery,但普通 Run cancellation convergence 一次只处理一个 Run 与最新 Attempt。Workflow aggregate 则以同一个 Run 承载最多 128 个 StepRun 和多个 Task Attempt;直接复用普通逻辑会出现三类错误:

  1. 父 Run 被提前写为 terminal,而在途 Worker 仍有 execution authority
  2. pending/ready StepRun 继续被 frontier 或 Task admission 推进;
  3. 只终结“最新 Attempt”,其余 StepRun 永久不能收敛。

同时,不能为每个 Workflow 或 Step 增加 timer、watcher、child Run、额外 claim 表或 workspace package。Edge/Standalone 的 SQLite 单连接与 Cluster 的 PostgreSQL 多副本 必须共享同一个领域裁决,但采用各自合适的锁和分页策略。

决策

1. 取消意图只属于父 Workflow Run

cancel_requested_at_mscancel_reason 继续写在 aggregate Run。收到意图后:

  • Workflow frontier 和 Task Attempt candidate 都必须排除该 Run
  • 不再产生新的 ready StepRun 或 Task Attempt
  • 已 durable 的 Task Attempt 仍由原 activation/completion/recovery authority 收敛, cancellation reconciler 不能伪造 Worker 已停止。

2. 使用一个 profile-neutral 纯状态机

runtime-core/plugin-package-workflow-cancellation-convergence 接收 exact Run、 全部 StepRun、immutable admission-bound active Task Attempt、lease 状态和观察时间, 确定性输出:

  • 可安全终结的 claimed、尚未执行 Attempt transition 与 Event
  • non-executing StepRun mutation/Event
  • 仍持有执行 authority 的 blocked Attempt/StepRun identity
  • 仅当 projected StepRun 全部 terminal 时才生成父 Workflow terminal transition。

状态机不读取数据库、不持有连接、不创建 timer,也不决定执行器 stop。输出 ID 由 Run、取消时间、target identity 和 durable epoch 生成,最长 36 字符,可在未知提交 结果后重算。

3. start barrier 不能被取消协调器越过

  • claimed 且没有 active lease 的 Task Attempt 可以在 exact admitted ready epoch 直接收敛为 cancelled|timed_out
  • claimed + leased、starting、running 一律 blocked,等待 Worker completion 或可信 recovery
  • running StepRun 即使缺少当前 adapter 可见的 Task Attempt,也保持 blocked,不能写 假终态;
  • pending/lost StepRun 在 aggregate timeout 下写 cancelled,因为它们没有实际 执行超时;ready/waiting_approval 可写 timed_out
  • 用户、Policy、shutdown、reconcile 取消统一映射为 cancelled;父 Run 的 timeout 映射为 timed_out

4. 父 Run 最后终结

每个可安全 Attempt Event、StepRun mutation/Event 都先消耗连续 aggregate version/event sequence。只有所有 StepRun projected terminal 且 blocked 集合为空时, 才追加 workflow.cancelled|workflow.timed_out 并终结父 Run。部分收敛后父 Run 保持 running 和原 cancel intent,后续 caller-driven cycle 继续处理。

一个 Workflow 可以在一页中结算多个 Attempt,因此共享 page result 的 settledAttempts 上界从 settledRuns 修正为 scanned * MAX_STEP_RUNS_PER_RUNsettledRunsblocked 仍以 aggregate Run 计数。

5. PostgreSQL 使用既有 Attempt authority 锁顺序

Cluster adapter 留在 @qinglong/cluster-postgres 的显式 subpath/既有 cancellation repository

  1. 普通非 Workflow Run 保留原 SKIP LOCKED 收敛;
  2. Workflow candidate 不先锁 Run
  3. 先稳定排序 active Attempt ID,并取得 pg_try_advisory_xact_lock
  4. 再锁父 Run、active Attempt、lease 和全部 StepRun,重新读取 immutable admission
  5. 在一个短事务中 CAS Attempt/StepRun/Run,并追加全部 RunEvent 与 StepRunMutation。

这保持 activation/completion/recovery 的 Attempt authority → Run/Attempt/Step 固定锁序。锁竞争返回 blocked,不以扩大 timeout 或交换锁序处理。

6. SQLite 使用每 Workflow 一个短事务

Edge/Standalone adapter 留在既有 @qinglong/local-sqlite 包并复用共享 LocalSqliteOperationAuthority。一页先有界读取 candidate,但每个 Workflow 单独执行 一个 BEGIN IMMEDIATE

  • 页面增大不会把一个 SQLite write lock 放大到整页 DAG
  • 单 Workflow 最多 128 StepRun,内存和写放大有硬上限;
  • 中途故障只会留下已完整提交的前序 Workflow,重跑按 durable terminal 状态继续;
  • 不增加连接、后台线程、timer、表、migration 或依赖。

不采用的方案

每个 Step 创建 child Run

拒绝。会复制 Workflow/Step 聚合、取消 intent、版本序列和 recovery authority。

cancellation reconciler 直接终结 leased/running Attempt

拒绝。stop_requested 不是执行已停止的证据,迟到 completion 可能来自仍合法的 Worker authority。

为 Workflow 新建 cancellation package、表或 cadence

拒绝。该能力没有独立部署、权限或重依赖边界;复用现有 core/storage subpath 更符合 D-207,也避免增加路由器供应链和空闲唤醒成本。

当前验证

  1. runtime-core 共享状态机覆盖全部 non-executing、unleased claim、leased/running blocked、timeout 映射、determinism 与 stale admission,完整测试 419/419
  2. local-sqlite 在真实内存 SQLite 上完成 Attempt + 两 StepRun + 父 Run 原子取消、 admission crash 与 conclusive-stop/control-terminal crash 的 exact replay 完整测试 189/189
  3. cluster-postgres 完整测试 238 项为 237 pass、1 条条件 skip、0 fail
  4. 全新 PostgreSQL 18.4 最小权限六角色实例已完成 admission→recovery→requeue→ second admission→whole cancellation,连续事件和所有 terminal fact 同事务提交;
  5. QL3_HA_SKIP_IMAGE_PULL=true 的 PostgreSQL 18.4 arm64 物理 HA 门再次通过 remote_apply、timeline 1→2、旧主先 fencing、pg_rewind 只读同步重入、两个 fresh control replica 与 gates.passed=true
  6. Cluster production composition 已复用原有全局 cancellation lifecycle 和同一个 PostgreSQL repository,因此不增加第二 cadence。
  7. ADR-0229 已把 Local cancellation、frontier、Task admission 与 dispatch 接入同一 scheduler cadence;启动前取消的 Workflow 原子终结两个 StepRun、零 Attempt 普通 Run recovery 扫描为 0
  8. PostgreSQL physical HA 已新增独立 Workflow Task Attempt report,原子提交、 exact replay、晋升前复制、runtime-only ACL 与晋升后存活五项 gate 全为 true;
  9. Local 两步 Workflow、每 Workflow 一个 SQLite write transaction,以及 Edge/Standalone 两组各 16 点 admission 与 conclusive-stop/control-terminal crash/reopen/replay 已接入既有 128/256 MiB Linux resource gate。arm64 实测的 Workflow process peak RSS 分别为 87449600/87949312 bytes,写锁 p95 分别为 4.053/3.317 mscgroup memory pressure/OOM 增量为零; workspace 仍为 20 包。
  10. PostgreSQL 18.4 arm64 physical HA 新增真实 Remote Workflow cancellation 矩阵:正式 admission/Task Attempt、stop_requested、成功退出映射 cancelled、 completion 与父 Workflow convergence 两个 COMMIT response-loss 窗口、 exact replay、remote_apply、timeline 1→2 与 promotion 后再次 replay 均通过; Run/Attempt/StepRun 为 cancelled、Lease 为 completed@v68 个 Event 与 3 个 StepRun mutation 无重复。

尚未关闭

  1. 固定 Edge/Standalone 物理设备基线、闪存/FTL 写放大和受控突发断电门;CI SIGKILL 证据不提升为物理 power-loss 结论。