Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle signals in bash during synchronous execution?

Tags:

bash

signals

I have a bash script process which at some point executes a long-running subprocess synchronously. During the run of that subprocess, a signal is sent directly to the bash script process requesting the script to terminate. Is there any way to intercept that signal, terminate the subprocess and then exit the bash process?

Apparently, bash's signal handling never interrupts synchronous calls?

I cannot control the fact that the termination signal is sent to the bash process. Although if the signal could propagate to the child process, that would also solve my issue.

thanks in advance, Broes

like image 946
Broes De Cat Avatar asked Oct 12 '12 10:10

Broes De Cat


People also ask

Does Bash forward signals?

In this case the TERM signal is received by the shell process, but Bash will not forward that signal to the child process. This means that the shell process will stop, but the JVM will continue to run.

How do I interrupt in Bash?

One of the many known methods to exit a bash script while writing is the simple shortcut key, i.e., “Ctrl+X”. While at run time, you can exit the code using “Ctrl+Z”.

How do you trap a Sigkill?

You can't catch SIGKILL (and SIGSTOP ), so enabling your custom handler for SIGKILL is moot. You can catch all other signals, so perhaps try to make a design around those. be default pkill will send SIGTERM , not SIGKILL , which obviously can be caught.


2 Answers

See the man page of bash, chapter 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. When bash is waiting for an asynchronous command via the wait builtin, the reception of a signal for which a trap has been set will cause the wait builtin to return immediately with an exit status greater than 128, immediately after which the trap is executed.

So, run your external program asynchronously and use wait. Kill it using $!.

like image 176
tzp Avatar answered Sep 27 '22 17:09

tzp


Here's a bash utility function I wrote to handle this. It's proved useful and robust. I hope you find it useful.

# Run a command in a way that can be interrupted by a signal (eg SIGTERM)
#
# When bash receives a SIGTERM it normally simply exits. If it's executing a subprocess
# that subprocess isn't signaled. (Typically that's not a problem for interactive shells
# because the entire Process Group gets sent the signal.)
#
# When running a script it's sometimes useful for the script to propagate a SIGTERM
# to the command that was running. We can do that by using the trap builtin to catch
# the signal. But it's a little tricky, per the bash manual:
#
#    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 a script executing a long-running command with a signal trap set won't
# notice the signal until later. There's a way around that though...
#
#    When bash is waiting for an asynchronous command via the wait builtin, the
#    reception of a signal for which a trap has been set will cause the wait
#    builtin to return immediately with an exit status greater than 128,
#    immediately after which the trap is executed.
#
# Usage:
#
#   interruptable [options] command [args]
#
# Options:
#   --killall - put the child into a process group (via setsid)
#               and send the SIGTERM to the process group
#   --debug   - print a message including pid of the child
#
# Usage examples:
#
#   interruptable sleep 3600
#
# If not interrupted, the exit status of the specified command is returned.
# If interrupted, the specified command is sent a SIGTERM and the current
# shell exits with a status of 143.

interruptable() {

    # handle options
    local setsid=""
    local debug=false
    while true; do
        case "${1:-}" in
            --killall)      setsid=setsid; shift ;;
            --debug)        debug=true; shift ;;
            --*)            echo "Invalid option: $1" 1>&2; exit 1;;
            *)              break;; # no more options
        esac
    done

    # start the specified command
    $setsid "$@" &
    local child_pid=$!

    # arrange to propagate a signal to the child process
    trap '
        exec 1>&2
        set +e
        trap "" SIGPIPE # ensure a possible sigpipe from the echo does not prevent the kill
        echo "${BASH_SOURCE[0]} caught SIGTERM while executing $* (pid $child_pid), sending SIGTERM to it"
        # (race) child may have exited in which case kill will report an error
        # if setsid is used then prefix the pid with a "-" to indicate that the signal
        # should be sent to the entire process group
        kill ${setsid:+-}$child_pid
        exit 143
    ' SIGTERM
    # ensure that the trap doesn't persist after we return
    trap 'trap - SIGTERM' RETURN

    $debug && echo "interruptable wait (child $child_pid, self $$) for: $*"

    # An error status from the child process will trigger an exception (via set -e)
    # here unless the caller is checking the return status
    wait $child_pid # last command, so status of waited for command is returned
}
like image 31
Tim Bunce Avatar answered Sep 27 '22 17:09

Tim Bunce