[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: Interrupting scripts with asynchronous subprocesses
From: |
Chet Ramey |
Subject: |
Re: Interrupting scripts with asynchronous subprocesses |
Date: |
Wed, 24 Jul 2024 10:36:41 -0400 |
User-agent: |
Mozilla Thunderbird |
On 7/14/24 12:58 AM, Yuri Kanivetsky wrote:
There was a discussion about this after bash-5.2 came out:
https://lists.gnu.org/archive/html/bug-bash/2023-01/msg00044.html
that resulted in some behavior changes.
Okay, I've read the discussion and experimented with the examples. Let
me try to explain what's going on there with my own words. But let me
start with a couple of cases of my own first:
I'm not going to repeat what I wrote in
https://lists.gnu.org/archive/html/bug-bash/2023-01/msg00050.html,
which explains the issue pretty completely.
You've described what happens with bash-5.2. As I said above, there were
some behavior changes that came in in January, 2023 in devel branch commit
http://git.savannah.gnu.org/cgit/bash.git/commit/?h=devel&id=a5d2617c7a7e602ace1f4149987cdfd075c4e762
I'll put in some comments below where the behavior differs in bash-5.3-alpha
and the devel branch.
e.sh:
{ sleep infinity
echo after sleep; } &
wait
$ ps -eHo pid,ignored,args --signames
...
428568 QUIT ./bash e.sh
428569 INT,QUIT ./bash e.sh
428570 - sleep infinity
$ bash e.sh
^Cafter sleep
Now { ... } gets SIG_IGN as an async process and only sleep (of those
2) receives SIGINT. sleep dies, but now the condition mentioned above:
In current bash, the sleep survives because it ignores SIGINT. `infinity'
is therefore a poor choice. ;-)
--
f.sh:
t='echo INT ${FUNCNAME[0]-main} >&2'
trap "$t" INT
foofunc(){ sleep 3; echo foo >&2; }
foofunc &
sleep 5
$ ps -eHo pid,ignored,args --signames
...
428573 QUIT ./bash f.sh
428574 QUIT ./bash f.sh
428575 - sleep 3
428576 - sleep 5
$ bash f.sh
^CINT main
Async builtins and functions set SIGINT to SIG_IGN (as async processes
usually do):
https://git.savannah.gnu.org/cgit/bash.git/tree/execute_cmd.c?h=bash-5.2#n5554
but with builtins and functions it's soon changed to termsig_sighandler():
https://git.savannah.gnu.org/cgit/bash.git/tree/execute_cmd.c?h=bash-5.2#n5365
The outer bash has a SIGINT handler which prints "INT main." The sleep
processes has SIG_DFL so they die. foofunc() sees that its child has
died from SIGINT and dies itself.
In current bash, the `sleep 3' survives, foofunc survives, `echo foo' runs.
This is the exact script from the original bug report in January, 2023.
--
g.sh:
t='echo INT ${FUNCNAME[0]-main} >&2'
trap "$t" INT
foofunc(){ trap "$t" INT; sleep 3; echo foo >&2; }
foofunc &
sleep 5
$ ps -eHo pid,ignored,args --signames
...
428579 QUIT ./bash g.sh
428580 QUIT ./bash g.sh
428582 - sleep 3
428581 - sleep 5
$ bash g.sh
^CINT foofunc
foo
INT main
Now both bash processes have SIGINT handler, and they both print "INT
...." The reason foo is printed is because foofunc()'s SIGINT handler
doesn't terminate the process.
In current bash, the `sleep 3' inherits SIG_DFL because foofunc changed the
signal disposition. This is also from
https://lists.gnu.org/archive/html/bug-bash/2023-01/msg00044.html so the
previous discussion applies.
i.sh:
t='echo INT ${FUNCNAME[0]-main} >&2'
trap "$t" INT
foofunc(){ trap -p; sleep 3; echo foo >&2; }
{ foofunc; } &
sleep 5
$ ps -eHo pid,ignored,args --signames
...
428591 QUIT ./bash i.sh
428592 INT,QUIT ./bash i.sh
428594 INT sleep 3
428593 - sleep 5
$ bash i.sh
trap -- '' SIGINT
^CINT main
foo
The tricky part here is that now { foofunc; } and its child both have
SIG_IGN. As such they both ignore SIGINT and finish as if nothing
happened (foo is printed after a delay).
Now why do they both have SIG_IGN? Normally when { foofunc; } invokes
sleep the latter inherits the original handlers (the handlers that {
foofunc; } inherited from its parent). In both cases (h.sh, i.sh) that
is SIG_DFL. But `trap -p` changes the original handler:
https://git.savannah.gnu.org/cgit/bash.git/tree/builtins/trap.def?h=bash-5.2#n134
https://git.savannah.gnu.org/cgit/bash.git/tree/sig.c?h=bash-5.2#n272
https://git.savannah.gnu.org/cgit/bash.git/tree/trap.c?h=bash-5.2#n1537
Current bash prints
trap -- 'echo INT ${FUNCNAME[0]-main} >&2' SIGINT
because the child inherits the trap string (children inherit the trap
strings so things like $(trap) work) but the true disposition is SIG_IGN.
This is also from
https://lists.gnu.org/archive/html/bug-bash/2023-01/msg00044.html
and kre's explanation in
https://lists.gnu.org/archive/html/bug-bash/2023-01/msg00046.html
is correct.
j.sh:
./bash -c 'echo first; trap -p' & wait
{ ./bash -c 'echo second; trap -p'; } & wait
{ trap -p >/dev/null; ./bash -c 'echo third; trap -p'; } & wait
$ bash j.sh
first
trap -- '' SIGINT
trap -- '' SIGQUIT
second
third
trap -- '' SIGINT
This was changed; current bash prints the same thing for all three cases.
* Async processes generally get SIG_IGN:
"If job control is disabled (see the description of set -m) when the
shell executes an asynchronous list, the commands in the list shall
inherit from the shell a signal action of ignored (SIG_IGN) for the
SIGINT and SIGQUIT signals."
https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_11
E.g. { ... } in `bash -c '{ ... } & wait'`, `bash a.sh` in `bash -c
'bash a.sh & wait'`.
But not async functions or builtins. E.g. f in `bash -c 'f() { ... };
f & wait'`.
This was the point of
https://lists.gnu.org/archive/html/bug-bash/2023-01/msg00044.html
and was changed.
* SIG_IGN is inherited by children of a "new" shell, such as `bash
a.sh` in `bash -c 'bash a.sh & wait'` (but not { ... } in `bash -c '{
... } & wait'`).
The behavior of group commands is one of the things that changed.
Also `trap -p` makes SIG_IGN inherited. E.g. `sleep infinity` in `bash
-c '{ trap -p; sleep infinity; } & wait'` inherits SIG_IGN.
Not exactly; https://lists.gnu.org/archive/html/bug-bash/2023-01/msg00050.html
explains this.
Chet
--
``The lyf so short, the craft so long to lerne.'' - Chaucer
``Ars longa, vita brevis'' - Hippocrates
Chet Ramey, UTech, CWRU chet@case.edu http://tiswww.cwru.edu/~chet/