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中根本没有代码设置此信号,因此这是默认行为
  • 当第一次AOF重写发生时,
    wait3()
    按预期成功工作
  • 从第二次AOF重写(创建的第二个子级)开始,
    wait3()
    开始返回-1
  • 如果没有挂起的子项,那么在当前代码中调用
    wait3()
    是不可能的,因为在创建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
    很奇怪。从for
    sigaction

    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);