Why is a signal watcher active after default loop destruction and re-creation?
Jan-Philip Gehrcke
jgehrcke at googlemail.com
Tue Nov 26 18:59:41 CET 2013
Marc,
thanks for this clarification and recommendations. Unfortunately, we are
not simply exec()ing in the child which is why I need to carefully
analyze the boundary conditions affecting the child. As a follow-up, I
have two more questions:
1) Is there a competition between signalfd and a normal signal handler
or can we reliably deactivate the former by installing the latter?
Explanation: I understand that by default, libev uses signalfd if
supported by the system. I am trying to understand how the system
behaves when libev has applied the signalfd mechanism to a certain
signal type (e.g. SIGTERM) and I at some point after that call
signal(SIGTERM, SIG_DFL) or install a custom signal handler via
signal(SIGTERM, handler).
You were agreeing that using signal(SIGTERM, SIG_DFL) is enough to
restore the default action / deactivate a signalfd-based libev signal
watcher. Why are you sure about this? I was trying to find answers to
these two questions, but failed:
- Does signal(SIGTERM, SIG_DFL) actually modify the signal mask and
unblock SIGTERM?
- Does a signal handler (or the default action) *always* take precedence
over a signalfd mechanism?
- Once handled in a signal handler -- is there still a chance left that
a signal triggers the signalfd mechanism?
The man pages are in my opinion too vague about these things -- what I
found was
> Normally, the set of signals to be received via the
> file descriptor should be blocked using sigprocmask(2), to prevent
> the signals being handled according to their default dispositions.
(http://man7.org/linux/man-pages/man2/signalfd.2.html)
What makes it more difficult for me to understand is that libev probably
performs some signal blocking around using signalfd which adds further
complexity.
So, is it safe to just install a new signal handler to reliably
deactivate signalfd? I need to be sure about this, since Python 2.x has
no interface to directly modify the signal mask (from the 2.x docs:
"There is no way to “block” signals temporarily from critical sections
(since this is not supported by all Unix flavors).")
2) You wrote that "when python has created a thread before, destroying
the default loop results in undefined behaviour on POSIX systems". Could
you give me some key words why this is the case?
Big thanks,
Jan-Philip
On 11/22/2013 06:00 PM, Marc Lehmann wrote:
> On Fri, Nov 22, 2013 at 05:24:24PM +0100, Jan-Philip Gehrcke <jgehrcke at googlemail.com> wrote:
>> I am working on gevent/gipc and need to figure out why a libev signal
>> watcher is still 'active' after destroying its originally assigned
>> event loop. In the example code at the bottom of this mail, I
>
> The simple answer is: you never stopped it. Relying on a watcher to do
> something defined after its loop was destroyed is outside the scope of
> guaranteed libev behaviour :)
>
>> - in the child, the signal watcher object is magically connected to
>> the new loop
>>
>> The latter surprises me.
>
> Well, the signal watcher is not connected to the new loop. What you see is
> undefined behaviour, which looks as if it were connected, but it isn't.
>
>> I went through the libev docs again and could not really find an
>> explanation.
>
> There is no explanation in the docs because this is not defined, nor
> documented, behaviour.
>
>> What I indeed want is to "orphan" everything in the child related to
>> the default loop created in the parent.
>
> That's what is happening - orphaning isn't the same as stopping a watcher.
>
>> However, it looks like signal watchers don't need to be re-registered,
>> at least not for my test system.
>
> Looks can be deceiving, even for your test ystem, signal watchers need to
> be reregistered. Note that you probably also need to block signals until
> you did so, to avoid races.
>
>> I want to understand: why is that signal watcher still active?
>
> There is no answer to that question, because the watcher is _not_ active
> anymore.
>
>> And yes, an obvious solution would be `signal(SIGTERM, SIG_DFL);` in
>> the child -- but what would the general approach be to make sure that
>> previously installed watchers are not active anymore when their
>> corresponding loop has been destroyed?
>
> If overriding the signal handler isn't generic enough, the only other way
> is to stop the signal watchers.
>
> A different question is why you think you need to destroy the loop
> - if you are simply going to exec() something, it would merely be
> counterproductive, and there is little else you can do after fork on
> modern systems (e.g. when python has created a thread before, destroying
> the default loop results in undefined behaviour on POSIX systems. And when
> not, you still have to take care of file descriptors that are open in your
> child that can confuse your parent and lead to errors. Controlling all
> this is impossible in general, and orphaned watchers are going to be the
> leats of your problems :).
>
More information about the libev
mailing list