Skip to content
预计 1 分钟右侧目录可快速定位

运行中的并发怎么收住:Concurrency Limit、Cancellation 与 Deadline ​

前置:DAG 怎么调度:Ready Queue、Parallel Execution 与 Scheduler

全局允许 50 个任务,但某 SaaS API 只允许 5 个 in-flight,可为该 tool 单独 semaphore。用户点击 Stop 后,Ready Queue 不再启动新节点,已运行 Tool 收到取消,Run 最终进入 CANCELLED 而不是 FAILED。

Runtime Concurrency Limits ​

并行度由依赖和容量共同决定。

并发上限保护模型/API/DB/CPU/内存等下游容量,通常需要全局、provider、tool type、tenant 多层限制。上限不是越大吞吐越高。

Runtime Cancellation Propagation ​

Cancellation 是协作式停止信号,必须从 Run 一路传到真正占资源的工作:

text
user cancels Run
→ Scheduler stops starting new Nodes
→ active Node tasks receive cancel
→ Tool/HTTP/subprocess cancel or terminate
→ cleanup resources
→ persist CANCELLED/ABORTED facts

如果只把 run.status 改成 CANCELLED,而底层模型调用和 Shell 进程继续跑,系统只是“UI 看起来取消了”。Cancellation 也要和普通 failure 区分,因为它通常不应该触发同一套 retry。

🔍 源码对照:Await/Tool Observation 必须感知 cancel/interruption ​

完整源码与通用分析见 11-01 Timeout / Deadline / Retry。这里不重复代码;这里关注 cancellation:cancel / interruption 必须传播到等待中的 Tool / Await;只修改 Run 状态并不能终止真实 I/O。

源码说明

Observation 层会追踪异步任务与 pending waits,说明 cancellation/deadline 不能只停留在 Python task 层;它们还必须投影成 Runtime 能解释的状态。一个 task 被 cancel、一个 tool timeout、一个 subagent 仍运行,对 Run 的意义并不相同。

工程边界

案例中的 async task 分类和 timing 常量是产品语义;Shadow Harness 应从自己的 Node/Tool/Run deadline hierarchy 推导取消传播。

Shadow Harness 对照

  • Deadline 从 Run 向 Turn/Node/Tool attempt 派生,子层不能延长父 deadline。
  • Cancellation 记录 cause 与 scope,并与 timeout/failure 分开。

验证

父 Run deadline 到期时启动三个子任务,验证未完成任务被取消、已提交 side effect 不被伪装成“从未执行”、终止原因可以从 trace 重建。

Deadline Propagation ​

Deadline 是一个绝对结束时间,它必须沿 Run → Node → Tool → HTTP/DB 逐层传递。

假设整个 Run 只剩 12 秒:

text
Run remaining = 12s
→ Node planning uses 2s
→ Tool 最多只剩约 10s
→ Tool 内 HTTP 不能再给自己 30s timeout

如果每一层都重新创建“30 秒 timeout”,嵌套调用会把总耗时无限向后推。正确做法是每层计算 remaining = deadline - now,并把自己的局部 timeout 限制在 remaining 内。

Deadline 要用“剩余时间”向下传 ​

假设 Run 的绝对 deadline 是 12:00:30。Planner 已经花了 8 秒,当前时间 12:00:08,那么 Tool 最多只剩 22 秒;如果 Tool 内部还要先等连接池 5 秒,真正 HTTP request 只剩约 17 秒。

text
Run deadline 12:00:30
  └─ Node starts 12:00:08  remaining=22s
       └─ pool wait 5s     remaining=17s
            └─ HTTP timeout <=17s

如果每一层都重新给“30 秒 timeout”,最坏时间会层层相加,Run 的总 deadline 就失去意义。Runtime 应传播 absolute deadline 或 remaining budget,并让下游在开始昂贵工作前重新检查预算是否还值得执行。

Failure Modes ​

已取消 Run 仍继续调度新节点 ​

症状: cancel flag 已写入 RunState,但 scheduler 的 ready queue 仍继续发出新 Node。

为什么危险: 用户看到任务已取消,系统却仍可能产生新的 Tool 副作用。

处理: cancellation 先改变 durable Run/Node eligibility,再向正在执行的 child operation 传播停止信号;scheduler 每次出队前检查 terminal/cancel state。

Cancellation 没有形成可恢复事实 ​

症状: 进程内 task 被 cancel 了,但 Run/Node durable state 没有记录取消原因和范围。

为什么危险: 重启后 scheduler 会把这些节点重新当成 pending work。

处理: 把 cancel request、affected nodes、terminal transition 和未确定副作用写入 Runtime state;底层 CancelledError 处理放在 Async 模块。

如何验证 ​

Case断言
已取消 Run 仍继续调度新节点写入 cancel command 后让 ready queue 仍有节点,断言新节点不再 claim,已运行 child 收到 cancellation。
Cancellation 没有形成可恢复事实cancel 后立即 crash/restart,断言 Run/Node 恢复为 CANCELLED/aborted,而不是重新进入 READY。

关联源码 ​

自测 ​

  1. Run deadline 只剩 2 秒,却又启动一个默认 timeout=30 秒的 Tool。deadline 应如何向下传播?
  2. 取消父 Run 后仍有子 Task 继续写状态。Cancellation propagation 哪一层没有收住?

Sources ​

AI Engineering · Agent · Harness · Backend Systems