wait3(waitpid别名)返回-1,errno设置为ECHILD,但它不应该返回
背景是这样的。我们有一个wait3(waitpid别名)返回-1,errno设置为ECHILD,但它不应该返回,c,linux,system-calls,zombie-process,waitpid,C,Linux,System Calls,Zombie Process,Waitpid,背景是这样的。我们有一个wait3()调用,它等待AOF重写子级在磁盘上创建新的AOF版本。完成子项后,将通过wait3()通知父项,以便用新的AOF替换旧的AOF 然而,在上述问题的上下文中,用户通知我们有一个bug。我对Redis 3.0的实现进行了一些修改,以便在wait3()返回-1时清楚地记录日志,而不是因为这种意外情况而崩溃。这就是明显发生的情况: 当我们有挂起的子项等待时,将调用wait3() SIGCHLD应设置为SIG_DFL,Redis中根本没有代码设置此信号,因此这是默认行
wait3()
调用,它等待AOF重写子级在磁盘上创建新的AOF版本。完成子项后,将通过wait3()
通知父项,以便用新的AOF替换旧的AOF
然而,在上述问题的上下文中,用户通知我们有一个bug。我对Redis 3.0的实现进行了一些修改,以便在wait3()
返回-1时清楚地记录日志,而不是因为这种意外情况而崩溃。这就是明显发生的情况:
wait3()
SIGCHLD
应设置为SIG_DFL
,Redis中根本没有代码设置此信号,因此这是默认行为wait3()
按预期成功工作wait3()
开始返回-1wait3()
是不可能的,因为在创建AOF子项时,我们将server.AOF\u child\u pid
设置为pid的值,并且只有在成功调用wait3()
后才重置它
因此,wait3()
应该没有理由使用-1和ECHILD
失败,但它确实失败了,因此可能是由于某些意外原因没有创建僵尸子级
假设1:Linux在某些奇怪的情况下可能会丢弃僵尸子系统,例如,因为内存压力?看起来不合理,因为僵尸只是附加了元数据,但谁知道呢
注意,我们使用WNOHANG
调用wait3()。如果默认情况下SIGCHLD
设置为SIG_DFL
,那么导致失败并返回-1和ECHLD
的唯一条件应该是没有僵尸可用于报告信息
假设2:可能发生的另一件事是,在第一个孩子死后,SIGCHLD
处理程序设置为SIG\u IGN
,导致wait3()
返回-1和ECHLD
假设3:是否有办法从外部移除僵尸子级?可能该用户有某种脚本,可以在后台删除僵尸进程,这样信息就不再可用于wait3()
?据我所知,如果父级不等待僵尸(使用waitpid
或处理信号),并且没有忽略SIGCHLD
,则永远不可能删除僵尸,但可能有某种特定于Linux的方法
假设4:Redis代码中实际上存在一些bug,因此我们在没有正确重置状态的情况下第一次成功地wait3()
了孩子,之后我们一次又一次地调用wait3()
,但不再有僵尸,因此它返回-1。分析代码看起来不可能,但也许我错了
另一件重要的事:我们过去从未观察到这一点。显然,这种情况只发生在这个特定的Linux系统中
更新:Yossi Gottlieb建议Redis进程中的另一个线程出于某种原因接收SIGCHLD
(通常不会发生,仅在此系统上)。我们已经在bio.c
线程中屏蔽了SIGALRM
,也许我们也可以尝试从I/O线程屏蔽SIGCHLD
附录:Redis代码的选定部分
其中调用wait3():
/* Check if a background saving or AOF rewrite in progress terminated. */
if (server.rdb_child_pid != -1 || server.aof_child_pid != -1) {
int statloc;
pid_t pid;
if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
int exitcode = WEXITSTATUS(statloc);
int bysignal = 0;
if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);
if (pid == -1) {
redisLog(LOG_WARNING,"wait3() returned an error: %s. "
"rdb_child_pid = %d, aof_child_pid = %d",
strerror(errno),
(int) server.rdb_child_pid,
(int) server.aof_child_pid);
} else if (pid == server.rdb_child_pid) {
backgroundSaveDoneHandler(exitcode,bysignal);
} else if (pid == server.aof_child_pid) {
backgroundRewriteDoneHandler(exitcode,bysignal);
} else {
redisLog(REDIS_WARNING,
"Warning, detected child with unmatched pid: %ld",
(long)pid);
}
updateDictResizePolicy();
}
} else {
backgroundRewriteDoneHandler的选定部分
:
void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
if (!bysignal && exitcode == 0) {
int newfd, oldfd;
char tmpfile[256];
long long now = ustime();
mstime_t latency;
redisLog(REDIS_NOTICE,
"Background AOF rewrite terminated with success");
... more code to handle the rewrite, never calls return ...
} else if (!bysignal && exitcode != 0) {
server.aof_lastbgrewrite_status = REDIS_ERR;
redisLog(REDIS_WARNING,
"Background AOF rewrite terminated with error");
} else {
server.aof_lastbgrewrite_status = REDIS_ERR;
redisLog(REDIS_WARNING,
"Background AOF rewrite terminated by signal %d", bysignal);
}
cleanup:
aofClosePipes();
aofRewriteBufferReset();
aofRemoveTempFile(server.aof_child_pid);
server.aof_child_pid = -1;
server.aof_rewrite_time_last = time(NULL)-server.aof_rewrite_time_start;
server.aof_rewrite_time_start = -1;
/* Schedule a new rewrite if we are waiting for it to switch the AOF ON. */
if (server.aof_state == REDIS_AOF_WAIT_REWRITE)
server.aof_rewrite_scheduled = 1;
}
如您所见,所有代码路径都必须执行将服务器.aof_child_pid
重置为-1的cleanup
代码
问题期间Redis记录的错误
21353:C 29 Nov 04:00:29.957*AOF重写:写时复制所使用的8MB内存
27848:M 29 Nov 04:00:30.133^@wait3()返回错误:没有子进程。rdb_child_pid=-1,aof_child_pid=21353
如您所见,aof_child_pid不是-1。TLDR:您目前依赖于
信号的未指定行为
(2);使用sigaction
(小心)代替
首先,SIGCHLD
很奇怪。从forsigaction
POSIX.1-1990不允许将SIGCHLD
的操作设置为SIG\u IGN
。POSIX.1-2001允许这种可能性,因此可以使用忽略SIGCHLD
来防止僵尸的创建(参见wait
(2))。然而,忽略SIGCHLD
的历史BSD和System V行为是不同的,因此确保终止的孩子不会变成僵尸的唯一完全可移植的方法是捕获SIGCHLD
信号并执行等待
(2)或类似操作
下面是wait
(2)的位:
POSIX.1-2001规定,如果SIGCHLD
的处置设置为SIG\u IGN
,或者SA\u NOCLDWAIT
标志设置为SIGCHLD
(参见sigaction
(2)),则终止的子级不会变成僵尸,并调用wait()
或waitpid()
将一直阻止,直到所有子项都终止,然后失败,errno设置为ECHILD
。(原始POSIX标准未指定将SIGCHLD
设置为SIG\u IGN
的行为。请注意,尽管SIGCHLD
的默认配置为“忽略”,但显式将配置设置为SIG\u IGN
会导致对僵尸进程子级的不同处理。)Linux 2.6符合此规范。但是,Linux 2.4(及更早版本)没有:如果wait()
sigset_t set; struct sigaction sa; /* block all signals */ sigfillset (&set); pthread_sigmask (SIG_BLOCK, &set, NULL); /* Set up the structure to specify the new action. */ memset (&sa, 0, sizeof (struct sigaction)); sa.sa_handler = handlesignal; /* signal handler for INT, TERM, HUP, USR1, USR2 */ sigemptyset (&sa.sa_mask); sa.sa_flags = 0; sigaction (SIGINT, &sa, NULL); sigaction (SIGTERM, &sa, NULL); sigaction (SIGHUP, &sa, NULL); sigaction (SIGUSR1, &sa, NULL); sigaction (SIGUSR2, &sa, NULL); sa.sa_handler = SIG_IGN; sigemptyset (&sa.sa_mask); sa.sa_flags = 0; sigaction (SIGPIPE, &sa, NULL); /* I don't care about SIGPIPE */ sa.sa_handler = SIG_DFL; sigemptyset (&sa.sa_mask); sa.sa_flags = 0; sigaction (SIGCHLD, &sa, NULL); /* I want SIGCHLD to be handled by SIG_DFL */ pthread_sigmask (SIG_UNBLOCK, &set, NULL);