Discussion:
trap not invoked after resuming read from trap
(too old to reply)
Owen Stephens
2018-11-05 20:59:14 UTC
Permalink
Hi,

I'm trying to understand the behaviour of the following simple script
in response to SIGINT:

$ cat read_sigint.sh
#!/usr/bin/env bash
trap 'trap - INT; kill -INT $$' INT
while read -r line; do echo "Read <$line>"; done

I expected this script to (upon receiving SIGINT) reset the trap to
the default handler and immediately exit due to receiving SIGINT from
itself (as per https://www.cons.org/cracauer/sigint.html). However,
the process doesn't immediately exit: if I run this process
interactively and hit Ctrl-c I have to hit Enter, for the process to
exit. Why is this (presumably it's so the read call returns, but why
has it resumed)?

Running under strace, I can see:

11630 rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7fa5770d24b0},
{SIG_DFL, [], 0}, 8) = 0
11630 rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7fa5770d24b0},
{SIG_DFL, [], SA_RESTORER, 0x7fa5770d24b0}, 8) = 0
11630 rt_sigaction(SIGINT, {0x4624a0, [], SA_RESTORER,
0x7fa5770d24b0}, {SIG_DFL, [], SA_RESTORER, 0x7fa5770d24b0}, 8) = 0
11630 read(0, 0x70c1e0, 128) = ? ERESTARTSYS (To be
restarted if SA_RESTART is set)
11630 --- SIGINT {si_signo=SIGINT, si_code=SI_KERNEL} ---
11630 rt_sigreturn({mask=[]}) = -1 EINTR (Interrupted system call)
11630 rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7fa5770d24b0},
{0x4624a0, [], SA_RESTORER, 0x7fa5770d24b0}, 8) = 0
11630 rt_sigaction(SIGINT, {0x465920, [], SA_RESTORER,
0x7fa5770d24b0}, {SIG_DFL, [], SA_RESTORER, 0x7fa5770d24b0}, 8) = 0
11630 kill(11630, SIGINT) = 0
11630 --- SIGINT {si_signo=SIGINT, si_code=SI_USER, si_pid=11630,
si_uid=1000} ---
11630 rt_sigreturn({mask=[]}) = 0
11630 read(0, "\n", 128) = 1
11630 rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7fa5770d24b0},
{0x465920, [], SA_RESTORER, 0x7fa5770d24b0}, 8) = 0
11630 kill(11630, SIGINT) = 0
11630 --- SIGINT {si_signo=SIGINT, si_code=SI_USER, si_pid=11630,
si_uid=1000} ---
11630 +++ killed by SIGINT +++

Some questions:
1) Why is read call interrupted (I'm assuming that's what the "= ?"
means) the first time the signal is received, but not the second?
2) Why are there two sigaction calls after the first SIGINT (the
second appears to be changing _from_ the default handler)?
3) Why does rt_sigreturn return -1 (with EINTR) the first time, but 0
the second?
4) Why does the kill call (presumably from my handler) show up twice?

Any light that someone can shed on this would be most-gratefully received.

Thanks,
Owen.
Eduardo Bustamante
2018-11-06 03:33:41 UTC
Permalink
Post by Owen Stephens
Hi,
I'm trying to understand the behaviour of the following simple script
$ cat read_sigint.sh
#!/usr/bin/env bash
What version of Bash are you using?
Owen Stephens
2018-11-06 09:00:08 UTC
Permalink
Post by Eduardo Bustamante
What version of Bash are you using?
Apologies. This was on Linux using
GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)
or
GNU bash, version 4.4.0(1)-release (x86_64-unknown-linux-gnu)
but I've also confirmed the same behaviour on my macOS box using:
GNU bash, version 4.4.23(1)-release (x86_64-apple-darwin17.5.0)

