bug-bash
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: SIGINT handling during async functions


From: Tycho Kirchner
Subject: Re: SIGINT handling during async functions
Date: Sat, 21 Jan 2023 13:55:27 +0100



Am 16.01.23 um 18:26 schrieb Chet Ramey:

The fix is to add enough state machinery to detect this situation and
behave in a way that can satisfy both the standard and the later
interpretation, while being careful not to undo this work later. This is
obviously not how bash worked in the past.

Thanks for the explanation. While editing the state machinery I would like to 
suggest to add a new shopt, let's call it keepsigint, which a user may set to 
preserve the SIGINT trap set in the parent shell for all asynchronous commands.
While the POSIX behavior to ignore SIGINT for background processes if job 
control is disabled makes totally sense for interactive shells, for scripts to 
me it often appears not constructive. Please consider a script launching 
several commands in background and waiting for their completion:

cmd1 &
cmd2 &
wait

If the user having launched this script from the interactive terminal aborts it 
by hitting Ctrl+C, by default, the shell sends SIGINT to the process group 
(pgid) of the script. However, while cmd1 and cmd2 get their signal, they 
usually (if they don't override it) ignore it due to above POSIX requirement. 
In my experience, what the user usually wants in such a case is to abort cmd1, 
cmd2 as well as the script having launched them.

Of course there are ways to kill cmd1 and cmd2 (and possible grandchildren) 
explicitly, e.g. by sending an additional TERM signal to the process group, 
e.g. (at the top of the script)
trap 'trap "" TERM; env kill -TERM -- -$$; exit 130' INT
However, this is usually only safe, when we are the process group leader 
(otherwise we might kill our parent as well!), so we need an additional
[ $$ -eq $(($(ps -o pgid= -p "$$"))) ] || exec setsid --wait "${BASH_SOURCE[0]}" 
"$@"
at the top of our script to create a new process group if necessary. Further, applications may 
react differently on TERM and INT, making the "signal conversion" undesirable in the 
general case. Finally, asynchronously running bash scripts may print "Terminated" 
messages which are usually not of interest for a user having aborted the command manually.

Another option would be to enable jobcontrol within the script and kill the 
commands that way, e.g.
set -m; cmd1 & jobs=($(jobs -p)); env kill -INT -- "${jobs[@]/#/-}"
However, jobcontrol disables the possibility to suspend the "whole script" with 
Ctrl+Z and bears the risk to eventually loose some jobs while without jobcontrol, killing 
the single pgid kills all leftovers with high certainty.

A third way is to launch cmd1 and cmd2 with
env --default-signal=SIGINT,SIGQUIT cmd1 &
so they do not ignore SIGINT. That's fine, but has to be repeated for every 
command. Further, process substitutions and functions cannot be called that way.

A fourth way is to explicitly set the INT trap within an async command group 
before executing the command, like
{ trap 'true' INT; exec cmd1; } &
Personally I regularly use below __async__ function for async commands, command groups, 
process substitutions and functions and I'm fine with that. But all these four options 
require some typing (and reading) overhead and just don't feel "sane". I think, 
bash would really benefit from a 'keepsigint' option. What are your thoughts about that?

Thanks and kind regards
Tycho

_____________________________________________________________________________


__async__ bash -c 'echo first; trap -p; sleep 6'; wait
{ __async__; exec bash -c 'echo second; trap -p; sleep 6'; } & wait
foofunc(){ bash -c 'echo foofunc; trap -p; sleep 6'; };  __async__ foofunc; wait
cat <(__async__; exec bash -c 'echo psub; trap -p; sleep 6';)


__async__(){
    local int_trap
    int_trap="$(trap -p INT)"
    [ -z "$int_trap" ] && int_trap="trap -- 'exit 130' SIGINT"
    if [ "${#@}" -eq 0 ]; then
        # Already running async, just set parent's INT handler.
        eval "$int_trap";
        return
    fi
    if [[ $(type -t "$1") == file ]]; then
        # exec into external file so pid is same as if called like 'cmd &'
        { eval "$int_trap"; exec "$@"; } &
    else
        { eval "$int_trap"; "$@"; } &
    fi
}




reply via email to

[Prev in Thread] Current Thread [Next in Thread]