How to Launch a Background Process Through a Wrapper That'Ll Undo The Sigint Ignore

How do I launch a background process through a wrapper that'll undo the SIGINT ignore?

If the process is execed (=isn't just a subshell but is based on binary), you can run it through a wrapper that'll undo the SIGINT/SIGQUIT ignore:

reset_sigint.c:

#include <signal.h>
#include <unistd.h>
int main(int C, char**V)
{
sigaction(SIGINT,&(struct sigaction){.sa_handler=SIG_DFL}, 0);
execvp(V[1],V+1);
return 127;
}

To prevent the SIGINT/SIGQUIT ignore more generically for any process run as a command in the shell, you can run it with set -m on

sh -c 'set -m; ( sleep 10 )& ps -p $! -o ignored'
#compare with: sh -c '( sleep 10 )& ps -p $! -o ignored'

but that'll run the command in a separate process group as a (possibly undesirable) sideffect.

bash: Why can't I set a trap for SIGINT in a background shell?

SIGINT and SIGQUIT are ignored in backgrounded processes (unless they're backgrounded with set -m on). It's a (weird) POSIX requirement (see
2. Shell Command Language or my SO question Why do shells ignore SIGINT and SIGQUIT in backgrounded processes? for more details).

Additionally, POSIX requires that:

When a subshell is entered, traps that are not being ignored shall be
set to the default actions, except in the case of a command
substitution containing only a single trap command ..

However, even if you set the INT handler in the subshell again after it was reset, the subshell won't be able to receive it because it's ignored (you can try it or you can inspect the signal ignore mask using ps, for example).

Trap and exit big shell script with background processes

shell kill parallelized subtasks

Some remarks:

  • chmod +x is useless here. At all you could run sh command1.sh & instead.
  • You have to kill all subtasks independantly
  • As this question is tagged shell, my answer don't use bashisms. All script here is tested under bash, dash and busybox.

Something like:

#!/bin/sh

for cmd in ./command1.sh ./command2.sh;do
exec $cmd &
PIDS="$PIDS $!"
done

trap "kill $PIDS;exit" 15
wait

Of coure, between for cmd in and ;do, you could put as many commandXX.sh you want (as long you keep line length into maximum supported by your installed OS)

Test script:

Here is a quick bash test script that sleep randomly between 2.0 to 12.99 seconds, then print done. before exit:

#!/bin/bash

declare -i toSleep
case $1 in '' | *[!0-9]* ) toSleep='RANDOM%10+2' ;; * ) toSleep=$1 ;; esac

exec {dummy}<> <(:)
read -t $toSleep.$RANDOM -u $dummy _
echo done.

I've saved this into command1.sh, chmod +x .. and linked to command2.sh...

More portable wrapper:

#!/bin/sh

for cmd in "$@";do
exec $cmd &
PIDS="$PIDS $!"
done

printf "You have to: kill -TERM %d\nto end %d tasks: %s\n" \
$$ $(echo $PIDS|wc -w) "$PIDS"

trap "kill $PIDS;echo 'Process $$ killed.';exit" 15
wait

echo "Process $$ running $@ ended normally"

You could save this shell script into a file named simpleParallel.sh, for sample, then:

chmod +x simpleParallel.sh

./simpleParallel.sh ./command1.sh ./command2.sh
You have to: kill -TERM 741297
to end 2 tasks: 741298 741299

Then if you kill -TERM 741297 from elsewhere,

Process 741297 killed.

But if you don't, you may read something like:

./simpleParallel.sh ./command1.sh ./command2.sh 
You have to: kill -TERM 741968
to end 2 tasks: 741969 741970
done.
done.
Process 741968 running ./command1.sh ./command2.sh ended normally

Nota: If you send your kill command while one process is already done, you may see error message like:

./simpleParallel.sh: 1: kill: No such process

See bash version could avoid this bug.

Following this from another terminal console

Before running ./simpleParallel.sh, you could run tty without argument:

tty
/dev/pts/2

