This is the mail archive of the
libc-alpha@sourceware.org
mailing list for the glibc project.
Re: [RFC] Toward Shareable POSIX Signals
On 03/09/2018 08:41 AM, Rich Felker wrote:
On Fri, Mar 09, 2018 at 02:43:06AM -0800, Daniel Colascione wrote:
People use signals for lots of things today. They mostly work fine.
I'm proposing a mechanism to make signals *less* "scary", not
*more*. Besides, it's not libc's job to make value judgments about
which techniques application developers should use. At this low
level, libraries should provide capabilities, not opinions.
I think we need to weigh the benefits of making signals less
scary/unsafe/hideous versus the benefits of leaving them so. Yes,
people use signals today. Most of the uses are utterly unsafe and
utterly wrong. Most of them are not even justified; they're for lack
of knowing better or just cargo-culting from something they saw done
elsewhere. Do new interfaces fix existing incorrect usage and
discourage it in the future?
I see little evidence that people use signals unnecessarily. If someone
is determined to use signals, the existing interfaces are adequate for
causing chaos. They are inadequate for the purpose of letting experts do
the right thing.
There is an argument for programming environments having safeguards, but
libc is far too fundamental and far too low-level to serve a role as a
guarantor of safety. libc needs to let people who know what they're
doing do their work.
For asynchronously delivered signals (such as subprocess termination),
the signal mechanism may not be entirely appropriate anyway.
It beats wait. Which part of my proposed mechanism would operate
improperly?
It just doesn't scale at all. For each subprocess termination,
you have to iterate through about half of the registered signal
handlers until you hit one that happens to know about the PID that
was terminated. Same for the other signals.
It's linear in the number of components asking for notification, not
in the number of processes awaited. si_pid in siginfo makes
identifying a particular child fast.
Also, I don't see any realistic alternatives to the wait family of
APIs being proposed either. (And as I explain below, "just use glib"
is completely unacceptable as a response to a fundamental defect in
the design of wait*(2).)
"Just use glib" is of course fundamentally unacceptable. But the
obvious solution is "just use threads" and I don't see why that's not
acceptable. The cost of a thread is miniscule compared to the cost of
a child process, and threads performing synchronous waitpid can
convert the result into whatever type of notification (poll wakeup,
cond var, synchronous handling, etc.) you like. This is clearly the
best approach for any application that's not creating at least
tens/hundreds of child processes per second; when people refuse to use
it in such a situation, it's because of irrational aversion to threads
and nothing else.
Suppose you're the JVM. You have no idea how many subprocesses someone
might create. Some users might have a lot of children, and an internal
thread per child starts to look very expensive. "Wait", you might
exclaim in surprise. "I can use wait(-1) and have the kernel just _tell_
me which process exits!" So you use wait(-1) and your system appears to
work fine.
Now imagine two runtime environments think this way and you want to use
them in the same process. (It's more likely that you might think.) The
wait(-1) scheme fails the "What if two libraries did this?" test and a
thread-per-process scheme doesn't scale.
SIGCHLD solves the whole problem. It provides a way for the kernel to
tell any interested observer about the death of a process without racing
with _other_ interested observers who want to learn about the states of
_their_ processes. (You have to scan, unfortunately, because multiple
pending SIGCHLDs can be collapsed into one.)
Is it ideal? No. I'd much rather have some kind of real waitable
process-handle FD. But it works.
In any case, SIGCHLD is separable from the rest of my proposal. We could
in principle agree that SIGCHLD is a bad idea and that we still need a
better interface for signals generally.
For loads where the cost of child process creation and termination is
the dominant factor, I'll grant that the added cost of a thread
lifecycle might not be acceptable. But there are much better
approaches like just forcing each child to inherit a pipe, and polling
those pipes to determine when the child exited, that are very light
(roughly equivalent to BSD forkfd) and much cleaner than using
signals.
The "death pipe" trick isn't general-purpose. A child can itself
propagate that FD to other children, leading to a false-negative result,
since the FD's lifetime can exceed that of the process it's intended to
monitor.
I very much want something like forkfd. The process waiting APIs being
awful is a longstanding problem with POSIX systems. It's also impossible
to write a race-free pkill! Most people just rely on PID reuse not being
fast enough to cause real problems. This kind of thinking is terrifying.
Ideally, you'd be able to open *any* process and get a file descriptor
"handle" for it. Then, you'd be able to perform all process management
operations (waiting, sending a signal, reading times, etc.) via the
handle, either directly (by passing the handle FD to some API) or
indirectly (by relying on the handle reserving the PID of the process to
which it refers and preventing reuse). The "direct" approach is better,
since it lets the process FD act as a credential.
In any case, focusing on this one child-monitoring use case misses
the point. My original message lists many different example use
cases for shared signals, all of which we could address with a
simple API. It would take decades for standard alternatives for each
of these use cases to become available universally.
I don't think it misses the point when the point is to determine
whether the legitimate uses of signals establish a compelling need for
new interfaces. Each possible use needs to be evaluated one by one.
Even if you ignored my other signal use cases, the first one I listed,
high performance runtime optimization via SIGSEGV, would be sufficient
grounds for a new interface for arbitrating access.