Files
orbit/docs/api/cancellation.md

109 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 任务取消与结果确认
状态:已实现 HTTP 取消意图、开始前取消、WSS 通知、重连核对及 Go Worker 自动停止。取消记录、审计、事件和回执使用 MySQL 事务;运行结果仍以有效尝试的确认或到期核查为准。
## 1. HTTP 接口
`POST /v1/tasks/{task_id}:cancel`。需要当前 `tasks:cancel` 动作和目标队列权限;Bearer、JSON 和 Idempotency-Key 规则同 [任务接口](tasks.md)。适用于普通任务和计划生成任务;流程拥有的任务由流程控制管理,此入口返回 409。
```json
{"expected_revision":"3","reason":"用户终止本次处理"}
```
| 当前状态 | 提交后行为 | HTTP 状态 |
| --- | --- | --- |
| WAITING | 阻止后续尝试,通常成为 CANCELLED | 200 |
| LEASED | 与开始事务竞争;尚未授权时结束尝试并释放许可 | 200 |
| RUNNING | 保持 RUNNING,记录 cancel_requested_at;许可暂不释放 | 202 |
| NEEDS_REVIEW | 保持核查状态,保存取消意图,不清除未知效果 | 200 |
| 已有取消意图或终态 | 新操作身份返回 STATE_CONFLICT;原键仍返回首次回执 | 409 或原回执状态 |
成功的 data 为 `{"task":<操作提交时的完整任务快照>,"deduplicated":false}`,与 CancelTaskResult 一致。202 只表示取消意图已经可靠保存,不能解释为处理器已经停止。
WAITING/LEASED 若承接先前未知效果,取消后进入 NEEDS_REVIEW,保留 effect_uncertain=true 和空 completed_at。本次未开始不能证明此前尝试没有产生效果。
请求结果不明时,保留原键、原 expected_revision 和原 reason 重报。只有明确收到 STATE_CONFLICT 后,才读取新状态并创建新的管理操作;任务的续租也会更新 revision。已保存的 RUNNING 取消回执即使任务后来结束,重报仍是原快照和 202,当前状态用 GET 查询。
取消与开始竞争有两种有效结果:取消先提交则 Start 不获授权;Start 先提交则旧 expected_revision 冲突,重新查询后可记录运行中取消。权限、名称空间、幂等回执、版本和审计都在事务内核对。
## 2. Worker 协议
Hello 可选 `cancellation_notifications:true` 表示接收异步通知。低层 SDK 的 WorkerRegistration 默认为 false,自动 NewWorker 显式开启。未开启的连接不会收到新通知类型。
服务端通知示例,session_id 是当前连接,消息没有 reply_to:
```json
{
"protocol_version":"1",
"type":"task.cancel",
"message_id":"11111111111111111111111111111111",
"session_id":"22222222222222222222222222222222",
"body":{
"task_id":"33333333333333333333333333333333",
"attempt_id":"44444444444444444444444444444444",
"epoch":"1",
"requested_at":"2026-09-17T08:00:00.123456Z"
}
}
```
每次连接按数据库事实扫描当前拥有的运行尝试,通常约一秒发现取消,实际延迟受数据库和网络影响。通知允许重复、迟到、断线丢失,只作用于 TaskID/AttemptID/Epoch 全部匹配的尝试。单次读取最多 256 条,连接发送与 SDK 接收缓冲有界;慢消费者断开后重新核对。
Start/Renew/Resume 返回的当前 Lease 也包含可选 `cancel_requested_at`;即使原 Start 回执是已批准执行,当前 Lease 中的取消意图也不能忽略。Active=true 表示原尝试仍可在有效期内提交权威结果,**有取消意图时不得继续业务处理**。SDK 的 ExecutionDeadline 在此情况下返回 false。
取消后 Renew 不延长任何执行期限。处理器应尽快停止并提交已知结果;原租约或尝试截止到期后,未确认效果转 UNKNOWN/NEEDS_REVIEW,禁止因任务幂等声明自动再试。服务停止、通知丢失或 Worker 不配合,都不会自动成为 CANCELLED。
结果继续使用 `task.finish`,新增 outcome=CANCELLED;error_code 必填稳定分类,retryable 不得为 true,result 是非 null JSON 或核查证据引用。不存在已保存取消意图时,此结果被拒绝。
| 有效尝试上报 | 任务结果 |
| --- | --- |
| SUCCEEDED | SUCCEEDED,成功不被取消意图覆盖 |
| FAILED | FAILED,停止后续自动重试;先前效果未知则仍需核查 |
| UNKNOWN | NEEDS_REVIEW,不能把 context 错误当作撤销证明 |
| CANCELLED | 本次确认停止且无遗留效果;若此前存在未知效果,仍为 NEEDS_REVIEW |
| 租约已失效或结果冲突 | 追加 EVIDENCE_ONLY,不覆盖权威状态 |
新版要求先停用旧服务、应用全部迁移(000007 扩展取消证据,000008 移除外键),再启动新版服务,最后升级自动 Worker。旧服务会拒绝新增 Hello 字段或 CANCELLED 结果;不能将带新能力的连接随机路由至旧节点。旧低层客户端可以继续原协议,但必须升级后才能依赖自动取消通知;取消后的续租不延长期限,同样适用于旧客户端。数据库升级细节见 [数据模型](../07-data-model.md)。
## 3. Go SDK 接入
自动 Worker 将业务 context 的 cause 设置为 `orbit.ErrTaskCancellationRequested`。仅当接入方能确认本次已停止且无未结清效果时返回 `*orbit.WorkerCancellationConfirmed`:
```go
<-ctx.Done()
if !errors.Is(context.Cause(ctx), orbit.ErrTaskCancellationRequested) {
return nil, ctx.Err()
}
// 先完成本次外部操作核实;此处的类型是业务声明,不替代核实。
return nil, &orbit.WorkerCancellationConfirmed{
Details: json.RawMessage(`{"evidence_ref":"objects/stopped"}`),
}
```
普通 ctx.Err()、panic、网络错误和无效结果仍属于 UNKNOWN。处理器已经成功时返回真实结果,即使此时 context 也被取消,平台仍按有效结果确认。开始批准已提交但 ACK 丢失时,SDK 重连先检查取消意图;如果处理器尚未被调用,可以明确报告本次没有副作用,禁止重启处理器。
低层接口设置 WorkerRegistration.CancellationNotifications 后,使用 NextCancellation 持续读取,并同时检查每次 Start/Renew/Resume 的 Lease。NextCancellation 的等待 context 取消只停止等待,不关闭连接。取消响应丢失后的 Finish 重报必须保留相同消息身份和结果。
任务取消不等同于取消 Run context:前者停止指定业务处理器,后者要求整个 Worker 排空。无法强杀不配合 context 的 goroutine;它仍占本地物理槽,ShutdownTimeout 到期会报告未收尾数量。
## 4. 可运行场景
源码:[cancellation/main.go](../../sdk/go/examples/cancellation/main.go)。使用 [Worker 初始化](../sdk/worker-runtime.md) 的私有凭据文件,需 tasks:submit/read/cancel、workers:connect/consume 和队列范围。此示例只等待停止提示,没有外部业务副作用。
```sh
cd sdk/go
ORBIT_URL=http://127.0.0.1:8080 \
ORBIT_CREDENTIALS_FILE=/private/path/worker.credentials.json \
go run ./examples/cancellation
```
预期输出:
```text
task=<任务ID> cancel_requested=true accepted_state=RUNNING
final_state=CANCELLED attempts=1
worker_drained=true running=0
```
每次运行使用新任务/处理器身份;正常执行保留任务、尝试和审计记录,不删除历史。错误时非零退出并尝试有界收尾。网络结果不明时示例停止,真实集成应保存原幂等身份以便核对。