Skip to content

立即中断并重新入队的 Scan Job Pause / Resume 方案 #22

Description

@Kherrisan

背景

当前 Pause / Resume 的主要实现是:

  • Pause:把 scan job 状态改为 paused,停止当前进程内的 pipeline runtime
  • Resume:把状态改回 running,基于已有队列重新启动 pipeline runtime

这种方式没有完整处理正在启动或正在运行的 task。Pause 后,task 容器、Agent turn、并发槽位和迟到结果可能继续存在;Resume 时也无法保证这些 task 从头重新执行且只执行一次。

本 issue 的目标不是保存 task 的执行进度,而是提供明确、简单、可靠的强制暂停语义。

核心语义

Pause

Pause 必须立即:

  1. 持久化 job 的 pause intent,并阻止 worker 领取新 task、阻止 pipeline 创建下游 task。
  2. 停止所有 startingrunning task 对应的 Agent turn、ACP process、容器及运行时。
  3. 将这些 task 当前 attempt 标记为 interrupted
  4. 丢弃当前 attempt 的执行进度、部分输出、临时 artifact 和未提交结果。
  5. 记录需要在 Resume 时重新执行的 logical task。
  6. 保留 Pause 前已经成功提交的 completed task 和有效结果。
  7. 等待所有运行时确认终止后,job 才从 pausing 进入 paused

不提供 graceful drain,不等待正在运行的 Agent turn 完成,也不尝试保存半途 checkpoint。

Resume

Resume 必须:

  1. 为 Pause 时被中断的 task 创建全新的 attempt。
  2. 使用原 task 的不可变 input、pipeline definition 和 runtime config snapshot 从头执行。
  3. 为新 attempt 创建新的 Agent session,不恢复被中断的 ACP session。
  4. 将新 attempt 与原本尚未启动的 queued task 一起重新放入队列。
  5. 重新启动 pipeline runtime,并继续正常的 edge/fan-out 调度。
  6. 保证重复 Resume 请求不会重复创建 attempt 或重复入队。

状态模型

Job 状态:

pending/running -> pausing -> paused -> resuming -> running

Task attempt 状态至少区分:

queued -> starting -> running -> completed
                           \-> interrupted

interrupted 是终态记录,不会被改回 running。Resume 创建新的 attempt,保留旧 attempt 供审计。

Pause Fence 与竞态处理

Pause 请求在数据库中原子写入:

  • pauseRequestedAt
  • 递增的 executionEpoch
  • pause request ID

所有 worker、queue consumer、Agent driver、artifact/result writer 和 edge fan-out 在启动或提交前检查 epoch:

  • Pause intent 写入前已经成功提交的 completed task 保留。
  • Pause intent 写入后才到达的旧 attempt 结果一律拒绝并丢弃。
  • 旧 worker 即使未及时退出,也不能覆盖 Resume 后的新 attempt。
  • Pause 与 task start、task complete、下游 enqueue 同时发生时,以数据库中的 epoch 和提交顺序为准。

运行时终止与清理

Pause 应统一终止并回收:

  • 正在启动和正在运行的 scan task container
  • ACP session/prompt 和 agent process
  • task runtime、terminal 和 monitoring subscription
  • BullMQ active job 对应的执行租约
  • 当前 attempt 的临时目录和部分输出
  • 尚未提交的 candidate/result projection

终止操作必须幂等。容器已经退出、进程不存在或 worker 重启时,reconciler 仍应能把 attempt 收敛为 interrupted

Resume Reconciliation

Resume 由单一 reconciler 获取 job lease,并按数据库状态恢复:

  • completed task 保持不变,不重新执行
  • 原本 queued、尚未启动的 task 恢复调度
  • interrupted task 创建新 attempt 并重新入队
  • 孤儿 container、旧 runtime 和旧 epoch queue item 被清理
  • 通过稳定 idempotency key 防止 task、edge、candidate 和结果重复

如果部分 task 入队失败,job 保持 resuming 或明确的恢复失败状态,可安全重试;不能表面显示为 running。

UI 与可观测性

  • UI 区分 pausingpausedresuming 和恢复失败
  • pausing 时展示仍待终止的 starting/running task 数量
  • task detail 展示被 Pause 中断的 attempt 及其 replacement attempt
  • 记录 pause/resume request、execution epoch、终止原因、重新入队结果和耗时
  • 明确提示用户:Pause 会丢弃所有运行中 task 的当前进度,Resume 后从头执行

验收标准

  • Pause 后不再有新的 task 或下游 edge 开始执行
  • 所有 starting/running task 被立即终止,当前 attempt 标记为 interrupted
  • 被中断 attempt 的部分输出和迟到结果不会生效
  • Pause 前已完成并提交的 task 不被重新执行
  • Resume 为每个 interrupted task 创建且只创建一个新 attempt,并重新入队
  • Resume 使用原输入和配置从头运行,不恢复旧 ACP session 或半途进度
  • 重复 Pause / Resume 请求保持幂等
  • Vulseek、BullMQ 或容器运行时重启后仍能收敛到一致状态
  • 覆盖 Pause/start、Pause/complete、Pause/fan-out、迟到提交、孤儿容器、重复 Resume 和部分入队失败测试

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions