Why bash background task ignores SIGINT? - linux

I noticed that sleep can't be killed by SIGINT when spawned by:
(sleep 1000 &)
I wonder why is so.
All of the following are killed by SIGINT:
sleep 1000
sleep 1000 &
(sleep 1000)
( ( (sleep 1000) ) )
( ( (sleep 1000)& ) )
so I figure it must have something to do with non-interactive bash (brackets are required to enter subshell) and task have to be run in the background.
I wrote a short C program to test the behaviour and found that sa_handler is set to SIG_IGN -- explaining phenomenon, but why exactly is so?
I haven't found any information whether it is an intended feature (though considering length of manual I may have simply missed it) and if so, what was the reason behide it.
I include the C code for those interested:
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
int main() {
struct sigaction oldact;
if(sigaction(SIGINT, NULL, &oldact) != 0) {
printf("Error in sigaction\n");
exit(1);
}
if(oldact.sa_flags & SA_SIGINFO) {
printf("Using sa_sigaction\n");
} else {
if(oldact.sa_handler == SIG_DFL) {
printf("Default action\n");
} else if(oldact.sa_handler == SIG_IGN) {
printf("Ignore signal\n");
} else {
printf("Other action\n");
}
}
return 0;
}
EDIT:
pilcrow answer is great and I accepted it. I wanted to add as to why posix says so that, according to signal(7) both SIGINT and SIGQUIT are from keyboard. So it kinda makes sens to ignore them in processes detached from one (and not job controled by bash).
EDIT2:
Checkout Mark Plotnick comment for true explanation WHY.

This construct, (sleep 1000 &) puts your sleep command in a grandchild subshell with no job control:
your-shell
\
a compound-list in a subshell
\
an asynchronous list in a subshell
The first subshell, ( compound-list ) (a grouping command construct), simply runs the backgrounded command & (an asynchronous list) and then exits. The asynchronous list is run in its own subshell.
That final subshell is too far removed from your initial shell for job control to be meaningful.
Per POSIX, "[i]f job control is disabled ... when the shell executes an asynchronous list, the commands in the list shall inherit from the shell a signal action of ignored (SIG_IGN) for the SIGINT and SIGQUIT signals."
Thus your sleep is run with SIGINT set to ignore.

Related

Perl: fork from parallel threads

Below code is supposed to run 2 parallel threads, each thread executes fork(), waits for child process to finish then threads are expected to join (finish) and result is printed.
In reality, first forked child process finishes as expected, but second one hangs on _mutex_lock() trying to exit, thus second thread never joins till you kill second child manually with -9 signal.
Could somebody please explain why this happens, and how to avoid this?
use strict;
use warnings;
use threads;
use Data::Dumper;
sub Run
{
my $tid = threads->tid();
my $log = {};
$log->{"[$$:$tid]:00"} = "started";
my $pid = fork();
if ($pid == 0)
{
print "In child ($$): started\n";
sleep 3;
print "In child ($$): finished\n";
# system("kill -9 $$"); -- brutal way
exit 0;
}
waitpid($pid, 0);
my $exitCode = $? >> 8;
$log->{"[$$:$tid]:01"} = "finished, code=$exitCode";
return $log;
}
my #threads = ();
foreach (1..2)
{
push #threads, threads->new(sub { return Run() });
}
print Dumper($_->join()) for #threads;
On my Linux box, using _exit from POSIX instead of exit works. This solution might be non-portable to other platforms, though.
The linked documentation says:
Note that when using threads and in Linux this is not a good way to exit a thread because in Linux processes and threads are kind of the same thing (Note: while this is the situation in early 2003 there are projects under way to have threads with more POSIXly semantics in Linux). If you want not to return from a thread, detach the thread.
Similarly, perlthrtut - Tutorial on threads in Perl says
Thinking of mixing fork() and threads? Please lie down and wait until the feeling passes. Be aware that the semantics of fork() vary between platforms. For example, some Unix systems copy all the current threads into the child process, while others only copy the thread that called fork(). You have been warned!

