nh2
nh2

Reputation: 25645

Start programs in the background and kill them as soon as one of them dies with an exit code

On Linux, I would like to spawn off some servers as background services.

As soon as one of them terminates with an exit code != 0, they shall all to be SIGTERMed, and after 3 seconds grace period be KILLed if they don't want to go.

The same shall happen when Ctrl-C is pressed.

I want a rough equivalent of

set -e
server1 &
server2 &
server3 &
wait (+ kill on error)

(Unfortunately that code above does not have all the mentioned properties.)

It shall not be possible for children to steal/escape the Ctrl-C signals.

I accept solutions in Bash and Python (using subprocess). For Python they may only use standard library functions (nothing I have to download modules with pip first). If you have a cool way to do this in another language, please do not hesitate to show it.

Bonus points if it also works on Mac OS.

Upvotes: 2

Views: 258

Answers (3)

William Pursell
William Pursell

Reputation: 212168

#!/bin/sh
# Avoid <() process substitutions for portability's sake
set -bm
trap clean SIGCHLD
trap 'kill 0' SIGINT
trap 'sleep 3; kill -9 0' SIGTERM

clean() {
        while read spec pid extra; do
                wait $pid || kill 0
        done << EOF
                $( jobs -l | grep 'Exit\|Done' )
EOF
}

server1 &
server2 &
server3 &
wait

Upvotes: 0

torek
torek

Reputation: 487725

It is technically possible to do this directly in bash, but I wouldn't, because it requires a lot of polling and will be inefficient and error-prone (basically you have to kill -0 $pid to test if the process is still around). Here's a bit of bash code (from a script I have lying around) that shows how to record a backgrounded process pid:

start_tail() {
    if [ x$TAIL_PID != x ]; then
        echo "internal error: already running tail (pid $TAIL_PID)"
    fi
    tail -0f $1 &
    TAIL_PID=$!
    trap "kill $TAIL_PID" 0 1 2 3 15
}

stop_tail() {
    if [ x$TAIL_PID != x ]; then
        kill $TAIL_PID; trap 0 1 2 3 15
        TAIL_PID=""
    fi
}

and in this particular case you could probably make it work better (than the above, which is deliberately limited) using a "coprocess" and a fair bit of hacking.

It will be easier in Python, but don't use subprocess; use direct OS calls (as @geekosaur described above). @jldupont's recommendation for http://supervisord.org/ looks like a good one (especially since it is written in Python).

Upvotes: 0

geekosaur
geekosaur

Reputation: 61369

Rather than write the solution for you, I will suggest where to look: setpgrp, waitpid, kill. Note that "It shall not be possible for children to steal/escape the Ctrl-C signals." is neither possible (the only signals that cannot be evaded are SIGSTOP and SIGKILL) nor meaningful (you aren't delivering SIGINT to them).

You can't do this properly in bash because wait waits for all specified (or, if none are specified, simply all) processes; you need "any" semantics, not "all".

Upvotes: 1

Related Questions