捕获信号时如何正确等待 bash 子进程完成

How to properly wait for bash child process to complete when trapping signals

我们有一个包装脚本,可以在后台启动 DelayedJob worker。此脚本会等到 DelayedJob worker 完成后再退出。包装器脚本是 Docker 容器的主要入口点,并设置 DJ 工作人员 运行 所需的一些环境。

我们注意到,尽管在发出 Docker 停止时,Docker 容器应该等到 DJ worker 正常退出(或直到最大超时),但这并没有发生。容器立即退出。

发出 Docker 停止对容器的调用将 SIGTERM 发送到主进程,即包装器脚本。在包装器脚本中,我们捕获 SIGTERM 并将信号传递给 DJ 工作进程。

这还是不行。我使用简单的 Bash 脚本创建了一个测试用例来说明问题。

脚本 p1:

#!/bin/bash
echo "P1: starting p1 and running p2 in bg"
exit_script() {
  echo "P1: Caught sigterm in p1, sending TERM to p2"
  kill -TERM $child
}

trap exit_script SIGINT SIGTERM

./p2 &
child=$!

echo "P1: waiting for p2 ($child)"
wait $child

echo "P1: Finished waiting for p2, exiting p1"

脚本 p2:

#!/bin/bash
echo "P2: starting p2"
exit_script() {
  echo "P2: Caught sigterm"
  NEXT_WAIT_TIME=0
  until [ $NEXT_WAIT_TIME -eq 10 ]; do
    echo "P2: EXIT_SCRIPT loop $NEXT_WAIT_TIME"
    sleep $(( NEXT_WAIT_TIME++ ))
  done  
  exit
}

trap exit_script SIGINT SIGTERM

echo "P2: Sleeping for a while"

NEXT_WAIT_TIME=0
until [ $NEXT_WAIT_TIME -eq 10 ]; do
  echo "P2: Main Loop $NEXT_WAIT_TIME"
  sleep $(( NEXT_WAIT_TIME++ ))
done

echo "P2: Finished sleeping in p2"

输出:

MBP:$ ./p1
P1: starting p1 and running p2 in bg
P1: waiting for p2 (74039)
P2: starting p2
P2: Sleeping for a while
P2: Main Loop 0
P2: Main Loop 1
P2: Main Loop 2
P2: Main Loop 3
P2: Main Loop 4
P1: Caught sigterm in p1, sending TERM to p2
P1: Finished waiting for p2, exiting p1
MBP:$ P2: Caught sigterm
P2: EXIT_SCRIPT loop 0
P2: EXIT_SCRIPT loop 1
P2: EXIT_SCRIPT loop 2
P2: EXIT_SCRIPT loop 3
P2: EXIT_SCRIPT loop 4
P2: EXIT_SCRIPT loop 5
P2: EXIT_SCRIPT loop 6
P2: EXIT_SCRIPT loop 7
P2: EXIT_SCRIPT loop 8
P2: EXIT_SCRIPT loop 9

如您所见,p1 脚本调用 wait 之后的行在捕获信号时调用的 exit_script 函数中的代码之前执行。

一个解决方案是用检查子 PID 是否存在的超时循环替换 wait,但为什么 wait 没有按预期工作? wait的用法不正确吗?

等待被传入信号打断,没有重新开始。您应该能够添加另一个等待调用以强制它完成等待。不过可能有更好的方法。

echo "P1: waiting for p2 ($child)"
wait $child
wait $child

echo "P1: Finished waiting for p2, exiting p1"