![](/img/trans.png)
[英]Sending kill -s USR1 from bash script stops the process while doing the same from terminal does not
[英]Why may USR1 signals sent from background jobs in a Bash script not be reliably received by the parent shell process waiting for their completion?
我有一个 Bash 脚本并行运行一堆后台作业。 在某些情况下,在后台作业完成之前,它会向生成的 Bash 进程发送一个 USR1 信号(例如,通知作为作业一部分运行的某个进程已以非零退出代码终止)。
在简化形式中,该脚本等同于如下所示的脚本。 这里,为了简单起见,每个后台作业总是在完成之前无条件地发送一个 USR1 信号(通过signalparent()
函数)。
signalparent() { kill -USR1 $$; }
handlesignal() { echo 'USR1 signal caught' >&2; }
trap handlesignal USR1
for i in {1..10}; do
{
sleep 1
echo "job $i finished" >&2
signalparent
} &
done
wait
当我运行上述脚本时(至少在 macOS 11.1 上使用 Bash 3.2.57),我观察到一些我无法解释的行为,这让我认为 Bash 作业管理和信号捕获之间的相互作用是我忽略的.
具体来说,我想获得对以下行为的解释。
几乎总是,当我运行脚本时,我看到 output(来自handlesignal()
函数)中的“信号捕获”行少于在for
循环中启动的作业——大多数时候它是其中的一到四个为正在启动的十个作业打印的行数。
为什么在wait
调用完成时,仍有后台作业的信号kill
命令尚未执行?
同时,每隔一段时间,在脚本的某些调用中,我观察到kill
命令(来自signalparent()
函数)报告有关运行脚本的原始进程(即具有$$
PID 的那个)的错误不再存在 - 请参阅下面的 output。
为什么在父 shell 进程已经终止时,有哪些作业的信号kill
命令仍在运行? 我的理解是,由于wait
调用,父进程不可能在所有后台作业完成之前终止。
job 2 finished job 3 finished job 5 finished job 4 finished job 1 finished job 6 finished USR1 signal caught USR1 signal caught job 10 finished job 7 finished job 8 finished job 9 finished bash: line 3: kill: (19207) - No such process bash: line 3: kill: (19207) - No such process bash: line 3: kill: (19207) - No such process bash: line 3: kill: (19207) - No such process
这两种行为都向我表明存在某种竞争条件,我不太了解其起源。 如果有人能在这些方面启发我,我将不胜感激,甚至可能建议如何更改脚本以避免这种竞争条件。
Bash 参考手册中对此进行了如下说明。
当 bash 通过
wait
builtin 等待异步命令时,接收到已设置陷阱的信号将导致wait
builtin 立即返回,退出状态大于 128,然后立即执行陷阱。
因此,您需要重复wait
,直到它返回 0 以确保所有后台作业都已终止,例如:
until wait; do
:
done
我的理解是,由于
wait
调用,父进程不可能在所有后台作业完成之前终止。
那是一种误解; 当后台有正在运行的作业时,由于接收到设置了陷阱的信号, wait
可能会返回,这可能会导致程序正常完成,从而使这些作业成为孤立的。
关于'几乎总是,当我运行脚本时,我在输出中看到更少的“信号捕获”行' -
根据信号(7) :
标准信号不排队。 如果在该信号被阻塞时生成了一个标准信号的多个实例,那么只有一个信号实例被标记为待处理(并且该信号在解除阻塞时只会被传递一次)。
更改脚本以使信号不会同时到达的一种方法如下:
signalparent() {
kill -USR1 $$
}
ncaught=0
handlesignal() {
(( ++ncaught ))
echo "USR1 signal caught (#=$ncaught)" >&2
}
trap handlesignal USR1
for i in {1..10}; do
{
sleep $i
signalparent
} &
done
nwaited=0
while (( nwaited < 10 )); do
wait && (( ++nwaited ))
done
这是在 macOS 10.15 上使用 Bash 5.1 修改的脚本的 output:
USR1 signal caught (#=1)
USR1 signal caught (#=2)
USR1 signal caught (#=3)
USR1 signal caught (#=4)
USR1 signal caught (#=5)
USR1 signal caught (#=6)
USR1 signal caught (#=7)
USR1 signal caught (#=8)
USR1 signal caught (#=9)
USR1 signal caught (#=10)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.