Is there a better way to detect that my Perl script was called from "firstboot"?

I have a Perl script that needs to act in a particular way if it was invoked by the firstboot script or invoked by a process that firstboot spawned. I have this routine handleFirstBoot and it seems to work ok, but there is probably better way to write this routine. So please take a look ...
sub handleFirstBoot {
my $child_id = shift || $$;
my $parent_id;
foreach (`ps -ef`) {
my ($uid,$pid,$ppid) = split;
next unless ($pid eq $child_id);
$parent_id = $ppid;
last;
}
if ( $parent_id == 0 ) {
debug "firstboot is NOT an ancestor.\n";
return;
}
my $psout = `ps -p $parent_id | tail -1 |sed -e's/^ //g'| sed -e's/ */ /g'|cut -d' ' -f4`;
if ( $psout =~ /firstboot/ ) {
debug "firstboot IS an ancestor. Set post option.\n";
$opt{'post'} = 1;
return;
} else {
# recursive case
handleFirstBoot($parent_id);
}
}
Can I offer an alternative approach - from the comments, the problem you are trying to solve is a startup script stalling because it's waiting for this one to return.
So can I suggest that fork() is probably your friend here?
my $pid = fork();
if ( $pid ) {
exit;
}
sleep $delay_time;
do_stuff();
What will happen is - your script will be called, and the caller will return immediately, but a parallel instance will spawn and delay the random delay interval - and for bonus points, this will work the same in cron too.
But as you seem to note in the comments - the 'good' solution is not to do it that way at all - I would suggest that looking at say, anacron which is available on most Linux systems would be exactly the tool for this particular job.

Log message in perl every 90 seconds in the parent process as long as the child process still runs

