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

hi!

well, i wanna have a "status-bar" via forking - while the
parent should display the status-bar (a 1char, changing sign),
the child should do the actual job. well the child does fine,
but the parent doesn't - here's the source i used so far:

if ($pid = fork)
 {
  # parent
  do
   {
     print "\b-";  sleep 1;
     print "\b\\"; sleep 1;
     print "\b|";  sleep 1;
     print "\b/";  sleep 1;
     $child_pid = waitpid(-1,0);
   } until $child_pid == -1;
 }
else
 {
  # child
  exec("cmd");
  exit(0);
 }
waitpid($pid, 0);
well, the "do"-loop doesn't actually loop - how can i get it
looping, until the child has done its job ?
thnx for any hint/snippet... - nostromo

Replies are listed 'Best First'.
Re: fork...
by perlmonkey (Hermit) on Apr 28, 2000 at 06:41 UTC
    First of all, this is cool stuff ... thanks for posting.

    The problem you are having (from what I can tell) is a extremely subtle one. Your program is working almost as designed, but there are a few issues. First you are not printing enough characters to cause the print buffer to actually print to the screen. So all of your characters are getting pushed into the buffer, then they over write each other in the last instant because the buffer is not getting flushed during the execution of the program. So to fix this problem add:local $| = 1 inside the do loop. $| is the 'autoflush' variable. 'local' will prevent the global $| from being overwritten in case you need buffered output elsewhere in your program.

    The next issue is that your waitpid is actually hanging ... waiting for th child to die. You want to keep looping, so you need to change the second paramater to 64 or POSIX::WNOHANG
    use POSIX; ... code here ... $child_pid = waitpid(-1, WNOHANG);
    This prevents the waitpid from hanging. So for your complete functioning code:
    use POSIX; if ($pid = fork) { # parent do { local $| = 1; print "\b-"; sleep 1; print "\b\\"; sleep 1; print "\b|"; sleep 1; print "\b/"; sleep 1; $child_pid = waitpid(-1,WNOHANG); } until $child_pid == -1; } else { # child #exec("cmd"); sleep 15; exit(0); }
    The last waitpid was not necessary. I also changed exec "cmd" to sleep 15 for testing.

    Now there is one more minor thing I would suggest. You only check for the child death every 4 seconds. You probably should be checking for this constantly with a signal handler and do the waitpid in the signal handler. My signal handler just sets a global flag to tell the parent to stop looping:
    use POSIX; $keep_going = 1; #set the SIGCHLD signal handler $SIG{CHLD} = \&REAPER; if ($pid = fork) { # parent while($keep_going) { local $| = 1; print "\b-"; sleep 1; print "\b\\"; sleep 1; print "\b|"; sleep 1; print "\b/"; sleep 1; } } else { # child sleep 15; exit(0); } #SIGCHLD signal handler sub REAPER { #test if all childred are dead unless(waitpid(-1, WNOHANG) == -1 ) { $keep_going = 0; } #reset the signal handler for more than #one child death. $SIG{CHLD} = \&REAPER }
    So now REAPER will get called whenever there is a signal from any child, and the waitpid will not have to be staged every 4 seconds.

    I hope this helps.
Re: fork...
by nostromo (Sexton) on Apr 28, 2000 at 13:00 UTC
    ok, now with perlmonk-account... hehe

    he, this piece of code was fantastic !! thanks so far, but....
    hm...well as i said, it worx fine, but actually the status-bar
    doesn't stop, after child has finished its job.

    so it finally ends up in a non-stopped parent, which blocks
    the rest of the program. :(
    any idea for that prob ??
      I dont think I am understanding your problem now. What do you mean by a 'non-stopped parent'. I think you are saying that the parent 'do' loop never ends so it keeps printing? On my system everything worked beautifully. They parent looped until the child died, then the parent quit the loop and exited. What OS are you using, I am on an SGI irix machine, so there could be differences with the OS handleing of signals and forking. Anomolies in forking/signal handling can be hugely dependant on OS.

      Did you try the signal handling example? Since this program is so short I would recommend stepping through the debugger (following the parent) to see what the results of your waitpid function is.

      Maybe the waitpid does not return a -1 (it should when all children die. ... you are only forking once?). It should return a 0 when any child dies, so maybe change the logic to
      do { ... code here ... } while $child_pid > 0;
      This will loop until *any* child dies. (Well on my system it would anyway.)

      Another idea (this might be completely false) maybe your child forks again? It might be that if the child process itself becomes a parent, then the grandparent waitpid might not return a -1. I have no idea ... something to consider though.

      Hopes this helps. Reply if you get more info.
Re: fork...
by Anonymous Monk on Apr 29, 2000 at 00:49 UTC
    hi pearlmonkey!

    "unless(waitpid(-1, 0) == -1 )>" instead of an "unless(waitpid(-1, 64) == -1 )"
    did the trick - no zombies, everything worked fine.
    it's a deadrat-linux6.0 with perl-5.00503-2.

    my progress-bar is working really fine now :)
    hm...if you wonder "wtf is he coding on?" - take a look at this
    this is the former version, with no status-|progress-bar :)

    once more: thnx a lot !!

    nostromo
      Bizzare. waitpid(-1,0) definately did not work for me.

      Glad you got it working though. Maybe POSIX::WNOHANG is not 64 on your system?

      PS. Judging from your website you seem to be into mp3s. I wrote a MP3 server tutorial you may be interested in (if you havent made one already).