编辑:如果管理shell脚本的子进程真的纯粹是“意见”的问题......难怪有这么多可怕的shell脚本。感谢您继续这样做。
我无法理解与 Linux 中的子进程相关的 SIGTERM 的常规处理方式。
我正在 Bash 中编写一个命令行实用程序。
看起来像
command1
command2
command3
很简单,对吧?
但是,如果我的程序收到 SIGTERM 信号,Bash 脚本将结束,但当前子进程(例如 command2
)将继续。
但是通过更多代码,我可以像这样编写我的程序
trap 'jobs -p | xargs -r kill' TERM
command1 &
wait
command2 &
wait
command3 &
wait
这会将 SIGTERM 传播到当前正在运行的子进程。我并不经常看到这样编写的 Bash 脚本,但这就是它所需要的。
我应该:
- 每次创建子进程时都用第二种风格编写程序?
- 或者希望用户在想要发送 SIGTERM 时在进程组中启动我的程序?
针对 child 的 SIGTERM 流程管理职责的最佳实践/惯例是什么?
最佳答案
tl;博士
第一种方式。
如果一个进程启动一个子进程并等待它完成(示例),则不需要任何特殊操作。
如果进程启动子进程并可能提前终止它,则它应该在新进程组中启动该子进程并向该组发送信号。
详细信息
奇怪的是,对于这种情况的应用频率(例如每个 shell 脚本),我找不到关于约定/最佳实践的好答案。
一些扣除:
创建进程组并发出信号通知非常常见。特别是,交互式 shell 可以做到这一点。因此(除非采取额外的步骤来防止),在非常正常的情况下,进程的子进程可以随时接收 SIGINT 信号。
为了支持尽可能少的范式,始终依赖它似乎是有意义的。
这意味着第一种风格是可以的,进程管理的负担被放在在常规操作期间故意终止其子进程的进程上(这种情况相对不太常见)。
另请参阅下面的“案例研究:超时”以获取更多证据。
如何做
虽然问题的角度是从普通被调用程序的要求出发,但这个答案提出了一个问题:如何在新进程组中启动进程(在非普通情况下)那个人希望提前中断该过程)?
这对于某些语言来说很容易,而对于另一些语言来说则很困难。我创建了一个实用程序 run-pgrp
来帮助处理后一种情况。
#!/usr/bin/env python3
# Run the command in a new process group, and forward signals.
import os
import signal
import sys
pid = os.fork()
if not pid:
os.setpgid(0, 0)
os.execvp(sys.argv[1], sys.argv[1:])
def receiveSignal(sig, frame):
os.killpg(pid, sig)
signal.signal(signal.SIGINT, receiveSignal)
signal.signal(signal.SIGTERM, receiveSignal)
_, status = os.waitpid(-1, 0)
sys.exit(status)
调用者可以使用它来包装它提前终止的进程。
Node.js 示例:
const childProcess = require("child_process");
(async () => {
const process = childProcess.spawn(
"run-pgrp",
["bash", "-c", "echo start; sleep 600; echo done"],
{ stdio: "inherit" }
);
/* leaves orphaned process
const process = childProcess.spawn(
"bash",
["-c", "echo start; sleep 600; echo done"],
{ stdio: "inherit" }
);
*/
await new Promise(res => setTimeout(res, /* 1s */ 1000));
process.kill();
if (process.exitCode == null) {
await new Promise(res => process.on("exit", res));
}
})();
在该程序结束时, sleep 进程终止。如果直接调用命令而不使用run-pgrp
,则 sleep 进程将继续运行。
案例研究:超时
GNU timeout
实用程序是一个可以终止其子进程的程序。
值得注意的是,它在 a new process group 中运行子进程。 。这支持了这样的结论:应该在潜在的中断之前创建一个新的进程组。
有趣的是,超时也将自身放入进程组中,以避免转发信号的复杂性,但会导致一些奇怪的行为。 https://unix.stackexchange.com/a/57692/56781
例如,在交互式 shell 中,运行
bash -c "echo start; timeout 600 sleep 600; echo done"
尝试中断此操作 (Ctrl+C)。它没有响应,因为 timeout
永远不会收到信号!
相比之下,我的 run-pgrp
实用程序将自身保留在原始进程组中,并将 SIGINT/SIGTERM 转发到子组。
关于linux - 信号处理/子进程的传统做法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70994907/