DEBUG.WIKI
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
雨云;给项目一台云服务器
RackNerd;$21.99/年 · 1GB KVM

GNU timeout 分两次执行,无法先发 INT 再强制结束同一进程

GNU timeout 的 --kill-after 从首个信号发出时开始计时。将 INT 和 KILL 安排在一次调用中,区分主时长、追加宽限时间、返回状态和 foreground 的子进程限制。

希望程序运行 5 秒后收到 SIGINT,到总计 10 秒仍未退出时再收到 SIGKILL,却写成两个连续的 timeout 命令。这种写法不会给同一次运行安排两个阶段:每次 timeout 都会启动自己后面的命令。如果连等待时长也省略,参数本身就不完整。

GNU coreutils 的 timeout 可以在一次调用里完成这两个阶段。这里讨论支持 --kill-after 的 GNU 实现;先用 timeout --version 和 timeout --help 确认本机程序,不将同名的其他实现视为完全兼容。

主时长与追加宽限时间分别填写

下面是调用结构示例,/path/to/your-program 是需要替换的可执行程序路径。只在隔离测试任务中验证,不能拿正在处理真实数据的程序试强制终止:

timeout --verbose --signal=INT --kill-after=5s 5s /path/to/your-program

第一个 5s 属于 --kill-after,表示首个信号发出后,再等待多少时间。第二个 5s 是必需的主时长,表示开始运行后多久发送首个信号。其余参数放在程序路径后面。

在程序一直不退出的情况下,这个配置会在约 5 秒时发送 INT,再过约 5 秒发送 KILL,合计约 10 秒。实际时刻受系统调度影响,不能作为精密计时器。如果程序收到首个信号就退出,无需等到第二阶段。

因此,写成 --kill-after=10s 5s 表达的是约 15 秒的两段时间,不是总计 10 秒。任一对应时长设为 0 都会禁用相关超时机制;不要用 0 表达“立即强杀”。

默认首个信号是 TERM。只有程序的退出协议确实要求 INT 时才指定它,不能把终端 Ctrl-C 的习惯当成所有服务的停止协议。使用 KILL 会跳过程序正常的清理处理,宽限时间应依据应用需要留出,而不是照抄示例中的 5 秒。

两条命令监控的不是同一次运行

下面这种结构,即使补上时长,也不符合要求:

timeout --signal=INT 5s /path/to/your-program
timeout --signal=KILL 10s /path/to/your-program

第一行结束后,第二行会再次启动程序。第一行若一直等不到退出,执行也不会自然走到第二行。不要靠再次启动程序来补救第一次运行的停止失败,否则可能多做一遍业务操作。

同样,timeout 后面要跟可执行命令,不能只传一个已存在进程的 PID,期待它接管那条进程。已经由服务管理器启动的任务,应沿用管理器的停止与超时配置,先确认任务归属。

验证发送了什么信号,并保留返回状态

--verbose 会把超时发送信号的诊断写到标准错误。检查测试任务自己的退出日志,判断首个信号是否触发收尾;出现强制结束只能说明需要进一步核对结果,不能算清理成功。

不使用 --preserve-status 时,发生超时通常返回 124;涉及 KILL 时可能返回 137。程序在时限内结束,则通常返回它自己的状态。125、126、127 分别对应 timeout 自身失败、找到命令但不能执行、找不到命令,应与业务失败分开处理。

尤其不能单凭 137 断定被管理程序已经消失:timeout 自身收到 KILL 也会产生这个状态,此时目标可能仍在运行。验收应结合目标进程与任务状态,不只看退出码。不要将这条命令当成清理任意脱离管理关系的后台进程的保证。

交互程序还涉及终端控制。确实需要 --foreground 时,应核对该模式的限制:它不会给被管理命令的子进程统一施加超时。批处理任务不要为了消除终端相关现象,顺手加上这个选项并假定覆盖范围不变。

修改前保留原调用配置,先验证正常完成、收到首个信号退出、进入强制终止三种路径。若强制终止留下半成品或不明确的外部结果,先对账,再决定重试。恢复旧命令或延长时限只会影响后续运行,不会撤销已经发生的写入。