Waitpid equivalent with timeout?

前端 未结 10 1198
小鲜肉
小鲜肉 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:58

    Don't mix alarm() with wait(). You can lose error information that way.

    Use the self-pipe trick. This turns any signal into a select()able event:

    int selfpipe[2];
    void selfpipe_sigh(int n)
    {
        int save_errno = errno;
        (void)write(selfpipe[1], "",1);
        errno = save_errno;
    }
    void selfpipe_setup(void)
    {
        static struct sigaction act;
        if (pipe(selfpipe) == -1) { abort(); }
    
        fcntl(selfpipe[0],F_SETFL,fcntl(selfpipe[0],F_GETFL)|O_NONBLOCK);
        fcntl(selfpipe[1],F_SETFL,fcntl(selfpipe[1],F_GETFL)|O_NONBLOCK);
        memset(&act, 0, sizeof(act));
        act.sa_handler = selfpipe_sigh;
        sigaction(SIGCHLD, &act, NULL);
    }
    

    Then, your waitpid-like function looks like this:

    int selfpipe_waitpid(void)
    {
        static char dummy[4096];
        fd_set rfds;
        struct timeval tv;
        int died = 0, st;
    
        tv.tv_sec = 5;
        tv.tv_usec = 0;
        FD_ZERO(&rfds);
        FD_SET(selfpipe[0], &rfds);
        if (select(selfpipe[0]+1, &rfds, NULL, NULL, &tv) > 0) {
           while (read(selfpipe[0],dummy,sizeof(dummy)) > 0);
           while (waitpid(-1, &st, WNOHANG) != -1) died++;
        }
        return died;
    }
    

    You can see in selfpipe_waitpid() how you can control the timeout and even mix with other select()-based IO.

    0 讨论(0)
  • 2020-11-27 13:00

    Due to circumstances I absolutely needed this to run in the main thread and it was not very simple to use the self-pipe trick or eventfd because my epoll loop was running in another thread. So I came up with this by scrounging together other stack overflow handlers. Note that in general it's much safer to do this in other ways but this is simple. If anyone cares to comment about how it's really really bad then I'm all ears.

    NOTE: It is absolutely necessary to block signals handling in any thread save for the one you want to run this in. I do this by default as I believe it messy to handle signals in random threads.

    static void ctlWaitPidTimeout(pid_t child, useconds_t usec, int *timedOut) {
        int rc = -1;
    
        static pthread_mutex_t alarmMutex = PTHREAD_MUTEX_INITIALIZER;
    
        TRACE("ctlWaitPidTimeout: waiting on %lu\n", (unsigned long) child);
    
        /**
         * paranoid, in case this was called twice in a row by different
         * threads, which could quickly turn very messy.
         */
        pthread_mutex_lock(&alarmMutex);
    
        /* set the alarm handler */
        struct sigaction alarmSigaction;
        struct sigaction oldSigaction;
    
        sigemptyset(&alarmSigaction.sa_mask);
        alarmSigaction.sa_flags   = 0;
        alarmSigaction.sa_handler = ctlAlarmSignalHandler;
        sigaction(SIGALRM, &alarmSigaction, &oldSigaction);
    
        /* set alarm, because no alarm is fired when the first argument is 0, 1 is used instead */
        ualarm((usec == 0) ? 1 : usec, 0);
    
        /* wait for the child we just killed */
        rc = waitpid(child, NULL, 0);
    
        /* if errno == EINTR, the alarm went off, set timedOut to true */
        *timedOut = (rc == -1 && errno == EINTR);
    
        /* in case we did not time out, unset the current alarm so it doesn't bother us later */
        ualarm(0, 0);
    
        /* restore old signal action */
        sigaction(SIGALRM, &oldSigaction, NULL);
    
        pthread_mutex_unlock(&alarmMutex);
    
        TRACE("ctlWaitPidTimeout: timeout wait done, rc = %d, error = '%s'\n", rc, (rc == -1) ? strerror(errno) : "none");
    }
    
    static void ctlAlarmSignalHandler(int s) {
        TRACE("ctlAlarmSignalHandler: alarm occured, %d\n", s);
    }
    

    EDIT: I've since transitioned to using a solution that integrates well with my existing epoll()-based eventloop, using timerfd. I don't really lose any platform-independence since I was using epoll anyway, and I gain extra sleep because I know the unholy combination of multi-threading and UNIX signals won't hurt my program again.

    0 讨论(0)
  • 2020-11-27 13:04

    The function can be interrupted with a signal, so you could set a timer before calling waitpid() and it will exit with an EINTR when the timer signal is raised. Edit: It should be as simple as calling alarm(5) before calling waitpid().

    0 讨论(0)
  • 2020-11-27 13:07

    If you're going to use signals anyways (as per Steve's suggestion), you can just send the signal manually when you want to exit. This will cause waitpid to return EINTR and the thread can then exit. No need for a periodic alarm/restart.

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