Why does my bash script take so long to respond to kill when it runs in the background?

545 views Asked by At

(Question revised, now that I understand more about what's actually happening):

I have a script that runs in the background, periodically doing some work and then sleeping for 30 seconds:

echo "background script PID: $$"
trap 'echo "Exiting..."' INT EXIT
while true; do
    # check for stuff to do, do it
    sleep 30
done &

If I try to kill this script via kill or kill INT, it takes 30 seconds to respond to the signal.

I will answer this question below, since I found a good explanation online.

(My original, embarrassingly un-researched question)

This question is for a bash script that includes the following trap:

trap 'echo "Exiting...">&2; kill $childPID 2>/dev/null; exit 0' \
SIGALRM SIGHUP SIGINT SIGKILL SIGPIPE SIGPROF SIGTERM \
SIGUSR1 SIGUSR2 SIGVTALRM SIGSTKFLT

If I run the script in the foreground, and hit CTRL-C, it gets the signal immediately and exits (under one sec).

If I run the same script in the background (&), and kill it via kill or kill -INT, it takes 30 seconds before getting the signal.

Why is that, and how can I fix it?

3

There are 3 answers

1
Jens On BEST ANSWER

Possible reason: signals issued while a process is sleeping are not delivered until wake-up of the process. When started via the command line, the process doesn't sleep, so the signal gets delivered immediately.

0
RashaMatt On

As explained in http://mywiki.wooledge.org/SignalTrap --

"When bash is executing an external command in the foreground, it does not handle any signals received until the foreground process terminates" - and since sleep is an external command, bash does not even see the signal until sleep finishes.

That page has a very good overview of signal processing in bash, and work-arounds to this issue. Briefly, one correct way of handling the situation is to send the signal to the process group instead of just the parent process:

kill -INT -123   # will kill the process group with the ID 123

Head over to the referenced page for a full explanation (no sense in my reproducing any more of it here).

0
Mike S On

@RashaMatt, I was unable to get the read command to work as advertised on Greg's wiki. Sending a signal to the script simply did not interrupt the read. I needed to do this:

#!/bin/bash
bail() {
        echo "exiting"
        kill $readpid
        rm -rf $TMPDIR
        exit 0
}

sig2() {
  echo "doing stuff"
}

echo Shell $$ started.
trap sig2 SIGUSR2
trap bail SIGUSR1 SIGHUP SIGINT SIGQUIT SIGTERM
trap -p

TMPDIR=$(mktemp -p /tmp -d .daemonXXXXXXX)
chmod 700 $TMPDIR
mkfifo $TMPDIR/fifo
chmod 400 $TMPDIR/fifo

while : ; do
        read < $TMPDIR/fifo & readpid=$!
        wait $readpid
done

...send the desired signal to the shell's pid displayed from the Shell $$ started line, and watch the excitement.

waiting on a sleep is simpler, true, but some os' don't have sleep infinity, and I wanted to see how Greg's read example would work (which it didn't).