Appearance
Run 等几个小时还能继续吗:Durable Wait、Timer 与 External Event
前置:多个 Worker 抢同一个 Run 怎么办:Claim、Lease、Heartbeat 与 Fencing
核心问题:Run 等待几小时的审批或外部事件时,怎样释放 Worker,又怎样保证事件到来后只唤醒正确的等待实例?
“等待”不能靠一个 Python Task 睡着
Agent 发起异步部署后,第三方系统可能 40 分钟后才通过 webhook 返回结果;另一个 Run 需要“明天 09:00 再检查”;审批则可能两天后才有人处理。
如果实现是:
python
await asyncio.sleep(60 * 60 * 24)或一直把 HTTP/worker 进程挂着等待,那么一次重启、扩缩容或部署就会丢失等待位置。
长任务中的等待应该被建模成 durable fact,而不是进程栈状态。
Wait Condition 是 Runtime 状态的一部分
Run/Node 进入 WAITING 时至少持久化:
text
wait_id
kind = TIMER | APPROVAL | EXTERNAL_EVENT
correlation_key
due_at / expected_event
resume_node
created_at
status = WAITING然后当前 Worker 可以完全退出。系统恢复后仍知道“在等什么”和“满足条件后从哪里继续”。
这就是 durable wait 与普通 sleep() 的区别。
Timer:时间到了只是产生一个可处理事件
Timer 不应该直接修改业务状态。更稳妥的模型是:
text
persist TIMER(due_at)
→ timer service / scheduler detects due
→ emit wake event(wait_id)
→ Runtime validates current state
→ WAITING → READY/RUNNING这样 timer 重复触发也可以通过 wait_id 去重;服务停机一小时后重启,也可以扫描 overdue timer 补发 wake-up,而不是永久丢失。
External Event:先相关,再恢复
Webhook、消息队列、另一个 Workflow 的结果本质上都是外部事件。Runtime 需要稳定 correlation key,例如:
text
run_id + wait_id
external_job_id
approval_id
business_operation_id外部事件到达后先写入 inbox/event record,再尝试推进状态。不要让 HTTP handler 直接从内存找到某个 Future 并 set_result();进程重启后那个 Future 根本不存在。
典型流程:
text
POST /webhook/deployment
→ verify/authenticate
→ persist ExternalEvent(event_id, correlation_key, payload)
→ dedup
→ match WAITING record
→ transition WAITING → READY
→ enqueue/notify schedulerLost Wake-up 与 Early Event
最容易漏掉的不是“事件没来”,而是事件到达时序:
- Runtime 还没把 WAITING 提交,external event 已经先到;
- WAITING 已提交,但 wake event 在 enqueue 前 crash;
- provider retry webhook,事件重复到达。
因此 wait registration 与 event inbox 都必须 durable。恢复/对账逻辑应能匹配“先来的 event”和“后注册的 wait”,而不是要求事件刚好在进程监听期间到达。
与 HITL 的关系
HITL 不是一套特殊 Runtime。它只是:
text
kind = APPROVAL
expected_event = approval.decision用户批准、定时器到期、第三方 job 完成,都可以共用同一个 wait/resume state machine;不同的是事件来源、鉴权和 payload validation。
Failure Modes
等待期间必须占住 Worker
会造成线程/连接/内存长期占用,并让部署重启变成数据丢失。
处理: 写 checkpoint + wait record 后释放执行资源。
Webhook 直接“唤醒内存对象”
多实例或重启后无法找到原 Future,事件也难以重放。
处理: 先持久化 external event,再由 Runtime 根据 correlation 和 state transition 恢复。
如何验证
| 场景 | 必须成立 |
|---|---|
| WAITING 后整个服务重启 | Timer/approval/external event 到来后仍能 resume |
| event 在 wait registration 前到达 | 后续注册时能够匹配,不丢事件 |
| 同一 webhook 重试 3 次 | 只发生一次有效 state transition |
| timer 到期但 enqueue 前 crash | 重启扫描后仍能补唤醒 |
自测
- Run 要等 6 小时人工回调。为什么不能让一个线程 sleep 6 小时保持执行上下文?
- External event 重复投递时,怎样用 stable wait/event identity 保证同一个等待只被满足一次?