Appearance
运行中的并发怎么收住:Concurrency Limit、Cancellation 与 Deadline
全局允许 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。 |
关联源码
自测
- Run deadline 只剩 2 秒,却又启动一个默认 timeout=30 秒的 Tool。deadline 应如何向下传播?
- 取消父 Run 后仍有子 Task 继续写状态。Cancellation propagation 哪一层没有收住?