Kill children when the parent is killed [duplicate] - perl

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.)

Related

Perl script to stop bsub job when control c is pressed

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.

In Perl, is it possible to kill an open(SENDMAIL, "|$sendmail") before close(SENDMAIL)

I'm needing to hack an old system that uses open(SENDMAIL, "|$sendmail") and close(SENDMAIL). Is it possible to stop the email from being sent once the stream is open? In this case, if some unexpected spammy content is discovered.
I've tried this with no luck:
$pid = open(SENDMAIL, "|$sendmail");
while (<>) {
....do lots of stuff in here....
# Oops, we need to abort
if ($needToAbort) {
kill 9, $pid;
exit(0);
}
}
close(SENDMAIL);
Even when the loop hits $needToAbort === true, the email still goes out. Best explanation I can find is that kill 9, $pid, is only closing off the stream forcibly, not actually killing it.
In order to verify $pid exists, I'd tried add to the if:
if ($needToAbort) {
$exists = kill 0, $pid;
if ($exists) {
kill 9, $pid;
exit(0);
}
}
Using logging, sometimes the $pid seems to exist and sometimes it doesn't. The system uses perl 5, version 16.
Question: Is it possible, and how would I edit my code to stop the email from being sent?
It seems that the command $sendmail isn't starting the sendmail program directly so the $pid returned by open isn't sendmail's (but shell's?).
Find the PID of the sendmail process itself and kill should work. (Or consder killing the whole process group, see the end).
Instead of doing that by manually parsing ps you can use Proc::ProcessTable
use Proc::ProcessTable;
my $pid = open my $sm_fh, '|-', $sendmail or die "Can't open sendmail: $!";
my $pid_sm;
my $pt = Proc::ProcessTable->new();
foreach my $proc (#{$pt->table}) {
if ($proc->cmndline =~ /^sendmail/) { # adjust regex for your system
$pid_sm = $proc->pid;
say "Sendmail command-line: ", $proc->cmndline;
say "Sendmail process pid: ", $proc->pid;
}
}
kill 9, $pid_sm;
my $gone_pid = waitpid $pid_sm, 0;
say "Process $gone_pid is gone";
# need a handler for SIGPIPE for prints to $sm_fh further in code
On my system the CMD field starts with sendmail, adjust for how it is on yours. If there may be multiple sendmail processes, what is quite possible, you'll need a more thorough analysis.
Since you need to blow the thing out of the water I presume that its following prints can't be modified for a check. (Otherwise you could solve this in much cleaner ways.)
Then you must install a signal handler for SIGPIPE or the program will die at the next attempt to print to that filehandle, since it will get a SIGPIPE and its disposition is to terminate.
Another solution is to wrap sendmail handling in Expect, which sets up a pseudo-terminal so you can send a Ctrl-C when needed. (Its own hard_close method does the job in my tests, too.) But for this the printing statements should be modified so it may be a no-go here.
A little more detail. It was clarified that the command is: /usr/lib/sendmail -f$sender -t
The module's object ($pt above) has a lot of process table fields, listed by $pt->fields, with descriptions at its "stub module". I find it more informative to print and review them all for objects of interest. Some that may be helpful for this purpose are exec, cwd, and various ids.
How exactly to identify a process depends on details of the system, but one way is to look at the command line details.
The example above extended a bit
$SIG{PIPE} = sub { print "Got $_[0]\n" }; # or just $SIG{PIPE} = 'IGNORE';
my $pid_sm;
foreach my $proc (#{$pt->table}) {
my $cmd = $proc->cmndline;
next if $cmd !~ m{^/usr/lib/sendmail};
if ( (split ' ', $cmd)[1] eq "-f$sender" ) {
$pid_sm = $proc->pid;
say "Our process $pid_sm: $cmd";
}
else { say "Some other sendmail: $cmd" }
}
warn "Didn't find our sendmail process" if not $pid_sm;
if ($needToAbort and $pid_sm) {
kill 9, $pid_sm;
my $gone_pid = waitpid $pid_sm, 0;
if ($gone_pid == -1) { say "No process $pid_sm" }
elsif ($gone_pid == 0) { say "Process $pid_sm still around?" }
else { say "Process $gone_pid is gone" }
};
The second field of the command-line is checked against the exact phrase "-f$sender", what can be relexad by using regex instead of eq. Review command-lines printed for all processes above and adjust as needed. If there are problems print out anything that has 'sendmail' in it.
Another option is to kill the process group: kill 9, -$pid (note the minus). This should catch the sendmail process itself, but of course make sure you know what is getting blown away.
To add, I doubt that you need to use SIGKILL (9). Once the right pid is found the SIGTERM (15 on my system, see man 7 signal) may well be good enough, what is much nicer.
Finally, a process can get tied down by the OS and be in an uninterruptible state, in particular in some I/O operation. However, that doesn't seem likely here and I'd first try the two approaches above.

