This is the mail archive of the libc-alpha@sourceware.org mailing list for the glibc project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: [RFC] Toward Shareable POSIX Signals


On Sun, Mar 11, 2018 at 2:56 PM, Daniel Colascione <dancol@dancol.org> wrote:
> On 03/11/2018 11:07 AM, Zack Weinberg wrote:
>
>> However, along with most of the other posters in
>> this thread, I don't like the proposed solution -- and not just
>> because I don't like signals (although, indeed, I do not like signals)
>> but because I think the basic mechanism you suggest, chained handlers,
>> is inherently unreliable and will cause more problems than it solves.
>
> Why? You're ignoring the present reality that people _already_ use chained
> signal handlers. They're not going to stop. When libc maintainers reject
> widespread use cases as illegitimate, all they're doing is forestalling any
> sort of improvement.

The C library has to be extremely conservative about adding new APIs,
because we are, to first order, stuck with anything we add _forever_.
In particular, we will _not_ accept "people are doing X now" as a
valid argument for codifying X as part of the C library, especially
not when we can come up with an alternative with fewer problems.  Yes,
the alternative might mean that the people doing X now have to do
something else instead.  But switching from libsigchain (for instance)
to libsigchain-codified-in-the-C-library is _also_ a code change for
the people doing X now.  It might be a _smaller_ code change than what
they would have to do to adopt the alternative, but we don't care
about that.  We care, instead, about whether the alternative makes it
easier in the long run to write reliable code.

> A C runtime needs to consider realistic proposals to address real
> problems of real software --- not hold out instead for some
> idealized alternative family of APIs that will never materialize.

This is unfair.  Some of the alternatives we have suggested already
exist, and others are proposals at least as concrete as yours is.

> I propose an API that ensures
> that the right thing happens as long as everyone follows the rules, and
> that's far better than the lawless waste that exists today.

Speaking only for myself here, "the right thing happens as long as
everyone follows the rules" is _not good enough_ for an API codified
as part of the C library.  It needs to be "the right thing happens for
everyone who follows the rules, _even if_ other code in the same
process is breaking the rules in ways that we reasonably anticipate
will happen."

For instance: Chained handlers for SIGCHLD are not good enough,
because we reasonably anticipate that some handlers will -- not out of
malice, just out of lack of foresight -- swallow notifications that
were properly intended for other handlers.  pdfork, on the other hand,
_is_ good enough, because the holder of a process handle is the only
code to receive a notification for that process, regardless of what
other code waiting for unrelated processes might be doing.

>> Adding signal_register()
>> to a universe that already has signal() also introduces a nasty
>> compatibility problem: suppose library A uses the new API to register
>> a handler for SIGINT (for example), but library B, or the application,
>> calls signal(SIGINT, SIG_IGN), or sighold(SIGINT): what do you do?
>
> My proposal specifically addresses this subject. Signal mask behavior is
> unchanged. Legacy signal hander installation behavior is unchanged. signal
> and sigaction affect the legacy signal handler slot, even when called with
> SIG_IGN.

My apologies; I missed the paragraph of your proposal that discusses
this.

I will drop this objection, since I'm not interested in hammering out
the details of an API that I think is a bad idea regardless of its
details.  There could be problems with the legacy interaction you
describe, but that could be said of _any_ legacy interaction.

>> I also think you haven't gone deep enough into the root cause of the
>> problem you're trying to solve.  You set out to make it possible to
>> have more than one signal handler per process for each signal, but
>> _why_ is that an undesirable limitation?  In most cases, it's because
>> _signals are too coarse_.  When you get a SIGCHLD or a SIGIO or a
>> SIGSEGV, you don't know which of many possible child processes / file
>> descriptors / memory addresses is relevant.
>
> This claim is technically incorrect. The siginfo structure passed to
> the sigaction handler (and, in my proposal, to registered handlers)
> provides the necessary specificity.

You misunderstand me.  The problem is not that the handler(s) don't
have enough information to figure out whether the specific event is
relevant to them; the problem is that the specific event is not
delivered directly to the specific handler that cares about it, and
nobody else.

This is why I'm sort-of OK with chained handlers for events that
really are broadcast in nature, such as SIGPWR and SIGTERM.  However,
having thought about it some more, I don't want it to be expressed in
the API as chaining, because chaining implies an order, and that's a
problem in itself.  I want it to be expressed as _independent_
handlers, and by "handlers" I mean "file descriptors" to the maximum
extent possible, e.g. open("/dev/power_failure_notify", O_RDONLY)
gives you a file descriptor that will become readable at the same time
SIGPWR is fired.

> You still need some kind of catch-all mechanism in case no specific
> handler is applicable.

NO WE DON'T.

Catch-alls are bad, OK.  They suffer intrinsically from the same
problem you are trying to solve -- "what if two pieces of code want
to be the catch-all?"

