自动化交付

文档处理超时,不要让一个任务拖死整个批次

一个扫描 PDF 卡住了,后面的九十九个任务都不动。给请求加超时时间看似解决了问题,实际处理进程却可能仍在后台吃内存。超时至少涉及三个动作:停止等待、停止执行,以及标记产物不可交付。

阅读文本 / Reading text
本文使用合成样本演示处理方法。代码已通过隔离环境中的示例检查;这不代表对所有真实文件或产品效果的保证。处理重要文件前,请保留原件,并按文中的边界与验收项复核。 一个扫描 PDF 卡住了,后面的九十九个任务都不动。给请求加超时时间看似解决了问题,实际处理进程却可能仍在后台吃内存。超时至少涉及三个动作:停止等待、停止执行,以及标记产物不可交付。 等待超时不等于工作已终止 subprocess.run(timeout=...) 对直接子进程提供超时处理,但不能泛化为“任意进程树已经被清理”。不同操作系统、启动方式和孙进程行为会影响结果。生产实现需要进程组、容器或作业对象等隔离策略。 先写清楚任务状态契约 下面只模拟状态判定,不启动外部命令、不故意挂起进程。它演示的重点是:达到截止时间后,即使某个工作单元晚到了一份结果,也不能直接把已超时任务改成成功。 from dataclasses import dataclass @dataclass(frozen=True) class Decision: state: str deliver: bool terminate_requested: bool def decide(now, deadline, worker_done, artifact_valid): if now >= deadline: return Decision("timed_out", False, not worker_done) if not worker_done: return Decision("processing", False, False) if not artifact_valid: return Decision("invalid_artifact", False, False) return Decision("verified", True, False) assert decide(9, 10, False, False).state == "processing" assert decide(10, 10, False, False).terminate_requested assert not decide(11, 10, True, True).deliver assert decide(9, 10, True, True).deliver assert decide(9, 10, True, False).state == "invalid_artifact" 这个协议选择在等于截止时间时视为超时。你也可以采用另一种边界,但应写进测试。真实时间比较应使用适合计算间隔的单调时钟,不能让系统时区或墙上时钟调整改变任务预算。 超时后留下哪些证据 需要知道的事 用途 注意 超时阶段 区分下载、解析或翻译 不只记一个 timeout 终止是否确认 防止后台遗留工作 请求终止不等于已退出 临时产物身份 避免误交半成品 不与正式结果共用状态 重试次数 控制总预算 不能每次重新获得无限时间 重试不是自动的下一步 若相同输入每次都在同一步耗尽资源,应隔离样本并排查复杂度问题。只有失败原因可恢复、调用具备适当幂等性时,重试才有意义。用户也应该能区分“服务忙,正在重试”和“输入不支持,需要处理”。 对外翻译任务保留自己的截止策略 接入 OmniDoc(产品入口) 时,客户端等待时间、平台处理状态与文件下载状态应分开表示。页面停止等待,不意味着远端任务不存在;再次点击按钮之前应先查询已知任务,避免重复消耗或产生难以对应的多个结果。 参考: Python subprocess 的超时行为 。 - 1 -
更新于 2026-10-08