ForkManager SIGINT only kills current process in fork

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', -$$ );
};

perl: can end block be called when program is 'kill'

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

Run a sub in a perl script based on the time?

I have a perl script that runs as a daemon looping all the time. I want to run a subfunction in a perl script that is based on the time (or timer) so every 2hrs it would run that subfunction and continue with it's loop. I'm thinking getting the epoch time and just checking it a few times through the loop and once it's greater then 2hrs it runs the subfunction. Is there a better way to do this in perl?
Thanks,
LF4
This depends on whether there should be 2 hours since the START of the last subroutine launch, or since the END of last execution.
1) If the latter (2 hours between the end of running the last subroutine and the start of new one), cespinoza's solution is perfectly acceptable (loop infinitely, and call sleep(7200); after executing the subroutine).
my $timeout = 7200;
while (1) {
dostuff();
sleep($timeout);
};
The only problem with this is that it can't handle the case where dostuff() takes forever, e.g. gets stuck - for the discussion of why it's an important situation to consider and approaches to solve, see below.
2) If the former (2 hours between starting points), you have three options, related to handling the subroutine run-time that exceeds 2 hours[0]. Your 3 options, explained in detail below, are to either:
2a) kick off a new subroutine while the old one keeps running (in parallel);
2b) to kick off a new subroutine AFTER the old one finishes;
2c) to kick off a new subroutine but first stop the execution of the prior one.
2a an 2c options require you to set an alarm() for 2 hours, and differ in what happens when an alarm gets triggered.
[0] NOTE: since any subroutine is likely to require at least SOME resources from the PC, there's always a - however small - chance that it would exceed 2 hours, so you have to pick one of those 3 options to handle such a scenario.
2a) Kick off every 2 hours, running in parallel with old execution if not finished.
This option is, essentially, implementing cron functionality.
Anytime you hear the word parallel, you would likely fork off the process.
my $timeout = 7200;
while (1) { # Not tested!
eval {
local $SIG{ALRM} = sub { die "alarm\n" };
if (!defined($child_pid = fork())) {
die "cannot fork: $!\n";
} elsif (!$child_pid) { # Child
dostuff();
exit;
} # Parent continues to sleep for 2 hours
alarm $timeout; # You need it in case forking off take >2hrs
sleep; # forever
};
die unless $# eq "alarm\n"; # propagate unexpected errors
# We don't need to check if $# is true due to forever sleep
}
2b) Kick off every 2 hours, if the old one didn't finish, let it run till it finishes
This can be re-worded as "kick off task, if it finishes faster than 2 hours, sleep for the remainder"
my $timeout = 7200;
while (1) {
my $start = time;
dostuff();
my $end = time;
my $lasted = $end - $start;
if ($lasted < $timeout) {
sleep($timeout - $lasted);
}
};
2c) Kick off every two hours, if the previous one didn't finish, time it out and kill it
Whenever you see logic like this, alarm is obviously the answer.
while (1) {
my $finished = 0;
eval {
local $SIG{ALRM} = sub { die "alarm\n" };
alarm 7200;
dostuff();
$finished = 1;
sleep; # forever
};
die unless $# eq "alarm\n"; # propagate unexpected errors
warn "Timed out!!!\n" unless $finished
}
P.S. As cespinoza noted, you need to somehow daemonize the script (ensure it doesn't get killed when you exit the shell that started it), by either Unix means (e.g. launching it as nohup) or Perlish means (search for daemonize + Perl on Stackoverflow for mechanics of that).
Something like crontab would be best to do a timed job like that. However, if you want to run a Perl daemon, you'll have to use some kind of event handler. Two choices off the top of my head are POE and AnyEvent.
You might want to check Schedule::Cron for task planning and execution.