http://www.perlmonks.org?node_id=226968

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

I would like to have a parent process spawn a child to perform some task every 5 seconds. I would also like to perform some cleanup every time a child exits.

Unfortunately it appears that sleep exits prematurely when a CHLD signal handler is triggered; for example,

#!/usr/bin/perl use strict; $|++; my $pid = fork(); if ($pid) { # the parent does not exit prematurely when the handler # is set to IGNORE $SIG{CHLD} = sub { print "$$: Reaping a child process in child + signal handler ...\n" }; print "$$: Parent sleeping 5 seconds at [".localtime()."] ...\ +n"; sleep 5; print "$$: That was refreshing. The time is now [".localtime( +)."].\n"; exit(0); } else { print "$$: \tChild sleeping for 2 seconds ...\n"; sleep 2; print "$$: \tChild exiting at [".localtime()."] ...\n"; exit(0); }
results in
3978: Child sleeping for 2 seconds ... 3977: Parent sleeping 5 seconds at [Tue Jan 14 14:17:11 2003] ... 3978: Child exiting at [Tue Jan 14 14:17:13 2003] ... 3977: Reaping a child process in child signal handler ... 3977: That was refreshing. The time is now [Tue Jan 14 14:17:13 2003] +.
From the documentation for sleep, May be interrupted if the process receives a signal such as "SIGALRM".

I can put the sleep in backticks, which seems to work albeit kludgily, but I am annoyed that putting it in backticks will result in a CHLD signal whenever the subprocess exits which means I have to worry about which child it was that exited when the child signal is caught.

My question to the monks is: How would those wiser in the ways of slumber do this?

Replies are listed 'Best First'.
Re: Sleeping and reaping
by jwest (Friar) on Jan 14, 2003 at 22:35 UTC
    Assuming you have a well-behaved POSIX system, one option is to block the SIGCHLD signal until you're ready to handle it:

    #!/usr/bin/perl use strict; use POSIX qw(:signal_h); $|++; my $pid = fork(); if ($pid) { my $sigset = POSIX::SigSet->new; my $blockset = POSIX::SigSet->new(SIGCHLD); $SIG{CHLD} = sub { print "$$: Reaping a child process in child + signal handler ...\n" }; sigprocmask(SIG_BLOCK, $blockset, $sigset)); print "$$: Parent sleeping 5 seconds at [".localtime()."] ...\ +n"; sleep 5; print "$$: That was refreshing. The time is now [".localtime( +)."].\n"; sigprocmask(SIG_SETMASK, $sigset); exit(0); } else { print "$$: \tChild sleeping for 2 seconds ...\n"; sleep 2; print "$$: \tChild exiting at [".localtime()."] ...\n"; exit(0); }

    The caveat to this, aside from its untested-ness, is that when you unblock SIGCHLD, even if more than one SIGCHLD signal was received, you'll only get one.

    To adjust for that, your parent process should keep track of its children and do a non-blocking waitpid for each child when the signal comes in. If waitpid returns a positive non-zero value, which is presumably the pid that you just waited on, you can perform the clean-up action.

    In fact, it's arguable that if you do this at regular intervals, you can leave $SIG{CHLD} = 'IGNORE' - unless you're really into signal handling.


    Hope this helps!

    --jwest

    -><- -><- -><- -><- -><-
    All things are Perfect
        To every last Flaw
        And bound in accord
             With Eris's Law
     - HBT; The Book of Advice, 1:7
    
Re: Sleeping and reaping
by Abigail-II (Bishop) on Jan 14, 2003 at 23:16 UTC
    It all depends. What do you want to do if a child exits, and when do you want to do it? If all you want to do is to reap the child, you should be able to just ignore the signal. If it's ok to wait until the sleep has finished, you can defer the signal as explained.

    Otherwise you can something like:

    my $sleep = 100; $sleep -= sleep $sleep while $sleep > 0;
    Or:
    use POSIX; # Need the one coming with 5.8.0. my $sleep = 100; $sleep = POSIX::sleep $sleep while $sleep > 0;

    Abigail

Re: Sleeping and reaping
by tall_man (Parson) on Jan 14, 2003 at 22:59 UTC
    There's another way to sleep that doesn't use SIGALRM. Try this:
    select (undef, undef, undef, $time_to_sleep);
    The Time::HiRes module also provides an alarm-free interface to nanosleep if your system supports it.