Trapping SIGINT in a backgrounded process - bash

I am trying to understand some sample code describing signal handling in bash. In Example 32-7 at http://tldp.org/LDP/abs/html/debugging.html, the writer's comments state that he is capturing a SIGINT, yet the trap is for EXIT.
{
trap "exit" SIGUSR1
sleep $interval; sleep $interval
while true; do
...
done; } & # Start a progress bar as a background process.
pid=$!
trap "echo !; kill -USR1 $pid; wait $pid" EXIT # To handle ^C.
Why does a trap of EXIT send the correct signal (SIGUSR1) to the backgroud process on a SIGINT (Ctl-C)?
Any help is appreciated explaining why this works.

EXIT is a special handler in trap for bash, it's not a signal. There is no exit signal. This trap gets executed whenever the bash processes terminates. So, what this does is make sure that if the user kills the bash process, SIGUSR1 is sent to the background process, which also is trapped and then executes 'exit' on that process. That makes sure if you kill the session, the background process doesn't live on forever but also quits (which is probably what the comment is trying to explain).
edit: I misread this question in my original response

The EXIT pseudo-signal is raised both on normal exit and when the script is being interrupted.

Related

How can I send a signal without the shell waiting for the currently running program to finish?

If I send a signal using kill, it seems to wait until the current program (in this example sleep 1000) finishes running. When I instead send SIGINT via pressing Ctrl+C in the shell, it receives the interrupt immediately however.
What I want, however, is for the interrupt to be received immediately after sending the signal via kill. Also, why does it behave like I would want it to when I press Ctrl+C?
#!/usr/bin/env sh
int_after_a_while() {
local pid=$1
sleep 2
echo "Attempting to kill $pid with SIGINT"
# Here I want to kill the process immediately, but it waits until sleep finishes
kill -s INT $pid
}
trap "echo Interrupt received!" INT
int_after_a_while $$ &
sleep 1000
I would appreciate any help on this issue. Thanks in advance!
As noted in the referenced answer https://unix.stackexchange.com/questions/282525/why-did-my-trap-not-trigger/282631#282631 the shell will normally wait for a utility to complete before running a trap. Some alternatives are:
Start the long running process in the background, then wait for it using the wait builtin. When a trapped signal is received during such a wait, the wait is interrupted and the trap is taken. Unfortunately, the exit status of wait does not distinguish between the child process exiting on a signal and a trap occurring. For example
sleep 1000 &
p=$!
wait "$p"
Send a signal to the whole process group via kill -s INT 0. The effect is much like if the user had pressed Ctrl+C, but may be more extreme than you want if your script is run from another script.
Use a shell such as zsh or FreeBSD sh that supports set -o trapsasync which allows running traps while waiting for a foreground job.

Handling signals in bash script when it started as a background process

Okay, I have a script like this:
trap 'echo "CTRL-C signal was caught!" ' SIGINT
for ((i=0; i<15; i++))
do
sleep 3
done
When I start my script in a usual way, it immediately reacts to CTRL-C command and echo "CTRL-C signal was caught!", even if there is a sleep 3 command. But when I run my script as a background process, it waits until sleep 3 command is finished, and then echo "CTRL-C signal was caught!"
I do not understand this. I think trap should wait until previous command is finished, and then it should echo something, like when it started as a background process.
Bash manual states:
Background processes (...) are immune to keyboard-generated signals.
If bash is waiting for a command to complete and receives a signal for which a trap has been set, the trap will not be executed until the command completes.
Consequently:
If your script runs in the foreground: when you press "Ctrl-C", a SIGINT is sent to the currently running process (i.e. the sleep command). The exit status of sleep tells Bash that it was interrupted by the SIGINT signal and bash calls your trap.
If your script runs in the background, then the backgrounded sleep does not receive the signal and the SIGINT trap is only executed once sleep has ended.

bash trap propagated to command with custom signal handler

In my script I'm trapping signals in the usual way.
function on_stop {
echo 'On Stop'
sleep 10
echo 'Signalling others to exit'
trap - TERM EXIT INT
kill -s INT "$$"
}
./executable_with_custom_signal_handling &
pid=$!
trap 'on_stop' TERM EXIT INT
wait
If sleep is used instead of ./executable_with_custom_signal_handling everything works as expected. Otherwise, ./executable_with_custom_signal_handling receives signal immediately in parallel with on_stop.
I am wondering does it have something to do with a custom signal handling in the executable?
signal(SIGINT, handler)
Any workarounds known?
By default, the shell runs backgrounded commands with SIGINT (and SIGQUIT) ignored.
Your backgrounded sleep is not interrupted by the Ctrl-C SIGINT to the process group, then, because it never sees the signal. When your custom executable installs a new signal action, replacing SIG_IGN, that executable will receive the SIGINT.

