4

I am aware of the many questions regarding waitpid and timeouts but they all cover this by killing the child from within an alarm handler.

That is not what i want, i want to keep the process running but dispatch it from waitpid.

The underlaying problem I try to solve is a daemon process with a main loop that processes a queue. The tasks are processed one at a time.

If a task hangs the whole main loop hangs. To get around this fork() and waitpid seemed an obvious choice. Still if a task hangs the loop hangs.

I can think of workarounds where i do not use waitpid at all but i would have to track running processes another way as i still want to process one task at a time in parallel to possibly hanging tasks.

I could even kill the task but i would like to have it running to examine what exactly is going wrong. A kill handler that dumps some debug information is also possible.

Anyway, the most convenient way to solve that issue is to timeout waitpid if possble.

Edit:

This is how I used fork() and waitpid and it may be clearer what is meant by child.

my $pid = fork();

if ($pid == 0){
    # i am the child and i dont want to die
}
elsif ($pid > 0) {
    waitpid $pid, 0;
    # i am the parent and i dont want to wait longer than $timeout
    # for the child to exit
}
else {
    die "Could not fork()";
}

Edit:

Using waitpid WNOHANG does what I want. Is this usage good practice or would you do it differently?

use strict;
use warnings;
use 5.012;
use POSIX ':sys_wait_h';

my $pid = fork();

if ($pid == 0){
    say "child will sleep";
    sleep 20;
    say "child slept";
}
else {
    my $time = 10;
    my $status;
    do {
        sleep 1;
        $status = waitpid -1, WNOHANG;
        $time--;
    } while ($time && not $status );

    say "bye";
}
matthias krull
  • 4,389
  • 3
  • 34
  • 54

3 Answers3

7

If a task hangs the whole main loop hangs. To get around this fork() and waitpid seemed an obvious choice. Still if a task hangs the loop hangs.

Use waitpid with the WNOHANG option. This way it's not going to suspend the parent process and will immediately return 0 when the child has not yet exited. In your main loop you'll have to periodically poll all the children (tasks).

Blagovest Buyukliev
  • 42,498
  • 14
  • 94
  • 130
5

instead of poling all the children periodically, you might want to set up a signal handler to handle SIGCHLD... from perlipc:

 use POSIX ":sys_wait_h";
    $SIG{CHLD} = sub {
        while ((my $child = waitpid(-1, WNOHANG)) > 0) {
            $Kid_Status{$child} = $?;
        }
    };
    # do something that forks...
1

Enabling and handling SIGCHLD is also a possibility; it'll notify you of child process state changes without polling -- see sigprocmask(2) and signal(3) in the manual pages.