rhumbliner has asked for the wisdom of the Perl Monks concerning the following question:

This perl script tests the use of fork(). I inadvertantly discovered that adding the problem code causes oddities with process id's, wait's, and such. When the problem code is uncommented, the child count goes to -1 instead of 0. I would greatly appreciate an explanation of why get_pidof() screws up wait().

Thank you.
#!/usr/bin/perl -w use strict; use Unix::PID; ## problem code: my $pid = Unix::PID->new(); #my @pids = $pid->get_pidof('fork.pl'); #die "one copy of me is already running\n" if scalar(@pids) > 0; ## :problem code print "<parent>: my process id: $$\n"; my $childcnt = 0; foreach my $process (@ARGV) { my $pid = fork; if (defined $pid ) { if ($pid ) { # parent process if (++$childcnt >= 2) { # hit child limit print "<parent>: must wait to start more\n"; my $completed_pid = wait; $childcnt--; print "<parent>: $completed_pid is done; $childcnt run +ning\n"; } else { # more children ok print "<parent>: $childcnt running; can start more\n"; } } else { # child process print "<$process>: my id: $$\n"; sleep int(rand(10)); print "<$process>: end: $$\n"; exit 0; } } else { # fork failed warn "<parent>: fork failed $!\n"; last; } } print "<parent>: waiting for $childcnt to complete\n"; until ((my $completed_pid = wait) == -1) { $childcnt--; print "<parent>: $completed_pid is done; $childcnt running\n"; }; print "<parent>: complete; $childcnt process(es) left\n";

Replies are listed 'Best First'.
Re: get_pidof() messes with wait()?
by liverpole (Monsignor) on Mar 08, 2006 at 12:53 UTC
    My guess is that you have one or more logic errors in the program.    I'm also not sure why you're using @ARGV -- is this just to control how many processes get started?  If so, that's okay, but it's a little misleading that you call the loop variable $process.

    You may also be expecting that get_pidof is returning the process id only of any running fork.pl programs, but you may have others -- for example, if you're editing fork.pl in the background, then get_pid can match it instead.  So first of all, try passing a second argument to get_pidof:

    my @pids = $get_pidof('fork.pl', 1);
    which should cause it to find processes which exactly match fork.pl.

    Next of all, why are you decrementing $childcnt when the return from the wait is -1?  According to perldoc -f wait:

    wait Behaves like the wait(2) system call on your system: it + waits for a child process to terminate and returns the pid of + the deceased process, or "-1" if there are no child process +es. The status is returned in $?. Note that a return value of +"-1" could mean that child processes are being automatically + reaped, as described in perlipc.
    Which means you probably want to decrement only when you get a value greater than zero for $completed_pid.  Additionally, you are executing a blocking wait, so the wait will block until it succeeds, or returns -1 for "no more children".  If you want to make it non-blocking, try something like this:
    while (1) { my $completed_pid = waitpid(-1, WNOHANG); if ($completed_pid == -1) { # No more children -- exit loop last; } if ($completed_pid > 0) { print "<parent>: $completed_pid is done; $childcnt running\n"; $childcnt--; sleep 1; } }

    You'll also need use POSIX ":sys_wait_h"; at the top of the program.  You can read more about non-blocking waits with perldoc -f waitpid.

    Update:  I discovered another possible reason why you're getting a negative $childcount when you use get_pidof() ... it's because get_pidof is itself spawning a process which is a child of fork.pl.  This causes your program to wait for it and it decrements $childcount one too many times.


    @ARGV=split//,"/:L"; map{print substr crypt($_,ord pop),2,3}qw"PerlyouC READPIPE provides"
      Your comments got me thinking about how get_pidof() is implemented and therein lies the solution. get_pidof() uses `ps` and kicks off a child process to execute `ps`. So even when my childcnt is zero, wait() will return a proper pid, not -1.

      The solution is to always follow any call to get_pidof() with a call to wait(). That will clear out the pid of the `ps` call.

      Just to touch on your other points:

      1) I use @ARGV just to enumerate the subprocesses I want to kick off. I invoke fork.pl as `fork.pl a b c` to kick off 3 children.

      2) get_pidof() was not picking up the editor running in the background, but it was very astute of you to think of that possibility. That would certainly have wreaked havoc.

      3) The `until` loop only decrements childcnt when the return from wait() is not -1. When wait() returns a -1 the program exits without decrementing childcnt.

      Thanks for your help.