【问题标题】:Killing child process after timeout, with multiple children?超时后杀死子进程,有多个孩子?
【发布时间】:2020-09-13 03:44:50
【问题描述】:

我已使用此代码示例在超时期限后自动终止子进程,但是当有多个子进程时它不起作用(此代码在线程池中运行)。一个子进程终止后,以下子进程在超时期限后不会终止,并继续直到它们正常退出。所以我会想象电线在某处交叉。我从这个问题的答案得出了这个结论:Waitpid equivalent with timeout?。完整代码示例:
https://www.linuxprogrammingblog.com/code-examples/signal-waiting-sigtimedwait

/* The program creates a child process and waits for it to finish. If a timeout
 * elapses the child is killed. Waiting is done using sigtimedwait(). Race
 * condition is avoided by blocking the SIGCHLD signal before fork().
 */
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

static pid_t fork_child (void)
{
    int p = fork ();

    if (p == -1) {
        perror ("fork");
        exit (1);
    }

    if (p == 0) {
        puts ("child: sleeping...");
        sleep (10);
        puts ("child: exiting");
        exit (0);
    }

    return p;
}

int main (int argc, char *argv[])
{
    sigset_t mask;
    sigset_t orig_mask;
    struct timespec timeout;
    pid_t pid;

    sigemptyset (&mask);
    sigaddset (&mask, SIGCHLD);

    if (sigprocmask(SIG_BLOCK, &mask, &orig_mask) < 0) {
        perror ("sigprocmask");
        return 1;
    }

    pid = fork_child ();

    timeout.tv_sec = 5;
    timeout.tv_nsec = 0;

    do {
        if (sigtimedwait(&mask, NULL, &timeout) < 0) {
            if (errno == EINTR) {
                /* Interrupted by a signal other than SIGCHLD. */
                continue;
            }
            else if (errno == EAGAIN) {
                printf ("Timeout, killing child\n");
                kill (pid, SIGKILL);
            }
            else {
                perror ("sigtimedwait");
                return 1;
            }
        }

        break;
    } while (1);

    if (waitpid(pid, NULL, 0) < 0) {
        perror ("waitpid");
        return 1;
    }

    return 0;
}

【问题讨论】:

  • 请比“不起作用”更好地描述问题。确切的预期行为和实际行为是什么?例如,您描述的问题是针对多个子进程,但显示的代码从不创建多个子进程。
  • “此代码在线程池中运行”。您需要显示minimal reproducible example。谁说线程代码没有问题?所以我们需要查看准确可以重现问题的代码。
  • 如果您有多个线程尝试使用该方法同时等待子进程,那么遇到问题我并不感到惊讶。然后,您不能假设特定子级的 SIGCHLD 将由尝试等待该子级的线程处理。
  • @JohnBollinger 在多线程中等待子进程的更好方法是什么?
  • ... 执行子运行时间限制?这很棘手。您肯定需要共享数据来跟踪哪个线程正在等待哪个孩子,以及在杀死孩子之前愿意等待多长时间。然后我会考虑有一个单独的线程专门用于代表所有其他人处理子进程管理职责。这会很复杂,但我认为不如尝试没有专门的线程来做。

标签: c


【解决方案1】:

我正要建议使用waitpid() 进行轮询,使用WNOHANGflag 来防止它被阻塞,但我想我想出了更好的东西:来自孩子的管道,以及来自父母的select()

具体来说,

  1. 父级在分叉之前创建一个管道。
  2. (成功)分叉后,父级关闭其管道写入端的副本。
  3. 然后父级使用select() 和所需的超时时间来等待管道变得可用于读取。孩子实际上不需要写信给管道,甚至不需要知道它。当子进程终止时,写入端将关闭,这将使读取端可用于读取(EOF 信号)。
  4. 如果超时到期,则父母会向孩子发送SIGTERM
  5. 无论哪种方式,父母都会使用waitpid() 收集孩子。

如果您关心孩子是否正常终止,请务必检查waitpid提供的状态码,因为它有可能在超时到期和SIGTERM被交付之间正常终止,在这种情况下我认为waitpid 将(正确地)显示它已正常终止。当然,它也可以由于接收到来自其他来源的信号或其他原因(例如SIGSEGV)而终止。

以下是这种方法的要点。您需要添加详细信息以使其适合您,并通过错误处理使其健壮:

pid_t child_pid;
int term_pipe[2];

pipe(term_pipe);
child_pid = fork();

switch (child_pid) {
    case -1:
        // handle error
        break;
    case 0:
        // child
        // ... whatever ...
        exit(0);
}
close(term_pipe[1]);  // essential

fd_set read_fds;
struct timeval timeout = { /* timeout */ };
int result;

FD_ZERO(read_fds);
FD_SET(term_pipe[0], read_fds);
result = select(term_pipe[0] + 1, &read_fds, NULL, NULL, &timeout);

if (result == 0) {
    // timeout
    kill(child_pid, SIGTERM);
} else if (result < 0) {
    // handle error
    // in particular, you may need to resume waiting if the error is EINTR
} else {
    // the child terminated within the timeout
    assert(result == 1);
}

// don't forget to close the read end of the pipe
close(term_pipe[0]);

int status;
pid_t collected_pid = waitpid(child_pid, &status, 0);

if (collected_pid < 0) {
    // handle error
} else {
    assert(collected_pid == child_pid);
    // ... test the status to see how the child terminated ...
    // ...
}

请注意,您希望正常处理子终止,因此不要像您的代码当前那样阻止 SIGCHLD

【讨论】:

  • 啊,奇怪。我原以为您至少可以对自己问题的答案进行投票。
猜你喜欢
  • 1970-01-01
  • 2019-12-09
  • 2012-05-30
  • 2012-02-25
  • 2012-05-03
  • 1970-01-01
  • 2010-12-08
  • 2011-05-08
  • 1970-01-01
相关资源
最近更新 更多