Re: Execution continuing after SIGINT received

2017-08-05 Thread Bob Proulx
Kevin Brodsky wrote:
> The thing is, "the next level of the program" really is another program,
> i.e. the one that invoked it, and you are communicating via the exit
> status, so it's certainly not as explicit as re-throwing an exception in
> C++, for instance. But sure, once you are aware of this mechanism, it's
> not difficult to understand the rationale.

One shouldn't confuse exception handling with signal handling.  They
are really quite different things. :-)

> Actually, IMHO, what makes it look very counter-intuitive is the fact
> that you need to first reset the signal handler for SIGINT. Of course
> this is necessary to avoid invoking the handler recursively, but it
> feels very much like a workaround.

You probably remember that signal(2) is unsafe because it does exactly
that and resets the signal handler to the default.  This creates a
race condition.  There is a window of time in the signal handler when
a second signal can catch the program with the default state set
before it can reset the signal handler.  That is the main reason why
sigaction(2) should be used instead.

> WIFSIGNALED is true if "the child process [...] terminated due to
> the receipt of a signal that was not caught". That's not really what
> we want to know here; we want to know if the child process received
> a signal that caused it to terminate. Whether it handled SIGINT to
> clean up resources is irrelevant; what's relevant is that it
> eventually terminated as a consequence of SIGINT. Ideally, exit()ing
> from a signal handler should set a bit in the exit status expressing
> exactly this.

Well...  Maybe.  But how are you going to know if the program
"eventually terminated as a consequence of SIGINT" or not?  And of
course then you would need a way to provide a program to do the
opposite when required.

But it has been 40 years already with the current way things are done
and it is a little late to change things now.

> I'll stop digressing, POSIX is what it is and we won't change it anyway
> ;-) For now, there's no other way to communicate with the shell, so
> that's fair enough.

I don't see how POSIX is even involved in this.  Other than stopping
the proliferation of differences on different systems.  Before POSIX
came along every system did this slightly differently.  It was awful.
POSIX.1 simply picked BSD as the behavior to standardize upon.  And
then we could stop putting #ifdef's in our code for every different
system quirk.

What you are seeing here is just the basic behavior of the old Unix
and BSD kernels and now every kernel that has followed since.  Since
that is the common behavior among all systems the standards bodies
will mostly say, freeze on that behavior, do it the same way, don't do
it differently.  Surely that is a good thing, right?

The subtleties of https://www.cons.org/cracauer/sigint.html about how
programs should behave were learned not all at once but over the
course of time.

Bob



Re: Execution continuing after SIGINT received

2017-08-05 Thread Kevin Brodsky
On 05/08/2017 20:35, Bob Proulx wrote:
>
> Really?  It seems intuitive to me that at any trap handling level one
> should handle what needs to be handled and then raise the signal
> higher to the next level of the program.  Software is all about layers
> and abstraction.  Sending the signal to one self to raise the signal
> again feels good to me.

The thing is, "the next level of the program" really is another program,
i.e. the one that invoked it, and you are communicating via the exit
status, so it's certainly not as explicit as re-throwing an exception in
C++, for instance. But sure, once you are aware of this mechanism, it's
not difficult to understand the rationale.

Actually, IMHO, what makes it look very counter-intuitive is the fact
that you need to first reset the signal handler for SIGINT. Of course
this is necessary to avoid invoking the handler recursively, but it
feels very much like a workaround. WIFSIGNALED is true if "the child
process [...] terminated due to the receipt of a signal that was not
caught". That's not really what we want to know here; we want to know if
the child process received a signal that caused it to terminate. Whether
it handled SIGINT to clean up resources is irrelevant; what's relevant
is that it eventually terminated as a consequence of SIGINT. Ideally,
exit()ing from a signal handler should set a bit in the exit status
expressing exactly this.

I'll stop digressing, POSIX is what it is and we won't change it anyway
;-) For now, there's no other way to communicate with the shell, so
that's fair enough.

> POSIX even added a raise(3) call to make this easier.  (Although I
> still do things the old way.)
>
>   man 3 raise

That's a good point, it's arguably more self-explanatory than
kill(getpid(), ...).

Kevin



Re: Execution continuing after SIGINT received

2017-08-05 Thread Kevin Brodsky
On 05/08/2017 03:22, Bob Proulx wrote:
> Kevin Brodsky wrote:
>> $ bash -c '(trap "echo INT; exit 1" INT; sleep 60s); echo after'
>> ^CINT
>> after
>> $
> This is a good example of a bad example case.  You shouldn't "exit 1"
> or you will replace the information that the process is exiting due to
> a signal with an error code.  The trap handler should kill itself
> instead.  Use this test case instead.
>
>> $ bash -c '(trap "echo INT; trap - INT; kill -s INT $$" INT; sleep 60); echo 
>> after'
> Of course that doesn't change the end result here.  But at least the
> program exit WIFSIGNALED information is now correct.
>
> Signal handlers should always raise the signal on themselves after
> handling whatever they need to handle first.
>
> In any case I can't recreate your problem when using real processes in
> separate shells not all on one line.
>
> Bob

