Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.Sign up
Signal Handling #16
Signals, how do they work?
The goal of this proposal is to make it possible to write programs using mio that can successfully handle Unix signals.
Unfortunately, signal handling is somewhat tricky, made even more complex by the way Unix delivers signals to multi-threaded programs.
At a high level, the goal for
This means that programs that want to use this facility will ignore signals on all threads, and we will use
If a program uses this facility without successfully ignoring signals, signals may be delivered to random user threads instead of the reactor.
To make it easy for users to successfully ignore signals across their entire program, a new function,
The information provided to the handler will be the subset of
In order to ensure that signal notifications are sent to the reactor loop, we need to:
It is in theory possible to use this facility without control over signal masking, but that will mean that signals can be missed if they get dispatched to another thread. For programs that want to handle signals, this is very unlikely to be desirable.
It masks out all the signals on the current thread. The intent is to run it early on in
Each reactor could opt into receiving signals. In practice, this means you should do one of the following:
I've been thinking about this issue and can't decide how to implement signal handling for OS X.
To summarise possible implementations:
@vbuslov I've been pretty slow on this, but here is my point of view:
I think what I would do is to
Does that sound plausible? Did I miss anything? Anything else I can add?
Sadly the Async-Signal-Safe rules only help you with libc, not libstd, jemalloc, or your own code. You're also compelled not to deadlock or otherwise reach unsafety on your own even if you're not calling libc unsafely. So, for instance, you can't do allocations except on the stack, and you can't access any data structures where code needs to complete to dynamically uphold safety invariants.
I believe you can get away with the self-pipe trick + sending
This does seem like it makes it hard to support multiple reactors. :( Perhaps there's no use case for multiple reactors independently interested in signals (since signals are process-wide anyway), and they can all select on a single fd, or something.
BTW, do you have partial work on this? I'm interested in seeing signal and
Ugh, another problem here: signal dispositions get inherited to child processes, and at the very least,
(Even if mio were able to conspire with
So I think a
I'm not seeing a solution to this other than using the self-pipe trick on all platforms (where the signal could get delivered on any thread, but that's OK), with the side effect of requiring all code to be
@wycats and I talked a bit on Sunday about the inheritance issue. To summarize how we understand things:
So, we need to go around to all the C libraries that spawn helper processes, and make sure that they properly unmask signals (and potentially reset all signal handlers first) between fork and exec. We also need to get the same change into Rust's
I'm still unhappy about the
Somewhat unrelatedly, I think I'm concluding that it's strictly worse to use
See also GHC ticket #2451, comment 19 onwards:
If we want to get one siginfo per
Alternatively, we could decide that the semantics for mio signal delivery involve siginfos as a best-effort thing, but acknowledge that they may get coalesced, and you may only get one siginfo if multiple signals of the same type were delivered between calls to the reactor.
I think there are three major subtasks in this issue:
I am of the opinion that items 1 and 2 are worthwhile tasks. 3 is not. I might be wrong, I am willing to defer to the results of a survey of developers for the market viability of signal delivery as a feature. My instinct is that, frankly, if someone is interested in organizing their management of signals they've made some wrong architectural decisions and I am not interested in helping someone do the wrong thing. That's as much as I'll say about item 3.
Re item 1, retrying on eintr is a pretty good thing to do in general (except for close()1) While I don't think much effort spent on signals is warranted as a lib designer, I do think that we should operate correctly if someone does have a legitimate use case for SIGUSR or SIGCHLD.
Re item 2, in my career as a POSIX dev, pretty much every mature software dev organization on *nix had an application framework context that they initialized. It set a static global variable named something intelligent like "volatile int __running_g = 1;" And the application context just set a handler for SIGINT and set that __running_g to 0, then the event loop would shut down, worker threads would spin down, etc. It doesn't matter which thread gets picked to handle the signal, the handler gets run, the global atomic variable gets tripped and things begin shutting down gracefully. I'd probably recommend a configuration setting in Mio EventLoop which is handle_sigint or something to let people opt out.
 close() isn't really as broken as the article referenced above might suggest, the most correct behavior is to not retry on close. The worst case is very remote possibility is you do leak a file descriptor. I don't know what the exact odds are, but let's say a 1 in 1,000,000 chance, let's also put the odds of a signal occuring during a close() in your program at 1,000,000 for round numbers. Also a paranoid sysadmin put the per-user fd limit at 4096 for some silly reason. Well then you'd need to handle 4e15 signals in your process to run out of fds. If you came anywhere close to that, I'd say that you're doing something really dumb with signals in the first place :)
The things that have me interested in signal handling are clean handling of
I'm actually less concerned about
I think retry-on-
Maybe we're saying the same thing here, but I don't think it's possible to generically designate a signal handling thread. Unknown future libraries in the application will create a thread and ruin everything :)
I am all for providing some cleanly wrapped library functions that let a user do it themselves, provided they know what they're doing. Unless I'm missing something, all they'd need at that point to interact with EventLoop is a Notify Sender channel. I don't even think it would require a special callback in Handler, notify should be sufficient, obviously Message would need to understand how to carry said information, but that is entirely up to the API user, IMO.
By the way, it was pointed out to me that you don't get reliable signal delivery over re-entrant
So this simplifies the API that mio needs to expose, and gets us closer to offering a clean abstraction like we do for sockets or timers, instead of something that's closely related to OS quirks. It also lets us get away with using
I do wonder if there's value in mio offering subprocess management in the mainloop, and offering an API that doesn't specifically reference
referenced this issue
May 25, 2015
After talking more with @geofft, I believe that signal handling would be better off as a standalone library (built on top of nix). The reasons:
I'm hoping that @geofft will write this library