Then in a new free window, you could run:

watch ps --tty pts/2

while using another window again to run kill command.

bash with some bashisms, now:

Under recent bash, there are lot of special feature.

  • You could use Array to store backgrounded tasks's PIDs.
  • From version 5.0, There is an $EPOCHREALTIME variable, which expands to the time in seconds since the Unix epoch with microsecond granularity.
  • From version 5.0, Associative arrays allow subscripts containing whitespace.
  • From version 5.1, wait has a new [-p VARNAME] option, which stores the PID returned by wait -n or wait without arguments.

My script could become:

#!/bin/bash
declare -A PIDS ENDED
started=$EPOCHREALTIME
for cmd; do
exec "$cmd" &
PIDS["$cmd"]=$!
CMDS[$!]="$cmd"
done
printf "You have to: kill -TERM %d\nto end %d tasks: %s\n" \
$$ ${#PIDS[@]} "${PIDS[*]}"

trapExit(){
kill "${PIDS[@]}"
printf 'Process %s + %d task killed: %s\n' $$ ${#PIDS[@]} "${!PIDS[*]}"
showDone
exit 1
}
trap trapExit 15
showDone() {
for cmd in "${!ENDED[@]}";do
read -r elap < <(bc -l <<<"${ENDED["$cmd"]}-$started")
printf "Elapsed: %.4f sec for %s\n" "$elap" "$cmd"
done
}
while ((${#PIDS[@]}));do if wait -n -p pid ;then
printf "Process %d done (%s).\n" "$pid" "${CMDS[pid]}"
ENDED["${CMDS[pid]}"]=$EPOCHREALTIME
unset PIDS["${CMDS[pid]}"] CMDS[pid]
fi; done
echo "Process $$ running $* ended normally"
showDone

Output could look like:

./simpleParallel.bash ./command{1,2}.sh
You have to: kill -TERM 1309816
to end 2 tasks: 1309817 1309818
done.
Process 1309817 done (./command1.sh).
done.
Process 1309818 done (./command2.sh).
Process 1309816 running ./command1.sh ./command2.sh ended normally
Elapsed: 3.1644 sec for ./command1.sh
Elapsed: 4.2488 sec for ./command2.sh

or

./simpleParallel.bash ./command{1,2}.sh
You have to: kill -TERM 1310031
to end 2 tasks: 1310032 1310033
done.
Process 1310033 done (./command2.sh).
done.
Process 1310032 done (./command1.sh).
Process 1310031 running ./command1.sh ./command2.sh ended normally
Elapsed: 9.1868 sec for ./command1.sh
Elapsed: 3.2310 sec for ./command2.sh

If you kill them early:

./simpleParallel.bash ./command{1,2}.sh
You have to: kill -TERM 1294577
to end 2 tasks: 1294578 1294579
Process 1294577 + 2 task killed: ./command1.sh ./command2.sh

If you kill them later:

./simpleParallel.bash ./command{1,2}.sh
You have to: kill -TERM 1294958
to end 2 tasks: 1294959 1294960
done.
Process 1294959 done (./command1.sh).
Process 1294958 + 1 task killed: ./command2.sh
Elapsed: 3.1779 sec for ./command1.sh

or

./simpleParallel.bash ./command{1,2}.sh
You have to: kill -TERM 1294971
to end 2 tasks: 1294972 1294973
done.
Process 1294973 done (./command2.sh).
Process 1294971 + 1 task killed: ./command1.sh
Elapsed: 6.9344 sec for ./command2.sh

and no error message about trying to kill unexistant pid.

Externally disabling signals for a Linux program

The process signal mask is inherited across exec, so you can simply write a small wrapper program that blocks SIGINT and executes the target:

#include <signal.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
sigset_t sigs;

sigemptyset(&sigs);
sigaddset(&sigs, SIGINT);
sigprocmask(SIG_BLOCK, &sigs, 0);

if (argc > 1) {
execvp(argv[1], argv + 1);
perror("execv");
} else {
fprintf(stderr, "Usage: %s <command> [args...]\n", argv[0]);
}
return 1;
}

If you compile this program to noint, you would just execute ./noint ./y.

As ephemient notes in comments, the signal disposition is also inherited, so you can have the wrapper ignore the signal instead of blocking it:

#include <signal.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
struct sigaction sa = { 0 };

sa.sa_handler = SIG_IGN;
sigaction(SIGINT, &sa, 0);

if (argc > 1) {
execvp(argv[1], argv + 1);
perror("execv");
} else {
fprintf(stderr, "Usage: %s <command> [args...]\n", argv[0]);
}
return 1;
}

(and of course for a belt-and-braces approach, you could do both).

Signal handling in a shell script

After a while I can see that both myscript.sh and Aprogram have been dead. However 'sleep 500 &' is still running.

As soon as Aprogram is finished myscript.sh prints "Done" and is also finised. sleep 500 gets process with PID 1 as a parent. That is it.

Can anyone explain why is this behavior?

SIGINT is not deliverd to Aprogram when myscript.sh gets it. Use strace to make sure that Aprogram does not receive a signal.

Also, when I issued SIGINT signal to the 'myscript.sh' what exactly is happening?

I first thought that it is the situation like when a user presses Ctrl-C and read this http://www.cons.org/cracauer/sigint.html. But it is not exactly the same situation. In your case shell received SIGINT but the child process didn't. However, shell had at that moment a child process and it did not do anything and kept waiting for a child. This is strace output on my computer after sending SIGINT to a shell script waiting for a child process:

>strace -p 30484
Process 30484 attached - interrupt to quit
wait4(-1, 0x7fffc0cd9abc, 0, NULL) = ? ERESTARTSYS (To be restarted)
--- SIGINT (Interrupt) @ 0 (0) ---
rt_sigreturn(0x2) = -1 EINTR (Interrupted system call)
wait4(-1,

Why is 'Aprogram' getting killed and why not 'sleep' ? How is the signal INT getting transmitted to it's child processes?

As far as I can see with strace a child program like your Aprogram is not getting killed. It did not receive SIGINT and finished normally. As soon as it finished your shell script also finished.

Can I send a ctrl-C (SIGINT) to an application on Windows?

The closest that I've come to a solution is the SendSignal 3rd party app. The author lists source code and an executable. I've verified that it works under 64-bit windows (running as a 32-bit program, killing another 32-bit program), but I've not figured out how to embed the code into a windows program (either 32-bit or 64-bit).

How it works:

After much digging around in the debugger I discovered that the entry point that actually does the behavior associated with a signal like ctrl-break is kernel32!CtrlRoutine. The function had the same prototype as ThreadProc, so it can be used with CreateRemoteThread directly, without having to inject code. However, that's not an exported symbol! It's at different addresses (and even has different names) on different versions of Windows. What to do?

Here is the solution I finally came up with. I install a console ctrl handler for my app, then generate a ctrl-break signal for my app. When my handler gets called, I look back at the top of the stack to find out the parameters passed to kernel32!BaseThreadStart. I grab the first param, which is the desired start address of the thread, which is the address of kernel32!CtrlRoutine. Then I return from my handler, indicating that I have handled the signal and my app should not be terminated. Back in the main thread, I wait until the address of kernel32!CtrlRoutine has been retrieved. Once I've got it, I create a remote thread in the target process with the discovered start address. This causes the ctrl handlers in the target process to be evaluated as if ctrl-break had been pressed!

The nice thing is that only the target process is affected, and any process (even a windowed process) can be targeted. One downside is that my little app can't be used in a batch file, since it will kill it when it sends the ctrl-break event in order to discover the address of kernel32!CtrlRoutine.

(Precede it with start if running it in a batch file.)

How do I terminate all the subshell processes?

Here's a simpler solution -- just add the following line at the top of your script:

trap "kill 0" SIGINT

Killing 0 sends the signal to all processes in the current process group.



Related Topics



Leave a reply



Submit