0

I have a very simple script to trap signals:

#!/bin/bash                                                                     
unlink SigTermRecieved
unlink SigIntRecieved
#trap " touch SigTermRecieved ; exit " SIGTERM                                  
trap " touch SigIntRecieved ; exit " SIGINT
echo "pid is $$"
while true
do
    sleep 60m
done
exit

BEHAVIOR

with both trap lines commented:
SIGTERM sent: terminates program.
? SIGINT sent from another terminal window is IGNORED!
CTRL C terminates program.

with SIGTERM trap uncommented:
? SIGTERM sent: program does not terminate and SIGTERM trap is not executed.
? SIGINT sent from another terminal window is IGNORED!
CTRL C terminates program.

with SIGINT trap uncommented:
SIGTERM sent: terminates program.
? SIGINT from another terminal window is IGNORED!
CTRL C executes trap code and terminates program.

The behaviors preceded by ? are unexpected or wrong. I'm not too concerned about the SIGINT behaviors. I tried those to see if any traps would work.

So, I removed the snooze function and just have:

sleep 60m &
wait $!

Now, kill -TERM -pid kills both processes and executes the trap for SIGTERM.

In my application, I'm waiting for SIGTERM from system shutdown, so I can clean up some details. I don't know whether system shutdown uses pid or -pid, both work for me, and the system will eventually kill all the processes. A few dangling sleep processes while I'm testing is no big deal.

That fifo solution is quite complicated and I don't understand it.

Thanks for the help.

2 Answers2

3

[In the comments below, keep in mind that bash (or any other shell) is a C program, which may set a handler for a signal even when the script sets no trap for it]

with both trap lines commented:
SIGTERM sent: terminates program.

Since bash does not set any handler for SIGTERM, that signal will kill it outright, causing the shell it was started from to return the prompt. But the sleep command, which is an external program and runs in a separate child process, will continue to run (pgrep -a sleep to verify).

? SIGINT sent from another terminal window is IGNORED!

When waiting for a child process (as sleep in this case), bash sets a signal handler for SIGINT, and if no trap was set for it, will ignore that signal, except for the case where the child process it's waiting for terminates because of the same signal. bash is able to gather from the exit status of the child whether it was killed by a signal, and which signal it was.

That's the so-called "wait and cooperative exit" (WCE) and only applies to the SIGINT and SIGQUIT signals.

Other shells may not do that, and may simply delay acting on the SIGINT signal until the child they wait for has terminated. Example comparing bash and dash:

$ ( (sleep .2; pkill -INT -P $$)&); dash -c 'while :; do /bin/sleep 3; done'
<dash is killed by a self-raised SIGINT after 3 seconds>

$ ( (sleep .2; pkill -INT -P $$)&); bash -c 'while :; do /bin/sleep 3; done'
^C
<bash has to be killed by hand with Control-C>

CTRL C terminates program.

A Ctrl-C will cause the kernel to send a SIGINT signal to all the processes from the foreground process group (job), which in this case include both your script and its sleep child. You can simulate that by using the kill command with the negated pid of the process group leader (the same as the process group id), or by using the kill shell builtin with the job id:

bash -c 'sleep 3600; echo DONE' &
[1] 12381
kill -INT -12381  # or kill -INT %1

Using the negated pid of the script only works when it really is the process group leader. That may not the case when the script was called from a subshell or another script, or as part of a pipeline.

with SIGTERM trap uncommented:
? SIGTERM sent: program does not terminate and SIGTERM trap is not executed.

Yes, the trap will be executed. But only after the sleep command has terminated. Use sleep 1 instead of sleep 60m if you don't want to wait 60 minutes to confirm it ;-)

This was also explained in the linked answer, and it's a standard-required behavior.

with SIGINT trap uncommented:
? SIGINT from another terminal window is IGNORED!

Ditto.

  • So, is there a way to sleep that is interruptible by SIGTERM? – Gilligan Jan 15 '20 at 20:23
  • Yes, kill the sleep process, too. –  Jan 15 '20 at 20:33
  • You may also try using a fifo, an doing a timed read on it: bash -c 'echo $$; mkfifo fifo; exec 3<>fifo; while :; do read -t2 <fifo; echo next; done'. You may also get some other ideas (not all great) here –  Jan 15 '20 at 20:42
1

I found a simple solution.

function snooze {
    sleep $1
}

while true
do
    snooze 60m &
    wait $!
done

This will execute the trap then terminate immediately after receiving SIGTERM.

Thanks for your assistance.