You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何wait $PID收到SIGTERM后立即返回而非等待进程终止?

信号传播场景下wait命令的异常行为与解决方法

问题概述

在shell脚本中运行子进程并传播终止信号(SIGINT/SIGTERM/SIGQUIT)时,发现使用wait $CHILD_PID会在陷阱处理函数收到信号的瞬间立即返回,必须再次执行wait $CHILD_PID才能获取子进程的实际退出码:

  • 第一次wait返回状态码为128+信号值(如SIGINT对应130)
  • 第二次wait返回子进程的实际退出码

测试环境与脚本

信号传播脚本(signal-propagate)

#!/bin/sh

_term() {
  signal=$1
  echo "🔴 caught $signal, sending it to child pid $CHILD..."
  kill -$signal $CHILD 2>/dev/null
}

# 注册终止信号
trap "_term TERM" SIGTERM
trap "_term QUIT" SIGQUIT
trap "_term INT" SIGINT

# 后台启动命令
$@ &

# 获取子进程PID
CHILD=$!

# 等待子进程结束
wait $CHILD
status=$?
echo "1 First Wait: child pid $CHILD exited with status $status"

wait $CHILD
status=$?
echo "2 Second wait: child pid $CHILD exited with status $status"

exit $status

测试用子进程脚本(work.php)

<?php

function sig_handler($signo)
{
    echo "🟡Signaler Handler Invoked! \n";
     switch ($signo) {
         case SIGINT:
            echo "Caught SIGINT CTRL-C...\n";
            echo "Shutdown Function Invoked, sleeping \n";
            sleep(5);
            echo "Shutdown Function done \n";
            exit(12);
            break;
         default:
     // 处理其他信号
     }

}

// 启用异步信号处理
pcntl_async_signals(true);

// 设置信号处理函数
pcntl_signal(SIGINT,  "sig_handler");

echo "Doing some work... \n";
sleep(1000);

?>

测试执行与输出

执行命令:

./signal-propagate php work.php

输出结果:

Doing some work...
^C🟡Signaler Handler Invoked!
Caught SIGINT CTRL-C...
Shutdown Function Invoked, sleeping
🔴 caught INT, sending it to child pid 47270...
1 First Wait: child pid 47270 exited with status 130
Shutdown Function done
2 Second wait: child pid 47270 exited with status 12

问题原因分析

根据POSIX标准对wait的定义,wait会等待指定进程终止,但当shell收到信号并触发陷阱函数时,正在阻塞的wait命令会被中断,提前返回并给出128+信号值的状态码,此时子进程只是收到了信号,还在执行优雅终止逻辑并未真正退出。后续再次调用wait $CHILD_PID时,shell会读取子进程已经缓存的退出状态,返回其实际退出码。

解决方案:循环等待直到子进程真正终止

为确保脚本等待子进程完全终止并获取正确的退出码,需将wait放在循环中,直到子进程状态被正确获取(即wait返回的状态码不是信号中断导致的128+值)。修正后的脚本如下:

#!/bin/sh

_term() {
  signal=$1
  echo "🔴 caught $signal, sending it to child pid $CHILD..."
  kill -$signal $CHILD 2>/dev/null
}

# 注册终止信号
trap "_term TERM" SIGTERM
trap "_term QUIT" SIGQUIT
trap "_term INT" SIGINT

# 后台启动命令
$@ &

# 获取子进程PID
CHILD=$!

# 循环等待直到子进程真正终止
while :; do
    wait $CHILD
    status=$?
    # 检查是否是信号中断导致的返回(状态码>=128)
    if [ $status -lt 128 ]; then
        break
    fi
    # 否则继续等待子进程退出
done

echo "Final child exit status: $status"
exit $status

说明

  • 循环中每次调用wait,如果是被信号中断返回(状态码>=128),则继续等待;直到子进程真正退出,wait返回其实际退出码(<128),才退出循环。
  • 该方案兼容sh/bash/zsh,符合仅支持sh的运行环境要求,同时满足循环运行子进程的实际场景需求。

内容的提问来源于stack exchange,提问作者Sherif Abdel-Naby

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 02:20:02