You're right Bob, I didn't think about the difference between exit()ing
and being kill()ed, notably in terms of WIFSIGNALED. Chet's reply
explains well the rationale, and it's now clear that the problem is on
Python's side (reproduced in my dummy example!).

Thanks,
Kevin



Re: Execution continuing after SIGINT received

2017-08-05 Thread Kevin Brodsky
On 05/08/2017 15:53, Chet Ramey wrote:
> On 8/4/17 7:52 PM, Kevin Brodsky wrote:
>
>> When Bash receives SIGINT while executing a command, it normally waits
>> for the command to complete, and then aborts execution. However, it
>> looks like somehow, this is not the case if the command handles SIGINT,
>> and execution continues after the command completes. For instance:
> The question of what happens when bash receives SIGINT while waiting for a
> foreground job to complete has come up many times in the past.
>
> See this for a good discussion of the issue:
>
> https://www.cons.org/cracauer/sigint.html
>
> The basic idea is that the user intends a keyboard-generated SIGINT to go
> to the foreground process; that process gets to decide how to handle it;
> and bash reacts accordingly.  If the process dies to due SIGINT, bash acts
> as if it received the SIGINT; if it does not, bash assumes the process
> handled it and effectively ignores it.
>
> Consider a process (emacs is the usual example) that uses SIGINT for its
> own purposes as a normal part of operation. If you run that program in a
> script, you don't want the shell aborting the script unexpectedly as a
> result.
>
> Chet

Thank you for your answer Chet. The article you linked is extremely
informative, I wasn't aware of the various possible strategies shells
can use to handle SIGINT!

So in summary, there is no bug on the Bash side, it simply implements
the WCE strategy, which does what the user wants... as long as the
invoked commands are well-behaved! And I just got very unlucky, as I did
stumble upon a misbehaved program, and all the other shells I tried
don't implement WCE... Some more experimentations showed that all three
(dash, mksh and zsh) implement WUE (I would have expected zsh to follow
Bash?).

Knowing this, it's now clear that the bug is on the Python side, as it
uses exit(1) when receiving SIGINT instead of doing the signal()/kill()
dance mentioned in the article. Other interpreters like Perl or Ruby
behave correctly, which confirms that there's a problem with Python. To
be fair, killing oneself when receiving SIGINT is quite
counter-intuitive, POSIX is not helping here.

Thanks,
Kevin



Re: Execution continuing after SIGINT received

2017-08-05 Thread Chet Ramey
On 8/4/17 7:52 PM, Kevin Brodsky wrote:

> When Bash receives SIGINT while executing a command, it normally waits
> for the command to complete, and then aborts execution. However, it
> looks like somehow, this is not the case if the command handles SIGINT,
> and execution continues after the command completes. For instance:

The question of what happens when bash receives SIGINT while waiting for a
foreground job to complete has come up many times in the past.

See this for a good discussion of the issue:

https://www.cons.org/cracauer/sigint.html

The basic idea is that the user intends a keyboard-generated SIGINT to go
to the foreground process; that process gets to decide how to handle it;
and bash reacts accordingly.  If the process dies to due SIGINT, bash acts
as if it received the SIGINT; if it does not, bash assumes the process
handled it and effectively ignores it.

Consider a process (emacs is the usual example) that uses SIGINT for its
own purposes as a normal part of operation. If you run that program in a
script, you don't want the shell aborting the script unexpectedly as a
result.

Chet
-- 
``The lyf so short, the craft so long to lerne.'' - Chaucer
 ``Ars longa, vita brevis'' - Hippocrates
Chet Ramey, UTech, CWRUc...@case.eduhttp://cnswww.cns.cwru.edu/~chet/



Re: Execution continuing after SIGINT received

2017-08-04 Thread Bob Proulx
Kevin Brodsky wrote:
> $ bash -c '(trap "echo INT; exit 1" INT; sleep 60s); echo after'
> ^CINT
> after
> $

This is a good example of a bad example case.  You shouldn't "exit 1"
or you will replace the information that the process is exiting due to
a signal with an error code.  The trap handler should kill itself
instead.  Use this test case instead.

> $ bash -c '(trap "echo INT; trap - INT; kill -s INT $$" INT; sleep 60); echo 
> after'

Of course that doesn't change the end result here.  But at least the
program exit WIFSIGNALED information is now correct.

Signal handlers should always raise the signal on themselves after
handling whatever they need to handle first.

In any case I can't recreate your problem when using real processes in
separate shells not all on one line.

Bob