Waitpid equivalent with timeout?

前端 未结 10 1193
小鲜肉
小鲜肉 2020-11-27 12:10

Imagine I have a process that starts several child processes. The parent needs to know when a child exits.

I can use waitpid, but then if/when the paren

相关标签:
10条回答
  • 2020-11-27 12:45

    I thought that select will return EINTR when SIGCHLD signaled by on of the child. I belive this should work:

    while(1)
    {
      int retval = select(0, NULL, NULL, NULL, &tv, &mask);
      if (retval == -1 && errno == EINTR) // some signal
      { 
          pid_t pid = (waitpid(-1, &st, WNOHANG) == 0);
          if (pid != 0) // some child signaled
      }
      else if (retval == 0)
      {
          // timeout
          break;
      }
      else // error
    }
    

    Note: you can use pselect to override current sigmask and avoid interrupts from unneeded signals.

    0 讨论(0)
  • 2020-11-27 12:45

    I can use a signal handler for SIGCHLD, and in the signal handler do whatever I was going to do when a child exits, or send a message to a different thread to do some action. But using a signal handler obfuscates the flow of the code a little bit.

    In order to avoid race conditions you should avoid doing anything more complex than changing a volatile flag in a signal handler.

    I think the best option in your case is to send a signal to the parent. waitpid() will then set errno to EINTR and return. At this point you check for waitpid return value and errno, notice you have been sent a signal and take appropriate action.

    0 讨论(0)
  • 2020-11-27 12:47

    Instead of calling waitpid() directly, you could call sigtimedwait() with SIGCHLD (which would be sended to the parent process after child exited) and wait it be delived to the current thread, just as the function name suggested, a timeout parameter is supported.

    please check the following code snippet for detail

    
    static bool waitpid_with_timeout(pid_t pid, int timeout_ms, int* status) {
        sigset_t child_mask, old_mask;
        sigemptyset(&child_mask);
        sigaddset(&child_mask, SIGCHLD);
    
        if (sigprocmask(SIG_BLOCK, &child_mask, &old_mask) == -1) {
            printf("*** sigprocmask failed: %s\n", strerror(errno));
            return false;
        }
    
        timespec ts;
        ts.tv_sec = MSEC_TO_SEC(timeout_ms);
        ts.tv_nsec = (timeout_ms % 1000) * 1000000;
        int ret = TEMP_FAILURE_RETRY(sigtimedwait(&child_mask, NULL, &ts));
        int saved_errno = errno;
    
        // Set the signals back the way they were.
        if (sigprocmask(SIG_SETMASK, &old_mask, NULL) == -1) {
            printf("*** sigprocmask failed: %s\n", strerror(errno));
            if (ret == 0) {
                return false;
            }
        }
        if (ret == -1) {
            errno = saved_errno;
            if (errno == EAGAIN) {
                errno = ETIMEDOUT;
            } else {
                printf("*** sigtimedwait failed: %s\n", strerror(errno));
            }
            return false;
        }
    
        pid_t child_pid = waitpid(pid, status, WNOHANG);
        if (child_pid != pid) {
            if (child_pid != -1) {
                printf("*** Waiting for pid %d, got pid %d instead\n", pid, child_pid);
            } else {
                printf("*** waitpid failed: %s\n", strerror(errno));
            }
            return false;
        }
        return true;
    }
    

    Refer: https://android.googlesource.com/platform/frameworks/native/+/master/cmds/dumpstate/DumpstateUtil.cpp#46

    0 讨论(0)
  • 2020-11-27 12:47

    If your program runs only on contemporary Linux kernels (5.3 or later), the preferred way is to use pidfd_open (https://lwn.net/Articles/789023/ https://man7.org/linux/man-pages/man2/pidfd_open.2.html).

    This system call returns a file descriptor representing a process, and then you can select, poll or epoll it, the same way you wait on other types of file descriptors.

    For example,

    int fd = pidfd_open(pid, 0);
    struct pollfd pfd = {fd, POLLIN, 0};
    poll(&pfd, 1, 1000) == 1;
    
    0 讨论(0)
  • 2020-11-27 12:52

    This is an interesting question. I found sigtimedwait can do it.

    EDIT 2016/08/29: Thanks for Mark Edington's suggestion. I'v tested your example on Ubuntu 16.04, it works as expected.

    Note: this only works for child processes. It's a pity that seems no equivalent way of Window's WaitForSingleObject(unrelated_process_handle, timeout) in Linux/Unix to get notified of unrelated process's termination within timeout.

    OK, Mark Edington's sample code is here:

    /* 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;
    }
    
    0 讨论(0)
  • 2020-11-27 12:57

    Fork an intermediate child, which forks the real child and a timeout process and waits for all (both) of its children. When one exits, it'll kill the other one and exit.

    pid_t intermediate_pid = fork();
    if (intermediate_pid == 0) {
        pid_t worker_pid = fork();
        if (worker_pid == 0) {
            do_work();
            _exit(0);
        }
    
        pid_t timeout_pid = fork();
        if (timeout_pid == 0) {
            sleep(timeout_time);
            _exit(0);
        }
    
        pid_t exited_pid = wait(NULL);
        if (exited_pid == worker_pid) {
            kill(timeout_pid, SIGKILL);
        } else {
            kill(worker_pid, SIGKILL); // Or something less violent if you prefer
        }
        wait(NULL); // Collect the other process
        _exit(0); // Or some more informative status
    }
    waitpid(intermediate_pid, 0, 0);
    

    Surprisingly simple :)

    You can even leave out the intermediate child if you're sure no other module in the program is spwaning child processes of its own.

    0 讨论(0)
提交回复
热议问题