Search code examples
cwaitpid

How to cancel waitpid if child has no status change?


Disclaimer: Absolute newbie in C, i was mostly using Java before.

In many C beginner tutorials, waitpid is used in process management examples to wait for its child processes to finish (or have a status change using options like WUNTRACED). However, i couldn't find any information about how to continue if no such status change occurs, either by direct user input or programmatic (e.g. timeout). So what is a good way to undo waitpid? Something like SIGCONT for stopped processes, but instead for processes delayed by waitpid.

Alternatively if the idea makes no sense, it would be interesting to know why.


Solution

  • How about if I suggest using alarm()? alarm() delivers SIGALRM after the countdown passes (See alarm() man page for more details). But from the signals man page, SIGALRM default disposition is to terminate the process. So, you need to register a signal handler for handling the SIGALRM. Code follows like this...

    #include <unistd.h>
    #include <signal.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <errno.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    
    void sigalrm(int signo)
    {
        return; // Do nothing !
    }
    
    int main()
    {
        struct sigaction act, oldact;
    
        act.sa_handler = sigalrm;   // Set the signal handler
        sigemptyset(&act.sa_mask);
        act.sa_flags = 0;
    
    #ifdef SA_INTERRUPT // If interrupt defined set it to prevent the auto restart of sys-call
        act.sa_flags |= SA_INTERRUPT;
    #endif
    
        sigaction(SIGALRM, &act, &oldact);
    
        pid_t fk_return = fork();
        if (fk_return == 0) {   // Child never returns
            for( ; ; );
        }
    
        unsigned int wait_sec = 5;
        alarm(wait_sec);    // Request for SIGALRM
    
        time_t start = time(NULL);
        waitpid(-1, NULL, 0);
        int tmp_errno = errno;  // save the errno state, it may be modified in between function calls.
        time_t end = time(NULL);
    
        alarm(0);  // Clear a pending alarm
        sigaction(SIGALRM, &oldact, NULL);
    
        if (tmp_errno == EINTR) {
            printf("Child Timeout, waited for %d sec\n", end - start);
            kill(fk_return, SIGINT);
            exit(1);
        }
        else if (tmp_errno != 0)    // Some other fatal error
            exit(1);
    
        /* Proceed further */
    
        return 0;
    }
    

    OUTPUT

    Child Timeout, waited for 5 sec
    

    Note: You don't need to worry about SIGCHLD because its default disposition is to ignore.

    EDIT

    For the completeness, it is guaranteed that SIGALRM is not delivered to the child. This is from the man page of alarm()

    Alarms created by alarm() are preserved across execve(2) and are not inherited by children created via fork(2).

    EDIT 2

    I don't know why it didn't strike me at first. A simple approach would be to block SIGCHLD and call sigtimedwait() which supports timeout option. The code goes like this...

    #include <unistd.h>
    #include <signal.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <errno.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    
    int main()
    {
        sigset_t sigmask;
        sigemptyset(&sigmask);
        sigaddset(&sigmask, SIGCHLD);
        sigprocmask(SIG_BLOCK, &sigmask, NULL);
    
        pid_t fk_return = fork();
        if (fk_return == 0) {   // Child never returns
            for( ; ; );
        }
    
        if (sigtimedwait(&sigmask, NULL, &((struct timespec){5, 0})) < 0) {
            if (errno == EAGAIN) {
                printf("Timeout\n");
                kill(fk_return, SIGINT);
                exit(1);
            }
        }
    
        waitpid(fk_return, NULL, 0);    // Child should have terminated by now.
    
        /* Proceed further */
    
        return 0;
    }
    

    OUTPUT

    Timeout