How does trap / kill work in bash on Linux?

My sample file
traptest.sh:
#!/bin/bash
trap 'echo trapped' TERM
while :
do
sleep 1000
done
$ traptest.sh &
[1] 4280
$ kill %1 <-- kill by job number works
Terminated
trapped
$ traptest.sh &
[1] 4280
$ kill 4280 <-- kill by process id doesn't work?
(sound of crickets, process isn't killed)
If I remove the trap statement completely, kill process-id works again?
Running some RHEL 2.6.18-194.11.4.el5 at work. I am really confused by this behaviour, is it right?
kill [pid]
send the TERM signal exclusively to the specified PID.
kill %1
send the TERM signal to the job #1's entire process group, in this case to the script pid + his children (sleep).
I've verified that with strace on sleep process and on script process
Anyway, someone got a similar problem here (but with SIGINT instead of SIGTERM): http://www.vidarholen.net/contents/blog/?p=34.
Quoting the most important sentence:
kill -INT %1 sends the signal to the job’s process group, not the backgrounded pid!
This is expected behavior. Default signal sent by kill is SIGTERM, which you are catching by your trap. Consider this:
#!/bin/bash
# traptest.sh
trap "echo Booh!" SIGINT SIGTERM
echo "pid is $$"
while : # This is the same as "while true".
do
a=1
done
(sleep really creates a new process and the behavior is clearer with my example I guess).
So if you run traptest.sh in one terminal and kill TRAPTEST_PROCESS_ID from another terminal, output in the terminal running traptest will be Booh! as expected (and the process will NOT be killed). If you try sending kill -s HUP TRAPTEST_PROCESS_ID, it will kill the traptest process.
This should clear up the %1 confusion.
Note: the code example is taken from tldp
Davide Berra explained the difference between kill %<jobspec> and kill <PID>, but not how that difference results in what you observed. After all, Unix signal handlers should be called pretty much instantaneously, so why does sending a SIGTERM to the script alone not trigger its trap handler?
The bash man page explains why, in the last paragraph of the SIGNALS section:
If bash is waiting for a command to complete and receives a signal for
which a trap has been set, the trap will not be executed until the
command completes.
So, the signal was delivered immediately, but the handler execution was deferred until sleep exited.
Hence, with kill %<jobspec>:
Both the script and sleep received SIGTERM
bash registered the signal, noticed that a trap was set for it, and queued the handler for future execution
sleep exited immediately
bash noted sleep's exit, and ran the trap handler
whereas with kill <script_PID>:
Only the script received SIGTERM
bash registered the signal, noticed that a trap was set for it, and queued the handler for future execution
sleep exited after 1000 seconds
bash noted sleep's exit, and ran the trap handler
Obviously, you didn't want long enough to see that last bit. :)
If you're interested in the gory details, download the bash source code and look in trap.c, specifically the trap_handler() and run_pending_traps() functions.

shell script to spawn processes, terminate children on SIGTERM

I want to write a shell script that spawns several long-running processes in the background, then hangs around. Upon receiving SIGTERM, I want all the subprocesses to terminate as well.
Basically, I want a "master process".
Here's what I got so far:
#!/bin/sh
sleep 600 &
PID1="$!"
sleep 600 &
PID2="$!"
# supposedly this should kill the child processes on SIGTERM.
trap "kill $PID1 $PID2" SIGTERM
wait
The above script fails with trap: 10: SIGTERM: bad trap.
Edit: I'm using Ubuntu 9.04
This works for me:
trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
kill -- -$$ sends a SIGTERM to the whole process group, thus killing also descendants.
Specifying signal EXIT is useful when using set -e (more details here).
Joe's answer put me on the right track.
I also found out I should trap more signals to cover my bases.
Final script looks like this:
#!/bin/sh
sleep 600 &
PID1="$!"
sleep 600 &
PID2="$!"
trap "kill $PID1 $PID2" exit INT TERM
wait
I suspect your /bin/sh is not a Bash (though you tagged the question as 'Bash').
From the message I guess it's a DASH. Check its manual or just fix your shebang if you need to write Bash code.
This script looks correct and works for me as expected.
How do you send the SIGTERM signal to the "master process"?
Maybe you should execute kill -l to check which signals are supported.
As the error message suggests you send signal "10" which your system doesn't seem to recognize.
And next time you should add operating system, shell version, kernel, ... for such a question

Resources