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

Monks,

We recently had a problem with an external program that needs to be called with strings that may contain shell-metachars. After poking around a bit for the Right Way to do it, I found something like the following in the perlsec perldoc, for a nifty way to avoid the shell:

die "Can't fork: $!" unless defined $pid = open(KID, "-|"); if ($pid) { # parent while (<KID>) { # do something } close KID; } else { exec 'myprog', 'arg1', 'arg2' or die "can't exec myprog: $!"; }

I promptly adapted this method to my program, saw that it worked, and went on to the Next Task.

... time passes ...

While working on something else, my memory was jogged by something I read in perlipc:

Forking servers have to be particularly careful about cleaning up their dead children (called ``zombies'' in Unix parlance), because otherwise you'll quickly fill up your process table.

So... in the code above, does the parent need to wait() on the child? If not, why not?

Looking forward to hearing your wisdom...

--
3dan

Replies are listed 'Best First'.
Re: open(KID, "-|") and wait()?
by Improv (Pilgrim) on Apr 10, 2003 at 13:38 UTC
    From the open() perldoc,

    Closing any piped filehandle causes the parent process to wait for the child to finish, and returns the status value in $?.

    In other words, perl will handle it for you when you call close (which may block until the child finishes).
Re: open(KID, "-|") and wait()?
by robartes (Priest) on Apr 10, 2003 at 13:38 UTC
    The answer is 'yes'. Zombies are created when a child has exited, but the parent has not wait()ed for it yet. Zombies are no good (they tend to eat nice friendly old ladies, for example). One way of ensuring that the parent always acts correctly upon the death of a child is installing a signal handler for SIGCHLD:
    $SIG{'CHLD'}=sub { my $pid=wait(); if ($pid == -1 ) { print "SIGCHLD handler called without dead child.\n"; return; } print "Child $pid exited with $?\n"; }
    That way, whatever else the parent is doing, it will normally always catch a SIGCHLD. The only exception to this is if the parent is in uninterruptible sleep (e.g. when it's waiting on an NFS op on a filesystem whose server has gone away) - then it becomes night of the living dead :).

    Update: In the case of an open()ed child, as apparently the OP is using (bad robartes, I had parsed the snippet as a standard fork and exec), see Improv's remark. However, installing a SIGCHLD handler can never hurt, for example in the case where the child process abnormally (or even normally) terminates before the parent closes the filehandle.

    CU
    Robartes-

      Your SIGCHLD handler leaves much to be desired.
      • You are interfering with the normal return of exit status, so a program checking for that for a particular child might not get what they need.
      • There's no guarantee (that I'm aware of) that promises one CHLD per dead child. Because of the way signals can be suspended or delivered, the CHLD signal really means "at some time in the recent past, one or more children went belly up". So you have failed to loop to grab multiple kids, and failed to ignore the possibility that the kid has already been reaped between the time the CHLD signal is initiated and your program runs to your handler.
      So, this is a bad general example, and a bad specific example. Sorry.

      -- Randal L. Schwartz, Perl hacker
      Be sure to read my standard disclaimer if this is a reply.

      May I make another suggestion? This is originally from the Perl 4 Camel, but it actually works very well to prevent zombies:
       unless (fork) { # this is the child
          unless (fork) { # this is the grandchild
             exec "yourprogram";  # the detached process
                                  # can also be perl code and exit(0)
          }   # exits when done
          # Back in the child
          exit 0;  # immediately terminates
       }
       wait; # gets rid of the terminated child immediately
      
      This works because the child process exits immediately and is wait()ed on immediately, so no zombie. The grandchild ends up as a child of init because its parent has exited, and init will take care of reaping the grandchild for you.
      "Zombies are created when a child has exited, but the parent has not wait()ed for it yet."

      We have a little bit misunderstanding of "zombie" here ;-) Zombie is actually a process still running, but we lost track of it. In other words, in the fork case, if the parent created a child that cannot exit on its own, say it has some sort of dead loop, whatever the dead loop is created by mistake or purposely, as a good practice, the parent process should kill the child process. Otherwise, the child becomes a zombie, as it will run forever, and it will have that seat in the process table forever.

      If a process exited, it will be removed from the process table, doesn't matter whether it is created by a parent process, or it runs on its own, as it is a process any way.

      Other than the zombie situation, there are also other cases you may want to waitpid on your child process. For example, a parent process keeps creating child processes, and delegating tasks to them at a high speed. If you don't waitpid() to control the number of child processes you created and still alive, you would soon see an error message saying that, you cannot create child process, as resource used up.

      In this case, you would keep a counter of running child processes, and when it reaches a predefined max, stop creating child process, and start waitpid(), until the number of running child processes drop below a safe line.

      Also a little comment about SIGCHLD. You cannot capture SIGCHLD on win32, so the approach is not fully portable.
        Q. How do you kill a zombie?
        A. You can't, it's already dead.

        Zombies are not running. They have terminated, but they have a living parent who either hasn't waited for them, or hasn't told the kernel that it doesn't want to do that.

        Zombies are created because child processes run asynchronously from their parents, and often terminate long before the parent has had a chance to call wait/waitpid on them. The kernel has to keep some minimal information about such child processes in the table so that the parent can have a chance to collect its status, but releases the memory & other resources they might be holding

        Zombie is actually a process still running, but we lost track of it. In other words, in the fork case, if the parent created a child that cannot exit on its own, say it has some sort of dead loop, whatever the dead loop is created by mistake or purposely, as a good practice, the parent process should kill the child process. Otherwise, the child becomes a zombie, as it will run forever, and it will have that seat in the process table forever.
        Ehm, I beg to differ. A zombie process is not what you describe here. A zombie process is one that is no longer running (it has exited), but its parent has not yet collected its exit status, so the kernel keeps it around in the process table. What you describe is a runaway process, or even a perfectly normal daemon. Something like inetd or sendmail fits your description, and I do not think many people will call those zombies.
        If a process exited, it will be removed from the process table, doesn't matter whether it is created by a parent process, or it runs on its own, as it is a process any way.
        Except when its a child process - it exits but stays in the process table unless its parent collects its status.

        Update: Added point about daemons.

        YAU: After a chatterbox conversation with pg, we've decided that we're both right - different sources mean different things with 'zombie process'. YMMV.

        CU
        Robartes-

Re: open(KID, "-|") and wait()?
by edan (Curate) on Apr 10, 2003 at 13:48 UTC

    Improv++ - for reading the open() docs more carefully than me (sheesh, it's even got its own paragraph there, and silly old me didn't see it!?)

    robartes++ - for zombie humor, even though I think Improv's answer is better in this case - your answer is great for the general fork() case, though!

    My next question: how do I prevent the close() from blocking until the child exits?

    --
    3dan
      how do I prevent the close() from blocking until the child exits?
      You could delay the close until you know for sure the child process has finished. Use the non blocking version of waitpid for this. From the docs:
      use POSIX ":sys_wait_h"; $kid = waitpid(-1, WNOHANG);
      This will see if any child has died (without blocking) and return the PID of said child (and the return value in $?, as normal). You can also wait for a specific child by using the pid of that child as first argument to waitpid.

      Using this, you can make sure you only close the filehandle when the child has already exited. Just periodically check whether the child is dead, and close its filehandle if it is (you'll get a -1 return from the close, but that should be no problem).

      Unfortunately, this is not portable to all OS. See waitpid for more information.

      Update: 'now' ne 'know'

      CU
      Robartes-

      I'd suggest SIG{CHLD} = 'IGNORE'; unless you care what the child processes get up to after they spring into life.