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

First, yes, I read the FAQ, I tried a handler on SIG{INT}.

I'm working on a script that runs rsync every few seconds and I'd like to be able to handle CTRL-C gracefully, which means letting rsync finish what it's doing before exiting.

Right now, rsync catches CTRL-C, not my script, so my script keeps running and rsync gets interrupted.

Here's a reeeealy short psudo-code version of my script

$SIG{INT} = \&blah; sub blah { sleep 10; exit; } while (1) { system("rsync -a rsync://blah.example.com/foo foo"); sleep 5; }

Hmmmm... Writing this I think I see the problem. I need to fork off the rsync job somehow so my perl script will actually be running at the time it gets signalled...

What's the best way to handle that? (If I've got this figured out that is...)

Replies are listed 'Best First'.
Re: protect children from SIG{INT}
by Zaxo (Archbishop) on Nov 18, 2005 at 01:37 UTC

    You're right, fork will give you the flexibility you need.

    sub slumber { # signal-safe sleep, added my $span = shift; $span -= sleep $span while $span > 0; 1; } $SIG{CHLD} = 'IGNORE'; while (1) { defined( my $cpid = fork ) or warn $! and slumber 5 and next; slumber 5 and next if $cpid; # parent # $SIG{INT} = $SIG{HUP} = sub {}; # child $SIG{INT} = $SIG{HUP} = 'IGNORE'; # child exec qw(rsync -a rsync://blah.example.com/foo foo); die 'exec failed'; }

    The empty handlers for keyboard interrupt and terminal hangup are both needed to isolate the child. The OS will send a HUP to the child when the parent exits, and the default handler is for the child to exit, too. Untested.

    Update: Added slumber function to ensure timing and corrected the INT and HUP handler assignment.

    After Compline,
    Zaxo

      WOO HOO!

      Yes, that did it.

      Making my main script ignore $SIG{CHILD} and making the child ignore $SIG{INT} and $SIG{HUP} makes it work the way I want it to.

      Your code snippet almost works out of the box. Just had to change:

      $SIG{INT} = $SIG{HUP} = sub{} ; # child

      To

      $SIG{INT} = $SIG{HUP} = 'IGNORE'; # child

      Thanks!

      Are you sure about that? I don't think that signal handlers persist across an exec (though it seems that the signal mask does; see the below update). They didn't in my short test:
      $SIG{INT} = $SIG{HUP} = sub {}; exec qw(/bin/sleep 5) or die 'exec failed';
      I was able to interrupt that with CTRL-C, even though I ignored SIGINT in the child process.

      Update: Well, the above apparently worked for the OP, though it still doesn't work for me. Maybe it's OS-dependent; I'm testing on Linux.

      Update: Kudos to pileofrogs for pointing out below that setting the signal handlers to the string IGNORE works perfectly! It seems that the signal mask persists across an exec, but signal handlers don't, which basically makes sense. So this code works on my system:

      $SIG{INT} = $SIG{HUP} = 'IGNORE'; exec qw(sleep 5) or die 'exec failed';

        If a sleeping process catches any signal, it doesn't automatically resume sleep after the handler fires. Your process is exiting because it's done.

        To sleep a specified time in spite of interrupts, you need to be prepared to resume sleep. One way in Perl,

        my $span = 5; $span -= sleep $span while $span > 0;
        The solution I gave would be improved by doing that wherever sleep 5 appears.

        After Compline,
        Zaxo

        Try setting the child's signal handler to 'IGNORE' rather than sub {};