I want to have all child processes die when I kill a perl process that is using ForkManager. In the code below, if I run it and hit ctrl+c while the sleep line is running, the sleep process is killed, but the print lines are then all simultaneously executed before the script ends. Ideally, I'd like an interrupt to immediately stop all execution. What can I do?
#!/usr/bin/perl -w
use Parallel::ForkManager;
main {
my $fork1 = new Parallel::ForkManager(8);
while (1) {
$fork1->start and next;
system("sleep 15s");
print "Still going!"
$fork1->finish;
}
fork1->wait_all_children;
}
According to perldoc system, system actually ignores both SIGINT and SIGQUIT:
Since SIGINT and SIGQUIT are ignored during the execution of system,
if you expect your program to terminate on receipt of these signals
you will need to arrange to do so yourself based on the return value.
So if you want your processes to stop executing if you SIGINT during the system call, you need to implement that logic yourself:
#!/usr/bin/perl -w
use Parallel::ForkManager;
main {
my $fork1 = new Parallel::ForkManager(8);
while (1) {
$fork1->start and next;
print "Sleeping...";
system("sleep 15s") == 0 or exit($?);
print "Still going!";
$fork1->finish;
}
fork1->wait_all_children;
}
OR the more reasonable approach is the use the Perl built-in sleep:
#!/usr/bin/perl -w
use Parallel::ForkManager;
main {
my $fork1 = new Parallel::ForkManager(8);
while (1) {
$fork1->start and next;
print "Sleeping...";
sleep 15;
print "Still going!";
$fork1->finish;
}
fork1->wait_all_children;
}
First off - using system means you might have something strange happen, because ... then you're allowing whatever you're calling to do stuff to handle signals by itself.
That may be your problem.
However otherwise, what you can do with perl is configure signal handlers - what to do if a signal is recieved by this process. By default - signals are either set to 'exit' or 'ignore'.
You can see what this is currently via print Dumper \%SIG;
However the simplest solution to you problem I think, would be to set a handler to trap SIGINT and then send a kill to your current process group.
The behavior of kill when a PROCESS number is zero or negative depends on the operating system. For example, on POSIX-conforming systems, zero will signal the current process group, -1 will signal all processes, and any other negative PROCESS number will act as a negative signal number and kill the entire process group specified.
$SIG{'INT'} = sub {
kill ( 'TERM', -$$ );
};
Related
I am running a perl script which internally launches bsub jobs . I wanted to kill all jobs launched by this perl script when control c is pressed in unix terminal when i run the perl script based on the group id .
Please advise.
Thanks & Regards
amrutha
Edit: code copied from a comment
my $command = "bsub -q normal -g GROUPID <command>";
system($command);
Try to send SIGTERM via kill to the process group from the signal handler for Ctrl-C
$SIG{INT} = sub { kill 15, -$gpid }; # $gpid used in bsub command
Whether this will work, and how reliably, depends on many details of what those jobs are and do.
An example of how a process can (unknowingly) dodge the bullet is seen below, where the child process changes its process group ID. It does this for a demo of signalling the group but then that also demonstrates a loophole in signalling the group.
An example
use warnings;
use strict;
use feature 'say';
my $pid;
$SIG{INT} = sub {
say "Got $_[0]. Send TERM to process group with $pid";
kill 15, -$pid;
};
$pid = fork // die "Can't fork: $!";
if ($pid == 0) {
$SIG{INT} = 'IGNORE';
setpgrp 0, $$;
say "\tChild's process group: ", getpgrp;
sleep 10;
say "\tkid done";
exit;
};
say "Parent $$ started $pid";
sleep 5;
sleep 3; # return to sleep after signal is handled
say "done";
When kill is sent to a negated PID (or with a negative signal) it goes to that process group. Here I use the child's $pid (to be assigned) and in the child set its group id to that ($$).
The signal has to be dealt with in the child as well since the forked process inherits the handler.
With Ctrl-C pressed after a few seconds the output is
Parent 10450 started 10451
Child's process group: 10451
^CGot INT. Send TERM to group 10451
done
where done is printed 3 seconds after the previous prints. The kid done never comes.
If you want the parent to terminate as well add an exit or such to the signal handler.
As noted, this may fail, for one thing since the child can simply change its group. A more reliable way would be to catalog jobs that may get triggered and then find their PIDs at runtime, so to terminate them. A useful tool for this is Proc::ProcessTable. Some examples of hunting down processes are in this post and this post.
I have a long-running program that used File::Temp::tempdir to create a temporary file and sometimes interrupted it via ^C.
The following program prints the name of the temporary directory it creates and the name of a file in it.
#!/usr/bin/env perl
use strict;
use warnings;
use File::Temp qw[tempdir];
my $dir = tempdir(CLEANUP => 1);
print "$dir\n";
print "$dir/temp.txt\n";
`touch $dir/temp.txt`;
exit;
On OS X, this creates a directory inside /var/folders
If the last line is exit; or die;, then the folder will get cleaned up and the temporary file inside it will get deleted.
However, if we replace the last line with sleep 20; and then interrupt the perl program via ^C, the temporary directory remains.
% perl maketemp.pl
/var/folders/dr/cg4fl5m11vg3jfxny3ldfplc0000gn/T/ycilyLSFs6
/var/folders/dr/cg4fl5m11vg3jfxny3ldfplc0000gn/T/ycilyLSFs6/temp.txt
^C
% stat /var/folders/dr/cg4fl5m11vg3jfxny3ldfplc0000gn/T/ycilyLSFs6/temp.txt
16777220 6589054 -rw-r--r-- 1 <name> staff 0 0 "Aug 1 20:46:27 2016" "Aug 1 20:46:27 2016" "Aug 1 20:46:27 2016" "Aug 1 20:46:27 2016" 4096 0 0
/var/folders/dr/cg4fl5m11vg3jfxny3ldfplc0000gn/T/ycilyLSFs6/temp.txt
%
using a signal handler that just calls exit; does clean up the directory. E.g.
#!/usr/bin/env perl
use strict;
use warnings;
use File::Temp qw[tempdir];
$SIG{INT} = sub { exit; };
my $dir = tempdir(CLEANUP => 1);
print "$dir\n";
print "$dir/temp.txt\n";
`touch $dir/temp.txt`;
sleep 20;
As does using a "trivial" signal handler
#!/usr/bin/env perl
use strict;
use warnings;
use File::Temp qw[tempdir];
$SIG{INT} = sub { };
my $dir = tempdir(CLEANUP => 1);
print "$dir\n";
print "$dir/temp.txt\n";
`touch $dir/temp.txt`;
sleep 20;
I tried looking through the source code (https://github.com/Perl-Toolchain-Gang/File-Temp/blob/master/lib/File/Temp.pm) to determine how tempdir is registering a cleanup action
Here's the exit handler installation
https://github.com/Perl-Toolchain-Gang/File-Temp/blob/master/lib/File/Temp.pm#L1716
which calls _deferred_unlink
https://github.com/Perl-Toolchain-Gang/File-Temp/blob/master/lib/File/Temp.pm#L948
which modified the global hashes %dirs_to_unlink and %files_to_unlink, but uses the pid $$ as a key for some reason (probably in case the Perl interpreter forks? Not sure why that's necessary though since removing a directory seems like it would be an idempotent operation.)
The actual logic to clean up the files is here, in the END block.
https://github.com/Perl-Toolchain-Gang/File-Temp/blob/master/lib/File/Temp.pm#L878
A quick experiment shows that END blocks are indeed run when perl has exited normally or abnormally.
sleep 20;
END {
print "5\n";
}
# does not print 5 when interrupted
And are run here
$SIG{INT} = sub {};
sleep 20;
END {
print "5\n";
}
# does print 5 when interrupted
So ... why does the END block get skipped after a SIGINT unless there's a signal handler, even one that seems like it should do nothing?
By default, SIGINT kills the process[1]. By kill, I mean the process is immediately terminated by the kernel. The process doesn't get to perform any cleanup.
By setting a handler for SIGINT, you override this behaviour. Instead of killing the process, the signal handler is called. It might not do anything, but its very existence prevented the process from being killed. In this situation, the program won't exit as a result of the signal unless it chooses to exit (by calling die or exit in the handler. If it does, it would get a chance to cleanup as normal.
Note that if a signal for which a handler was defined comes in during a system call, the system call exits with error EINTR in order to allow the program to safely handle the signal. This is why sleep returns as soon as SIGINT is received.
If instead you had used $SIG{INT} = 'IGNORE';, the signal would have been completely ignored. Any systems calls in progress won't be interrupted.
On my system, man 1 kill lists the default actions of signals.
Your signal handler $SIG{INT} = sub {} isn't doing nothing, it is trapping the signal and preventing the program from exiting.
But to answer your original question, END blocks, as perlmod says:
is executed as late as possible, that is, after perl has finished running the program and just before the interpreter is being exited, even if it is exiting as a result of a die() function. (But not if it's morphing into another program via exec, or being blown out of the water by a signal--you have to trap that yourself (if you can).)
That is, a fatal signal, if not trapped, circumvents Perl's global destruction and does not call END blocks.
This question already has answers here:
Killing child and its children when child was created using open
(4 answers)
Closed 6 years ago.
Is there an easy way for Perl to kill child processes when the parent is killed? When I run kill on a parent PID the children stay alive.
Test script:
#!/usr/bin/perl
#sleeps = qw( 60 70 80 );
#pids = ();
foreach $sleeptime (#sleeps) {
my $pid = fork();
if ( not defined $pid ) {
die;
}
elsif ( $pid == 0 ) {
#child
system("sleep $sleeptime");
exit;
}
else {
#parent
push #pids, $pid;
}
}
foreach $pid (#pids) {
waitpid( $pid, 0 );
}
Note The second example, using END block, is more complete.
Note Discussion of how to use the process group for this is at the end.
Most of the time chances are that you are dealing with SIGTERM signal. For this you can arrange to clean up child processes, via a handler. There are signals that cannot be trapped, notably SIGKILL and SIGSTOP. For those you'd have to go to the OS, per answer by Kaz. Here is a sketch for others. Also see other code below it, comments below that, and process group use at the end.
use warnings;
use strict;
use feature qw(say);
say "Parent pid: $$";
$SIG{TERM} = \&handle_signal; # Same for others that can (and should) be handled
END { say "In END block ..." }
my #kids;
for (1..4) {
push #kids, fork;
if ($kids[-1] == 0) { exec 'sleep 20' }
}
say "Started processes: #kids";
sleep 30;
sub handle_signal {
my $signame = shift;
say "Got $signame. Clean up child processes.";
clean_up_kids(#kids);
die "Die-ing for $signame signal";
};
sub clean_up_kids {
say "\tSending TERM to processes #_";
my $cnt = kill 'TERM', #_;
say "\tNumber of processes signaled: $cnt";
waitpid $_, 0 for #_; # blocking
}
When I run this as signals.pl & and then kill it, it prints
[13] 4974
Parent pid: 4974
Started processes: 4978 4979 4980 4982
prompt> kill 4974
Got TERM. Clean up child processes.
Sending TERM to processes 4978 4979 4980 4982
Number of processes signaled: 4
Die-ing for TERM signal at signals.pl line 25.
In END block ...
[13] Exit 4 signals.pl
The processes do get killed, checked by ps aux | egrep '[s]leep' before and after kill.
By courtesy of die the END block gets executed orderly so you can clean up child processes there. That way you are also protected against uncaught die. So you'd use the handler merely to ensure that the END block cleanup happens.
use POSIX "sys_wait_h";
$SIG{CHLD} = sub { while (waitpid(-1, WNOHANG) > 0) { } }; # non-blocking
$SIG{TERM} = \&handle_signal;
END {
clean_up_kids(#kids);
my #live = grep { kill 0, $_ } #kids;
warn "Processes #live still running" if #live;
}
sub clean_up_kids {
my $cnt = kill 'TERM', #_;
say "Signaled $cnt processes.";
}
sub handle_signal { die "Die-ing for " . shift }
Here we reap (all) terminated child processes in a SIGCHLD handler, see Signals in perlipc and waitpid. We also check in the end whether they are all gone (and reaped).
The kill 0, $pid returns true even if the child is a zombie (exited but not reaped), and this may happen in tests as the parent checks right after. Add sleep 1 after clean_up_kids() if needed.
Some notes. This is nowhere near to a full list of things to consider. Along with mentioned (and other) Perl docs also see UNIX and C documentation as Perl's ipc is built directly over UNIX system tools.
Practically all error checking is omitted here. Please add
Waiting for particular processes is blocking so if some weren't terminated the program will hang. The non-blocking waitpid has another caveat, see linked perlipc docs
Child processes may have exited before the parent was killed. The kill 'TERM' sends SIGTERM but this doesn't ensure that the child terminates. Processes may or may not be there
Signal handlers may get invalidated in the END phase, see this post. In my tests the CHLD is still handled here but if this is a problem re-install the handler, as in the linked answer
There are modules for various aspects of this. See sigtrap pragma for example
One is well advised to not do much in signal handlers
There is a lot going on and errors can have unexpected and far ranging consequences
If you kill the process group you won't have any of these issues, since all children are then terminated as well. On my system this can be done at the terminal by
prompt> kill -s TERM -pid
You may have to use a numeric signal, generally 15 for TERM, see man kill on your system. The -pid stands for the process group, signified by the minus sign. The number is the same as the process ID, but add say getpgrp; to the code to see. If this process has not been simply launched by the shell, but say from another script, it will belong to its parent's process group, and so will its children. Then you need to set its own process group first, which its children will inherit, and then you can kill that process group. See setpgrp and getpgrp.
This is something that has to be done at the operating system in order to be reliable. Linux has a prctl (process control) system call which multiplexes numerous functions, similarly to ioctl. One of the functions (opcode PR_SET_PDEATHSIG) arranges for a process to receive a specific signal (passed as the second argument) when its parent dies.
There is a CPAN module wrapping up prctl where this appears as a set_pdeathsig function which takes the signal number.
Without prctl, a child process (which wasn't spawned by the init process and thus has a parent process ID other than 1, initially) can periodically test the value of getppid(). If that changes to 1, that means its original parent died.
Processes which manage children can also implement graceful shutdown logic: catch their termination signal and terminate their children as part of the cleanup. Of course, that is impossible for signals that can't be handled.
How about the following:
kill( -1 * getpgid(), 9 );
(9 is SIGKILL: no need for macros.)
BEGIN {
while (1) {
print "hi\n";
}
}
END {
print "end is called\n";
}
in shell:
kill <pid>
OUTPUT:
hi
hi
hi
hi
hi
hi
hi
hi
hi
Terminated
The end block didnt get called when i killed it via kill or ctrl-c.
Is there something equivalent that will always get called before program exits
Ctrl C sends a SIGINT to your program. You can 'catch' this with a signal handler by setting the appropriate entry in %SIG. I would note - I don't see why you're using BEGIN that way. BEGIN is a special code block that's called at compile time - at the very first opportunity. That means it's triggered when you run perl -c to validate your code, and as such is really a bad idea to set as an infinite loop. See: perlmod
E.g.
#!/usr/bin/perl
use strict;
use warnings;
$SIG{'INT'} = \&handle_kill;
my $finished = 0;
sub handle_kill {
print "Caught a kill signal\n";
$finished++;
}
while ( not $finished ) {
print "Not finished yet\n";
sleep 1;
}
END {
print "end is called\n";
}
But there's a drawback - some signals you can't trap in this way. See perlipc for more details.
Some signals can be neither trapped nor ignored, such as the KILL and STOP (but not the TSTP) signals. Note that ignoring signals makes them disappear. If you only want them blocked temporarily without them getting lost you'll have to use POSIX' sigprocmask.
By default if you send a kill, then it'll send a SIGTERM. So you may want to override this handler too. However it's typically considered bad to do anything other than exit gracefully with a SIGTERM - it's more acceptable to 'do something' and resume when trapping SIGHUP (Hangup) and SIGINT.
You should note that Perl does 'safe signals' though - and so some system calls won't be interrupted, perl will wait for it to return before processing the signal. That's because bad things can happen if you abort certain operations (like close on a file where you're flushing data might leave it corrupt). Usually that's not a problem, but it's something to be aware of.
put the proper signal handler in your code:
$SIG{INT} = sub { die "Caught a sigint $!" };
the control-c sends the SIGINT signal to the script, who is catched by this handler
I am using perl v14 on windows. I have 2 simple files:
$SIG{'INT'} = sub {
print "got SIGINT\n";
#some useful code to be executed
#on reception of signal
};
$SIG{'ALRM'} = sub {
print "got SIGALRM\n";
};
print "my pid: ",$$,"\n";
while(1)
{
print "part 1\n";
sleep(3);
print "part 2\n\n";
sleep(3);
}
the above file starts and waits to be killed having given its pid.
The second file simply kills the first perl process using its pid(set manually).
$pid = xxxx; #this is the manually entered pid for I process
print "will attempt to kill process: $pid\n";
kill INT, $pid;
What I run the first perl script and press Ctrl-C, the handler works as expected but using the second file I can't get the same result. I have also tried with other signals like ALRM,HUP,TERM,FPE but no success. All I want to do is to execute the code in the signal handler.
I found something called INT2 signal for win32.
Thanks in advance.
Windows does let you use signals only within the same thread. So signaling different processes will not work.
Instead of signals you could use other methods of interprocess communication like sockets, pipes or files.
From perlwin32:
Signal handling may not behave as on Unix platforms (where it doesn't
exactly "behave", either :). For instance, calling die() or exit()
from signal handlers will cause an exception, since most
implementations of signal() on Windows are severely crippled. Thus,
signals may work only for simple things like setting a flag variable
in the handler. Using signals under this port should currently be
considered unsupported.