# P3 取消与无外键回归记录 验证日期:2026-09-17。本阶段交付任务取消闭环,并落实用户要求的无数据库外键设计。P3 配额/背压、可靠订阅及 P4–P7 仍未完成;持续目标保持进行中。 ## 1. 已实现行为 - WAITING、LEASED、RUNNING、NEEDS_REVIEW 的取消控制,动作与队列授权、CAS、稳定幂等身份、取消事件及审计同事务提交。 - 开始前取消阻止 Start 并释放许可;运行中只保存意图、返回 HTTP 202,不提前释放许可或伪造完成。 - 取消后不延长期限、不产生后续重试;有效成功结果仍能确认,普通取消错误或失联保持未知效果。 - 明确取消结果使用 task.finish / CANCELLED;本次确认停止不能清除此前未核实的外部效果。 - 可选择的 task.cancel 通知、Resume/Start/Renew 中的当前取消意图、独立 SDK 自动停止处理器和有界收尾。 - [完整取消场景](../../examples/cancellation/main.go)、[接口与升级说明](../api/cancellation.md) 已同步交付。 - 迁移 000007 扩展取消证据;000008 移除 70 个历史外键,保留索引、唯一约束、CHECK、触发器和数据。实体数仍为 32,没有新增持久化字段。 - 开发规范明确禁止再引入外键。应用启动/readiness 核验迁移及零外键要求;任务、计划及 Worker 关联由服务端事务校验。 ## 2. 环境、命令及结果 macOS 本机、Go 1.27.1、Docker 单实例 MySQL 8.4.10。完整集成使用一次性数据库、随机凭据及回环端口;测试结束删除容器,没有修改已有业务库。 ```sh make fmt test vet build make integration go test -race ./internal/transport/httpserver -run '^TestWorkerPublishedContract$' cd sdk/go GOWORK=off go test -race ./... GOWORK=off go vet ./... ``` 上述检查均通过。普通单元运行未配置集成环境时会跳过数据库/外部进程用例,真实证据来自完整 integration。 | 测试组 | 本轮实际结果 | | --- | --- | | MySQL 全部集成 | PASS,52.851 秒 | | 历史 v7 → 当前无外键升级 | PASS,3.99 秒 | | 无外键条件下的资源归属校验 | PASS,0.08 秒 | | 取消事务、恢复及竞争 | PASS,2.89 秒 | | SDK Worker 连接与运行器全部集成 | PASS,78.267 秒 | | 其中自动取消六种故障/结果场景 | PASS,15.51 秒 | | SDK 计划生命周期 | PASS,含包开销 4.586 秒 | | 真实进程故障组 | PASS,21.500 秒 | | 八个完整示例 | health / tasks / worker-protocol / worker / history / retry / schedules / cancellation 全部通过 | 上述耗时是单次测试用时,不是性能或容量指标。 ## 3. 零外键与关联校验证据 [无外键集成用例](../../internal/storage/mysql/no_foreign_keys_integration_test.go) 在独立库重建真实 v7,再使用正式 Migrate 入口升级: - information_schema 中外键从 70 变为 0,新建完整结构也断言外键为 0。 - 升级前后的全部 325 条索引列定义完全一致,包含索引名、唯一性、列顺序、前缀、类型及可见性。 - 预置任务的所有字段逐一核对不变,包括大整数参数、状态、版本、策略和时间;再次执行迁移成功。 - 仅在一次性测试库故意添加违规外键,验证 CheckSchema 拒绝;移除后恢复通过。这是漂移注入测试,正常迁移与服务不创建该测试外键。 - 对不存在的队列及另一命名空间队列,任务提交、计划创建、Worker 注册均返回 NOT_FOUND;无残留任务、计划、幂等记录或新 Worker 会话。 - 会话的父进程关系被故意改坏后,控制请求返回 STALE_SESSION,不能借用错误关联继续消费。 Worker 注册补充了事务内队列存在性检查;会话使用核验进程、凭据及代次。已有任务、计划和执行事务保留其命名空间、父资源与尝试身份校验。 历史迁移文件保留原校验和,通过 000008 前向移除外键;新装也运行至当前版本。取消/恢复不再取得为历史外键检查准备的会话共享锁,使用队列 → 任务 → 尝试 → 许可的锁顺序。此前开始/取消竞争曾因隐式外键锁发生死锁,本轮在无外键结构上通过该竞争用例;这不表示所有负载下不存在其他锁竞争。 应用校验不能防止绕过服务的直接 SQL 错写。当前无公开硬删除,后续清理及流程/订阅引擎必须遵守 [关联与保留规则](../07-data-model.md)。 ## 4. 取消事务证据 [数据库用例](../../internal/storage/mysql/worker_cancel_integration_test.go) 覆盖: - LEASED 取消后禁止 Start,许可只释放一次,迟到成功仅作为证据。 - RUNNING 收到取消时保持任务/尝试运行及许可;有效成功、失败、未知、确认取消分别按真实结果处理,不创建重试。 - 旧 Start ACK 保留原回执,同时带当前取消意图;取消后 Renew 不延长期限。 - 新节点会话隔离旧连接,Resume 后取消意图仍存在;原取消请求在任务最终完成后重报,仍返回原 RUNNING 快照。 - 先前未知效果不能被后一次确认取消抹掉;无取消意图的 CANCELLED 报告被拒绝。 - 测试触发器使审计写入失败,取消意图和版本整体回滚;解除故障后可用原身份重试。 - 已取消的授权尝试到期进入核查,迟到取消结果只保存证据。 - 12 次并发 Start/Cancel:取消获胜阻止开始,Start 获胜使旧版本取消冲突,随后可按新版本记录运行中意图。 ## 5. SDK 与故障证据 [SDK 真实服务用例](../../sdk/go/worker_cancel_integration_test.go) 覆盖六种情况: 1. 明确确认停止,丢失 Finish ACK 后原结果重报,最终 CANCELLED 且一次尝试。 2. 普通 context 错误返回 UNKNOWN,任务进入 NEEDS_REVIEW。 3. 取消后仍取得真实成功结果,成功和精确整数保留。 4. 丢失取消通知并断开连接,重连 Resume 仍能停止原处理器。 5. Start ACK 丢失且暂时断网,取消后重连,处理器调用次数为 0。 6. 处理器不配合取消,任务到期核查,Run 在 ShutdownTimeout 后返回未收尾错误,不能假称已排空。 协议 Schema 与实际解析共同验证可选 Hello 字段、取消通知、当前 Lease 意图、CANCELLED 结果和 retryable 分类。低层 SDK 验证通知读取、取消等待不关闭连接,以及带取消意图的 Lease 不提供继续执行权。 完整取消示例实际输出: ```text task=<独立任务ID> cancel_requested=true accepted_state=RUNNING final_state=CANCELLED attempts=1 worker_drained=true running=0 ``` 原有 WSS、安全重试、计划调度及真实进程故障回归同步通过;进程组包括 ACK 丢失后节点接管、Worker 强杀后未知核查、调度强杀与服务间隙后的两节点补跑。 ## 6. 当前边界与后续 - 取消要求业务遵守 context;不能强杀 goroutine,也不能撤回外部已经发生的副作用。普通错误不证明停止成功。 - 取消通知是有界提示,持久化意图和结果才是权威。服务端取消扫描每批最多 256 项,SDK 未读通知最多 32 条。 - 先停旧服务、应用全部迁移、启动新版服务,再升级自动 SDK;没有宣称通过混合版本滚动升级验收。 - 本次验证单数据库及本机进程故障,未验证数据库复制切换、跨故障域网络分区、大表在线 DDL、备份恢复或百万租户吞吐。 - 后续继续 P3 配额/背压与可靠订阅,随后推进 Weave。当前没有把取消闭环或无外键改造等同于最终生产交付。