使用命名管道时如何避免僵尸进程?

使用命名管道时如何避免僵尸进程?

我们通常使用控制操作符在后台执行 FIFO 文件的写入工作&。像下面这样的东西。

if [ ! -p "/tmp/mysqld.init" ]; then
    mkfifo /tmp/mysqld.init
fi

echo "something" > /tmp/mysqld.init &

exec mysqld --init-file=/tmp/mysqld.init

但是当读出 fifo 文件时,该echo进程将成为僵尸进程。如何避免呢?

笔记这个脚本是一个 docker 入口点脚本,我没有合适的僵尸处理程序。 Mysqld 总是采用 pid 1。如下所示。

  PID  PPID USER     STAT   VSZ %VSZ CPU %CPU COMMAND
    1     0 mysql    S     383m  19%   0   0% mysqld --init-file=/tmp/mysqld.init
   40     0 root     R     1532   0%   1   0% top
    7     1 root     Z        0   0%   0   0% [entrypoint.sh]

也许我可以使用 tini 作为 docker 的 init 系统,但没有它如何实现?双叉?

答案1

使用 Bourne Shell 或 Bash,您可以用来trap关闭命名管道。例如:

#!/bin/sh
set -eu
trap 'close_fifo; echo 1>&2 "!!! Unexpected signal termination."; exit 66' HUP TERM INT STOP

LOG_PATH="/tmp/acme.log"
touch "{$LOG_PATH}"

# Setup fifo and pipe input to log file with tee
FIFO=$(mktemp -u);
mkfifo $FIFO
tee -ia "{$LOG_PATH}" < $FIFO &

# Capture tee's process ID for the wait command.
TEE_PID=$!
# Redirect the rest of the stderr and stdout to our named pipe.
exec > $FIFO 2>&1

log() { echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*"; }

close_fifo() {
    # close the stderr and stdout file descriptors.
    exec 1>&- 2>&-

    # Wait for tee to finish since now that other end of the pipe has closed.
    [ -n $TEE_PID ] && wait $TEE_PID

    rm -f $FIFO
}

main() {
  // ---
  // Main procedure here...
}

main
close_fifo

这将捕获任何信号并关闭终止信号上的管道。任何标准输出/错误都将使用命名管道记录到文件中。

Fifo处理参考:https://superuser.com/a/86961/252171

答案2

问题是出奇地更复杂超出大多数人的预期。

简而言之,您需要使用 init,或者编写 init 的替代品来实现收割和下游信号传播,或者找到一种将数据传递到 mysqld 的新方法,或者找到一种接受它的方法(这需要理解和限制你的僵尸创造率)。

当父进程未收集(获取)退出状态时,僵尸进程就会出现。 exec用 mysqld 替换当前进程(一个 shell),它不希望继承子进程。

如果您在后台启动 mysqld,并让 shell 脚本保留以获取子级,那么它还需要处理将接收到的信号传播到 mysqld 并等待它们响应。如果不这样做,将导致 mysql 在 shell 脚本退出时无法正常终止。这种收获和繁殖通常是 的工作init

您可以通过打开读取句柄来立即退出 echo,如下所示:

echo "something" > /tmp/fifo &
exec 3< /tmp/fifo

...但这会产生一个新问题:通常echo会关闭 fifo,但现在不能:它退出得太快。这意味着mysql在读取它时会阻塞。可以进行非阻塞读取并实现超时等等,但我们可以假设 mysql 不会,因为它可能期望--init-file表现得像常规文件一样。

最后一个选择是忍受它。如果我们谈论的是 1 个进程或 100 个进程,那就没问题。但进程表是有限的,每个僵尸进程都会消耗其中的一个条目。如果您不管理僵尸计数,并且进程表已满,则无法创建任何新进程。

我链接的博客文章有很多很好的细节,并插入了由作者创建的一个小的 init 替换,以解决您的确切问题。

相关内容