SIGINT not blocked by system()?

T

Tom

I've got a multithreaded script in which I'd like to have my "worker"
threads ignore SIGINT (i.e. the main thread traps it and calls a sub to
cleanup nicely after all workers are done). The workers do their thing
via system() calls, and my experience (using 5.8.8 at least) indicates
that SIGINT is being propogated to the worker threads' system() call
(even though it's locally ignored).

According to perlfaq8, system() is shielded from SIGINT:

-----------------------------------------------------------------------------
How do I make a system() exit on control-C?

You can't. You need to imitate the system() call (see perlipc for
sample code) and then have a signal handler for the INT signal that
passes the signal on to the subprocess.
-----------------------------------------------------------------------------

When CTRL-C is hit on the console, the following code will print
"Cleaning up..." and exits promptly. I would have expected it to exit
after all threads have returned.


#!/usr/bin/perl -w
use strict;

use threads;

$SIG{INT} = \&cleanup;

foreach my $id ( 1..10 ) {
threads->new( \&sleeper, $id );
}

sleep 60;

sub cleanup {
print "Cleaning up\n";
foreach my $thread ( threads->list() ) {
$thread->join();
}
exit();
}

sub sleeper {
my $id = shift;
my $time = int( rand(30) );
local $SIG{INT} = 'IGNORE';
print "Thread $id sleeping for $time...\n";
system( "sleep $time" );

return;
}
 
B

Ben Morrow

Quoth "Tom said:
I've got a multithreaded script in which I'd like to have my "worker"
threads ignore SIGINT (i.e. the main thread traps it and calls a sub to
cleanup nicely after all workers are done).

In general which thread receives a signal is not well defined; and
Perl's signal handling can be a little... funny. You may have luck with
any or all of these:

1. Catch the signal in the worker threads as well, and have them signal
a condition variable (see threads::shared). The main thread should be
sitting in a cond_wait on that variable, and if it gets signalled it
does the cleanup and exits. You may need some playing around with
mutexes and/or sigprocmask to avoid a race between the child starting
and the parent going into the wait.

2. Mask the signal in the child threads with POSIX::sigprocmask. This
may well mask it in the parent as well, though... :(

3. Set the environment variable PERL_SIGNALS to 'unsafe'. This will go
back to the old immediate signal handling, which is less safe (there is
some chance of memory corruption). You will want to heed the old advice
about doing nothing in the signal handler beyond setting a flag you can
check in the main program.

4. Fake up a system() yourself, as the faq suggests, and ignore the
signal in the (forked) child before you exec.

I don't actually know how well Perl handles the interaction of fork and
threads: it's not something I would try, myself. If you really do just
need your children to run system() and return, you'd be much better off
just forking and waiting.

Ben
 
X

xhoster

Tom said:
I've got a multithreaded script in which I'd like to have my "worker"
threads ignore SIGINT (i.e. the main thread traps it and calls a sub to
cleanup nicely after all workers are done). The workers do their thing
via system() calls, and my experience (using 5.8.8 at least) indicates
that SIGINT is being propogated to the worker threads' system() call
(even though it's locally ignored).

In your shell, when you hit ^C, the shell sends an interupt signal to
all the processes in its "process group" (or whatever they call it),
which includes the processes spawned by the system function.

If, instead of hitting ^C, you instead determined the script's pid and
use linux's "kill -2 <pid>", you will probabl see the behaviour you want.

I can also get the the behaviour you want on my system by using
POSIX::setsid.

According to perlfaq8, system() is shielded from SIGINT:

-------------------------------------------------------------------------
---- How do I make a system() exit on control-C?

You can't. You need to imitate the system() call (see perlipc for
sample code) and then have a signal handler for the INT signal that
passes the signal on to the subprocess.

Perl doesn't pass the INT into the system. But if your shell decided to
signal the spawned process on its own, Perl doesn't stop it from doing so.
sub sleeper {
##prevent shell from clobbering system
POSIX::setsid();
my $id = shift;
my $time = int( rand(30) );
local $SIG{INT} = 'IGNORE';
print "Thread $id sleeping for $time...\n";
system( "sleep $time" );

return;
}

Xho
 
B

Ben Morrow

Quoth (e-mail address removed):
Perl doesn't pass the INT into the system. But if your shell decided to
signal the spawned process on its own, Perl doesn't stop it from doing so.

My system(3) says

| During execution of the command, SIGCHLD will be blocked, and SIGINT and
| SIGQUIT will be ignored.

Does Perl's system() not do this?

Ben
 
X

xhoster

Ben Morrow said:
Quoth (e-mail address removed):

My system(3) says

| During execution of the command, SIGCHLD will be blocked, and SIGINT
| and SIGQUIT will be ignored.

That seems to mean that the parent process itself process will ignore
SIGINT while the child is running, not that the child will be arranged to
ignore SIGINT. I'm not sure if that is how you were interpreting it or not.
Does Perl's system() not do this?

It seems to depend on the threads used.

$ perl -wle 'use strict; use threads; $SIG{INT}=sub {die }; \
print system "sleep 6"; print "Done"'
2
Done

(Exits immediately upon ^c, but not because the parent itself got the
signal, rather because the child the parent was waiting on got the signal
and exited early.)

$ perl -wle 'use strict; use threads; $SIG{INT}=sub {die }; \
threads->create(sub{print system "sleep 6"}); \
threads->list->join(); print "Done"'
2
Died at -e line 1.

Here, the parent itself does apparently get the signal.

This is perl, v5.8.3 built for x86_64-linux-thread-multi

Xho
 
B

Ben Morrow

Quoth (e-mail address removed):
That seems to mean that the parent process itself process will ignore
SIGINT while the child is running, not that the child will be arranged to
ignore SIGINT. I'm not sure if that is how you were interpreting it or not.

Ah, yes, I missed that ambiguity. I was reading it as

fork
if (parent) {
waitpid
}
else {
ignore SIGINT
exec
}

i.e. the parent sets up the child to ignore the signals.

So, the answer is, if you care do it all yourself and handle the signals
as you need :).
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,995
Messages
2,570,236
Members
46,822
Latest member
israfaceZa

Latest Threads

Top