I don't even like your SA_LOW_PRIORITY, because, again, what if two
pieces of code want to be the last to receive the notification?  You
can't honor both requests, so you mustn't even offer the possibility
in the first place.

Instead, what I ideally want is for us to decompose all coarse events
until there is one and only one handler for each specific event, and
then figure out some way to map all of the specific events into file
descriptor notifications that can be fielded via select() or epoll().
If an event is legitimately a broadcast event, like SIGPWR, then we
make it possible for there to be multiple _independent_ -- not
chained; no ordering -- listeners.

> I would prefer process handle file descriptors. Linux upstream has
> specifically rejected process handle file descriptors on several
> occasions.

This is news to me; could you please dig up pointers to specific
objections by people with veto authority?  I thought it had just been
neglected.

...
> ALRM, VTALRM, PROF --- as a completely separate matter, additional
> arbitration for coordinating timer deadlines would be useful.

Yeah.  I haven't had to do anything complicated with timers in C
myself, so I'm not sure what would be ideal as a C API, but
timer_create seems more like the Right Thing than setitimer does.

There is an additional headache in that SIGALRM or SIGVTALRM + a
non-SA_RESTART handler are still sometimes the only way to impose a
timeout on a blocking system call.  Abstractly, all such system calls
need to grow extended versions that take timeouts, but that's a large
and mostly independent project, and there's still an issue with
blocking system calls happening inside a library you don't control.

> I understand your motivation for ensuring all such handlers are
> called for these broadcast signals. I think API uniformity matters
> more than ensuring that all handlers are called, especially since
> I'm certain that we need multi-handler support for synchronous
> signals as well as asynchronous ones, and synchronous signals need
> to be cancelable.

To me it's exactly the other way around: if we can't ensure that all
handlers are called, then the design problem has not yet been solved;
if the API needs to be non-uniform in order to fit the design
requirements, then so be it.

I don't understand what you mean by "synchronous signals need to be
cancelable."

>> ILL, ABRT, FPE, SEGV, BUS, SYS, TRAP, IOT, EMT, STKFLT -- Synchronous
>> signals arising from processor faults deserve a specialized mechanism
>> all their own.  The notion I currently like, at the kernel level, is
>> just-in-time instantiation of a ptrace monitor
>
> That approach doesn't solve the arbitration issue and would make
> performance significantly worse than present. Not every instance of
> one of these synchronous signals is a crash. Spawning a process to
> handle them is far too expensive (and unreliable!) for something
> like a Java runtime's null pointer checks.

As discussed elsethread, I currently agree with Rich Felker that
Java's null pointer checks are better implemented with explicit tests
emitted by the JIT; not by taking a fault and then fixing up
afterward.  Same for persistent object stores and incremental GC; use
compiler-generated write barriers, not page faults.

I _could_ be convinced otherwise, but what it would take is a
head-to-head performance comparison between a JIT that relies on page
faults and a JIT that relies on explicit tests and implements
state-of-the-art elimination of unnecessary tests, all else held
equal, on a real application.

In the absence of that comparison, for synchronous faults I'm really
only interested in making crash recovery more reliable.  That needs to
happen from outside the corrupted address space, and it's OK if it
takes a slow path.

You're right that "instantiate a ptrace monitor just-in-time" still
has an arbitration problem _at the kernel level_.  I imagine the
arbitration - via exception tables or whatever - happening _inside_
the monitor.  The C library might provide a "shell" ptrace monitor
that could be extended with application-specific modules.

Note also that we wouldn't spawn a fresh instance of the monitor for
every fault.  Once it's running, it would stay running and remain
attached to the process.  If the process was already being ptraced by
a full debugger, the monitor would not be involved.  (This gets tricky
when you want to debug the monitor, but not worse than when you want
to debug a debugger.)

> While I would approve of adding SEH, I don't think it's a realistic option
> at the moment.

Agreed that it is too much of a coordination challenge to add SEH; also,
since it relies on dynamic information on the stack, it's not safe in
the face of adversarial memory corruption.

> An except_table approach might solve part of the problem, but you'd need to
> provide a dynamic registration facility for the sake of JIT systems.

Yeah.  But you don't want the JIT-generated code to be able to access
the registrar.  Here, perhaps the right thing is for the JIT to invoke
its sandboxed untrusted-code subprocess already under its own ptrace
monitoring.

> Also, think of how a table lookup would work at a mechanical level. The
> kernel would still push a SIGSEGV frame onto some stack and transfer control
> flow to the handler.

No. The kernel would wake up the monitor process sleeping in ptrace
(or perhaps select() on the process handle) or instantiate one if it
doesn't already exist.

"For code using the new API, we NEVER need to interrupt normal control
flow and push a signal frame" is also on my list of constraints that
must be satisfied for the design to be complete and acceptable.

zw


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]