Python 21:任务状态与结果如何一起落盘
一次任务已在数据库里改成 running,写入结果之前人为抛出错误。重新打开同一个 SQLite 文件查询,状态仍为 queued,结果仍为 null;随后正常运行,另一个 Python 进程查询到 succeeded 和 {"one": 2, "two": 1}。这不是断言“错误从没发生”,而是事务撤销了未完成的状态变更。状态与结果若分开提交,查询者就可能看见成功却没有结果,或旧状态配上新结果。
20 的版本化 JSON为任务字段提供了校验入口;本篇第一次把状态、结果和错误放进 SQLite 的实际记录。单次进程内词频运算仍委托给相同的 app.run,没有复制一套计算规则。
先确定读写边界
tasklab.database.SQLiteTaskGateway 提供提交、执行、查询和取消四个方法。提交用既有 app.submit 验证文本和任务种类,在 SQLite 表内插入 queued 记录,由数据库分配连续 ID;task-0001 是持久化表中的第一条任务,不再是每次启动都从 1 开始的进程内序号。查询读回标签和结果,先构造快照再经 20 的 encode_task/decode_task 校验版本、字段和状态关系。查询返回的是独立的字典,不把 SQLite 游标或连接交给调用方。
执行操作在同一连接中以 BEGIN IMMEDIATE 开始写事务,读取并确认任务仍 queued,然后调用原有计算;写回时先更新数据库状态 running,再写 result,最后更新为 succeeded。中间注入 fail_at="after_state" 时抛异常,连接上下文执行回滚;旧的 queued 与 null 一起保留。正常路径退出上下文后提交,之后重开连接查询才能确认持久结果。重复执行同一个已完成任务会被拒绝,并不覆盖已提交的结果。
Python 3.12 的 sqlite3.Connection 上下文管理器负责事务提交/回滚,不负责关闭连接。代码额外用 contextlib.closing 明确关闭;不能把 with sqlite3.connect(...) as connection 解释为离开作用域就关闭文件。执行过程中已有计算可能运行了但提交失败,所以“数据库里还是 queued”也不能证明外部副作用从未发生。本章的词频计算不调用外部服务;未来重试与恢复必须显式处理这一边界。
失败任务、取消和跨进程查询
fail_at="compute" 走原有 app.run 的异常路径,数据库里保存 failed、异常诊断和空结果;注入数据库写回故障则整段更新回滚为 queued,这两种失败不能合并成一个结果。取消排队任务写入 cancelled 并返回请求已接受,第二次取消返回 false;终态不能重写。当前顺序实现把写事务覆盖了完整执行过程:另一个写者会等待锁,不能据此声称运行中任务能被即时取消;并发取消与工作者释放需要到 28–35 的事件/屏障测试。
演示先打开临时 SQLite 数据库,注入故障并重开检查,再成功执行;接着用五个独立 python -m tasklab.db_cli --db PATH ... 子进程查询、提交、取消、查询、重复执行。成功请求只向 stdout 输出一行 JSON;重复执行退出 2、stdout 为空,stderr 有 only queued tasks can run。db_cli 是原有包的持久化入口,17 的一次性 tasklab.cli 仍保留供前后对照;两个入口委托相同的业务运算,不能混用数据库与内存 ID 的生命周期。
1 | |
本次累计 66 项测试通过,含真实临时数据库的新实例查询、回滚后重试、执行异常保留失败状态、排队取消和新进程执行同一数据库的操作。examples/python-language/evidence/21/20261007-sqlite/ 保存原始子进程标准流、注入时的快照、测试记录与页面结果。SQLite 库版本单独记录,不能拿 Python 的语言版本推断库的具体补丁。数据库 schema 目前只有版本 1,尚未做升级/迁移或进程强制终止后的恢复。
预测题
执行已将 state 更新为 running,紧接着在写 result 前抛异常。事务退出后另开连接查到什么?外部消息如果已在写库前发送,能否也被回滚?
答案:本章实验读到之前已提交的 queued 和 null;连接退出将本次未提交更新一起回滚。SQLite 只能撤销参与该事务的数据修改,不能撤销独立外部系统的副作用。这里没有发送外部消息的实验,不冒称验证了外部原子性。
变更题
若在更新 running 后主动 commit(),再写结果时发生异常,怎样观察被破坏的不变量?
答案:另一连接可能在故障后查到永久 running 且没有结果,注入故障后的断言 queued / null 立即失败。恢复要另订规则,不能把“看到 running”自动判为正在执行。本章代码没有做这种提前提交,强制杀进程或恢复遗留 running 的验证在 35 才进行。
下一章进一步界定单元、集成和故障测试各证明哪一层。系列导航只链接已生成的页面。