I just passed over from php to perl due to my company's request so even if this may be a silly question is kind of nerve wreaking right now.
I have one little perl script deployed on a server through a debian package. I have this all figured out so that's all cool.
Now this script is called from another server through an SSH connection and the script logs back to that server all its actions. I use Log::Log4perl for that.
One of the tasks takes a very long time and also runs some other scripts in the process. The ssh connection has a set timeout of 5 minutes unless I log something back. So I figured out I would create a child process to run the task and let the parent process log back every 90 (or whatever) seconds. My issue is that I don't want to use sleep because if the task is finished sooner it will mess up the log.
I have also tried using Time, Time::HiRes and alarm, but they all mess up my log one way or another.
This is my code:
$log->info("uid $uid: calling the configure script for operation $mode,on $dst_path");
my $pid = fork();
die "Could not fork\n" if not defined $pid;
if ( $pid == 0 ) {
configure( $script_dir, $mode, $node, $uid, $gid); # this also uses a parallel process in its execution, but we don't have a non blocking wait
}
while ( !waitpid( $pid, WNOHANG ) ) {
sleep(90);
if ( !$pid ) {
$log->info("Still waiting for the process to finish"); # this should come up every 90 seconds of so
}
}
$log->info("uid $uid: configure script executed"); # this should come up only once, now I get it every 90 seconds
# do other stuff here after the execution of the configure sub is done
Unfortunately I inherited this architecture as it is and cannot change it because there are a lot of services based on it.
If you don't want to sleep, you can call select with a timeout. To implement this reliably, you can employ the self-pipe trick which involves creating a pipe, writing to the pipe in a SIGCHLD handler, and making the select call wait on the pipe's read handle.
Here's a simple example:
#!/usr/bin/perl
use strict;
use warnings;
use Errno qw(EINTR);
use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
use Symbol qw(gensym);
sub make_non_blocking {
my $handle = shift;
my $flags = fcntl($handle, F_GETFL, 0)
or die("F_GETFL: $!");
fcntl($handle, F_SETFL, $flags | O_NONBLOCK)
or die("F_SETFL: $!");
}
my ($read_handle, $write_handle) = (gensym, gensym);
pipe($read_handle, $write_handle)
or die("pipe: $!");
make_non_blocking($read_handle);
make_non_blocking($write_handle);
local $SIG{CHLD} = sub {
syswrite($write_handle, "\0", 1);
};
my $pid = fork();
die("fork: $!") if !defined($pid);
if ($pid == 0) {
sleep(10);
exit;
}
my $rin = '';
vec($rin, fileno($read_handle), 1) = 1;
while (1) {
my $nfound = select(my $rout = $rin, undef, undef, 2);
if ($nfound < 0) {
# Error. Must restart the select call on EINTR.
die("select: $!") if $! != EINTR;
}
elsif ($nfound == 0) {
# Timeout.
print("still running...\n");
}
else {
# Child exited and pipe was written to.
last;
}
}
waitpid($pid, 0);
close($read_handle);
close($write_handle);
I tried to run the code and noticed a few things that may be your issue, but without knowing what configure does, I can't be sure. Here's what I found:
The child process doesn't exit after calling configure
waitpid does not change the value of $pid, so $pid is always 0 in the child and always the pid of the child in the parent.
What this means is that the parent is never writing out "Still waiting for the process to finish", the child is writing it out every 90 seconds after it completes it's call to configure.
Additionally, the child should print that message ever 90 seconds forever because it's waiting for pid 0 to send it the CHLD signal which won't happen because it doesn't have a child with pid 0.
I updated your code with a few stubs that does what I think you want (on a slightly tighter timeline because I don't like to wait :) ). My code makes the following assumptions that you may wish to change:
Log the waiting message every second
The child always exits with a status value of 0
Here's my code:
#!/usr/bin/env perl
use strict;
use warnings;
use Log::Log4perl qw(:easy);
use POSIX qw(:sys_wait_h);
Log::Log4perl->easy_init();
my ($uid,$mode,$dst_path,$script_dir,$node,$gid) = (0..5);
my $log = get_logger();
$log->info("uid $uid: calling the configure script for operation $mode,on $dst_path");
my $pid = fork();
die "Could not fork\n" if not defined $pid;
if ( $pid == 0 ) {
configure( $script_dir, $mode, $node, $uid, $gid); # this also uses a parallel process in its execution, but we don't have a non blocking wait
exit(0);
}
my $zombie;
while ( ($zombie = waitpid( $pid, WNOHANG ) ) != $pid) {
$log->info("Still waiting for the process to finish"); # this should come up every 90 seconds of so
sleep(1);
}
$log->info("uid $uid: configure script executed"); # this should come up only once, now I get it every 90 seconds
# do other stuff here after the execution of the configure sub is done
sub configure {
sleep 10;
}

Perl 5.8: possible to get any return code from backticks when SIGCHLD in use

When a CHLD signal handler is used in Perl, even uses of system and backticks will send the CHLD signal. But for the system and backticks sub-processes, neither wait nor waitpid seem to set $? within the signal handler on SuSE 11 linux. Is there any way to determine the return code of a backtick command when a CHLD signal handler is active?
Why do I want this? Because I want to fork(?) and start a medium length command and then call a perl package that takes a long time to produce an answer (and which executes external commands with backticks and checks their return code in $?), and know when my command is finished so I can take action, such as starting a second command. (Suggestions for how to accomplish this without using SIGCHLD are also welcome.) But since the signal handler destroys the backtick $? value, that package fails.
Example:
use warnings;
use strict;
use POSIX ":sys_wait_h";
sub reaper {
my $signame = shift #_;
while (1) {
my $pid = waitpid(-1, WNOHANG);
last if $pid <= 0;
my $rc = $?;
print "wait()=$pid, rc=$rc\n";
}
}
$SIG{CHLD} = \&reaper;
# system can be made to work by not using $?, instead using system return value
my $rc = system("echo hello 1");
print "hello \$?=$?\n";
print "hello rc=$rc\n";
# But backticks, for when you need the output, cannot be made to work??
my #IO = `echo hello 2`;
print "hello \$?=$?\n";
exit 0;
Yields a -1 return code in all places I might try to access it:
hello 1
wait()=-1, rc=-1
hello $?=-1
hello rc=0
wait()=-1, rc=-1
hello $?=-1
So I cannot find anywhere to access the backticks return value.
This same issue has been bugging me for a few days now. I believe there are 2 solutions required depending on where you have your backticks.
If you have your backticks inside the child code:
The solution was to put the line below inside the child fork. I think your statement above "if I completely turn off the CHLD handler around the backticks then I might not get the signal if the child ends" is incorrect. You will still get a callback in the parent when the child exits because the signal is only disabled inside the child. So the parent still gets a signal when the child exits. It's just the child doesn't get a signal when the child's child (the part in backticks) exits.
local $SIG{'CHLD'} = 'DEFAULT'
I'm no Perl expert, I have read that you should set the CHLD signal to the string 'IGNORE' but this did not work in my case. In face I believe it may have been causing the problem. Leaving that out completely appears to also solve the problem which I guess is the same as setting it to DEFAULT.
If you have backticks inside the parent code:
Add this line to your reaper function:
local ($!, $?);
What is happening is the reaper is being called when your code inside the backticks completes and the reaper is setting $?. By making $? local it does not set the global $?.
So, building on MikeKull's answer, here is a working example where the fork'd child uses backticks and still gets the proper return code. This example is a better representation of what I was doing, while the original example did not use forks and could not convey the entire issue.
use warnings;
use strict;
use POSIX ":sys_wait_h";
# simple child which returns code 5
open F, ">", "exit5.sh" or die "$!";
print F<<EOF;
#!/bin/bash
echo exit5 pid=\$\$
exit 5
EOF
close F;
sub reaper
{
my $signame = shift #_;
while (1)
{
my $pid = waitpid(-1, WNOHANG);
print "no child waiting\n" if $pid < 0;
last if $pid <= 0;
my $rc = $? >> 8;
print "wait()=$pid, rc=$rc\n";
}
}
$SIG{CHLD} = \&reaper;
if (!fork)
{
print "child pid=$$\n";
{ local $SIG{CHLD} = 'DEFAULT'; print `./exit5.sh`; }
print "\$?=" . ($? >> 8) . "\n";
exit 3;
}
# sig CHLD will interrupt sleep, so do multiple
sleep 2;sleep 2;sleep 2;
exit 0;
The output is:
child pid=32307
exit5 pid=32308
$?=5
wait()=32307, rc=3
no child waiting
So the expected return code 5 was received in the child when the parent's reaper was disabled before calling the child, but as indicated by ikegami the parent still gets the CHLD signal and a proper return code when the child exits.

How to properly exit a child process within a thread?

I am trying to handle timeouts within threads. My script has 4 threads, each thread needs to execute commands, and kill the command process if it takes too long.
What I am doing is:
my $pid;
if (!($pid = fork))
{
my $pid2;
if (!($pid2 = fork))
{
exec_cmd $command;
}
local $SIG{ALRM} = sub {kill 9, $pid2;};
alarm $timeout;
waitpid $pid2, 0;
exit(0);
}
waitpid $pid, 0;
$ret = $?;
This is executed inside a thread, so when the child exits, other threads are still unjoined.
I think you are asking, how can I enforce a time limit on the execution of a child process spawned from a perl thread, and capture that child's exit code?
The easiest thing you could do (on a UNIX system) is to set an alarm on the child process itself:
my $pid = fork();
if (defined($pid) and $pid == 0) {
alarm($timeout); # Preserved across exec()
exec(...);
die "exec(): $!";
}
Exit status will still be available in waitpid/$? as usual.
The safest thing you could do is not to fork while multithreading. It's dangerous both for the application and the implementation. The application, because the child will have running copies of the parent's threads. The implementation, because it's relatively easy to coerce "Unbalanced scopes/saves/tmps/context" errors from threads when doing so.

Resources