Owen.
Chet Ramey
2018-11-06 21:19:05 UTC
Permalink
Post by Owen Stephens
Hi,
I'm trying to understand the behaviour of the following simple script
$ cat read_sigint.sh
#!/usr/bin/env bash
trap 'trap - INT; kill -INT $$' INT
while read -r line; do echo "Read <$line>"; done
I expected this script to (upon receiving SIGINT) reset the trap to
the default handler and immediately exit due to receiving SIGINT from
itself (as per https://www.cons.org/cracauer/sigint.html). However,
the process doesn't immediately exit: if I run this process
interactively and hit Ctrl-c I have to hit Enter, for the process to
exit. Why is this (presumably it's so the read call returns, but why
has it resumed)?
Here's what happens. The shell receives SIGINT and sets a flag noting it.
The SIGINT interrupts read(2), and the shell notices the -1/EINTR and runs
the SIGINT trap. That's not strictly the way things should happen, since
traps are run at command boundaries and the read builtin doesn't complete,
but it is what people expect.

The trap handler resets the signal disposition to SIG_DFL, which causes the
shell to set the SIGINT handler to an internal function that will allow the
shell to clean up after itself before terminating. The subsequent SIGINT
from the trap action sets a flag that tells the shell to call this function
at its next opportunity. We're still running in the trap handler, not the
read.

The trap handler returns, and resumes the read. There is nothing to
interrupt the read, and the shell won't check for signals and run handlers
until it returns. That explains the behavior you observed.

There are a couple of things to do, all involving checking for terminating
signals before resuming the read. I'll do one of them.

Chet
--
``The lyf so short, the craft so long to lerne.'' - Chaucer
``Ars longa, vita brevis'' - Hippocrates
Chet Ramey, UTech, CWRU ***@case.edu http://tiswww.cwru.edu/~chet/
Owen Stephens
2018-11-07 01:09:39 UTC
Permalink
Post by Chet Ramey
Here's what happens. The shell receives SIGINT and sets a flag noting it.
The SIGINT interrupts read(2), and the shell notices the -1/EINTR and runs
the SIGINT trap. That's not strictly the way things should happen, since
traps are run at command boundaries and the read builtin doesn't complete,
but it is what people expect.
When you say "That's not strictly the way things should happen" are you
referring to the manual:
https://www.gnu.org/software/bash/manual/bash.html#Signals
specifically, "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"? It would indeed be strange if SIGINT didn't interrupt a
pending read.

I note that the manual 6.11 Bash POSIX Mode section says "The read builtin may
be interrupted by a signal for which a trap has been set. If Bash receives a
trapped signal while executing read, the trap handler executes and read returns
an exit status greater than 128." and indeed, using --posix, my script performs
as I originally expected, namely exiting with 130 after a single Ctrl-C. What
is the reason for behaving differently without --posix in this regard?
Post by Chet Ramey
There are a couple of things to do, all involving checking for terminating
signals before resuming the read. I'll do one of them.
Will this mean that my example _will_ exit after a single Ctrl-c? What are the
couple of things to do, and what behaviour will they change?

Thanks,
Owen.
Post by Chet Ramey
Post by Owen Stephens
Hi,
I'm trying to understand the behaviour of the following simple script
$ cat read_sigint.sh
#!/usr/bin/env bash
trap 'trap - INT; kill -INT $$' INT
while read -r line; do echo "Read <$line>"; done
I expected this script to (upon receiving SIGINT) reset the trap to
the default handler and immediately exit due to receiving SIGINT from
itself (as per https://www.cons.org/cracauer/sigint.html). However,
the process doesn't immediately exit: if I run this process
interactively and hit Ctrl-c I have to hit Enter, for the process to
exit. Why is this (presumably it's so the read call returns, but why
has it resumed)?
Here's what happens. The shell receives SIGINT and sets a flag noting it.
The SIGINT interrupts read(2), and the shell notices the -1/EINTR and runs
the SIGINT trap. That's not strictly the way things should happen, since
traps are run at command boundaries and the read builtin doesn't complete,
but it is what people expect.
The trap handler resets the signal disposition to SIG_DFL, which causes the
shell to set the SIGINT handler to an internal function that will allow the
shell to clean up after itself before terminating. The subsequent SIGINT
from the trap action sets a flag that tells the shell to call this function
at its next opportunity. We're still running in the trap handler, not the
read.
The trap handler returns, and resumes the read. There is nothing to
interrupt the read, and the shell won't check for signals and run handlers
until it returns. That explains the behavior you observed.
There are a couple of things to do, all involving checking for terminating
signals before resuming the read. I'll do one of them.
Chet
--
``The lyf so short, the craft so long to lerne.'' - Chaucer
``Ars longa, vita brevis'' - Hippocrates
Chet Ramey
2018-11-07 01:55:06 UTC
Permalink
Post by Owen Stephens
Post by Chet Ramey
Here's what happens. The shell receives SIGINT and sets a flag noting it.
The SIGINT interrupts read(2), and the shell notices the -1/EINTR and runs
the SIGINT trap. That's not strictly the way things should happen, since
traps are run at command boundaries and the read builtin doesn't complete,
but it is what people expect.
When you say "That's not strictly the way things should happen" are you
https://www.gnu.org/software/bash/manual/bash.html#Signals
specifically, "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"? It would indeed be strange if SIGINT didn't interrupt a
pending read.
Maybe. People expect the read to be restarted if the signal is trapped and
the trap handler doesn't exit the shell, but they expect the trap handler
to be executed. This is analogous to signal handlers.
Post by Owen Stephens
I note that the manual 6.11 Bash POSIX Mode section says "The read builtin may
be interrupted by a signal for which a trap has been set. If Bash receives a
trapped signal while executing read, the trap handler executes and read returns
an exit status greater than 128." and indeed, using --posix, my script performs
as I originally expected, namely exiting with 130 after a single Ctrl-C. What
is the reason for behaving differently without --posix in this regard?
Posix mode never restarts a read on SIGINT, trap or no trap. That's rarely
what people want.
Post by Owen Stephens
Post by Chet Ramey
There are a couple of things to do, all involving checking for terminating
signals before resuming the read. I'll do one of them.
Will this mean that my example _will_ exit after a single Ctrl-c? What are the
couple of things to do, and what behaviour will they change?
Yes, checking for terminating signals before resuming the read means that
it will note the receipt of a terminating signal and exit before the read
resumes.
--
``The lyf so short, the craft so long to lerne.'' - Chaucer
``Ars longa, vita brevis'' - Hippocrates
Chet Ramey, UTech, CWRU ***@case.edu http://tiswww.cwru.edu/~chet/
Loading...