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 时,应核对该模式的限制:它不会给被管理命令的子进程统一施加超时。批处理任务不要为了消除终端相关现象,顺手加上这个选项并假定覆盖范围不变。
修改前保留原调用配置,先验证正常完成、收到首个信号退出、进入强制终止三种路径。若强制终止留下半成品或不明确的外部结果,先对账,再决定重试。恢复旧命令或延长时限只会影响后续运行,不会撤销已经发生的写入。


