4 ms·
People are unreasonable scared of doing things in signal handlers. It's very much not the case that the only sane thing to do is writing to a pipe --- for start
by quotemstr 9y ago
People are unreasonable scared of doing things in signal handlers. It's very much not the case that the only sane thing to do is writing to a pipe --- for starters, there's an AF_UNIX socket. Or longjmp.
There's a lot of bad advice floating around about what is and is not "portable" based on half-remembered bugs in old systems nobody uses anymore. Examine the situation from first principles based on the world today, not dubious advice from an irrelevant era.
- wahern 9y agoWhat people often don't understand is that a signal handler never has to return, and indeed as far as the kernel is concerned (traditionally speaking) never does return. To deliver a signal all the kernel really does is reset the program counter. Okay, that's not the whole truth--first it sets up a trampoline so that if the handler does return the program counter and stack pointer is restored, plus potentially some other magic. But the important thing is that a signal is fundamentally nothing more than an unscheduled goto, as opposed to an asynchronous call/return. This is why the kernel is never in danger of recursing and only a small, fixed amount of kernel state is needed to deliver signals (ignore real-time signals). Maintaining this O(1) space and time characteristic explains a lot about the semantics of signals, abstractly and in concrete implementations. Know this doesn't make it any less dangerous in terms of the potential pitfalls, but IMO understanding how it works can help to make it clear how signals can be usefully employed. And it also shows why simply writing to a pipe is problematic--a pipe can overflow and cause an application to miss signals that smarter code wouldn't. signalfd was born precisely because of this limitation. (Unlike BSD kqueue, signalfd only supports a single listener--including signal handlers--per signal, which means signalfd only solves the very narrow problem of overflowing a pipe.)
- cryptonector 9y agoWriting to a pipe is not a problem! Make it non-blocking and ignore EAGAIN. The point is to wake the event loop, and if the pipe is full then the event loop will wake eventually -- the event loop has fallen behind, it might yet catch up. If you think it won't, then _exit() instead on EAGAIN when writing to the pipe.
- wahern 9y agoImagine a series of SIGPIPEs filling the pipe, followed by a SIGTERM or SIGHUP that gets dropped on the floor. At best that's a broken application, at worst it could be a security vulnerability. Every modern Unix (BSDs, Linux, macOS, Solaris) provides a pollable signal notification mechanism. But you can also always carefully make use of signal masks and 1) use either pselect as the root[1] of your event loop or 2) use a special thread to catch and coalesce signals. [1] BSD kevent(), Linux epoll(), and Solaris port_wait() all support waiting on a kqueue, epoll, or port descriptor, respectively. That is, you can have a tree of event descriptors. Though with those you also always have a better [passive] signal notification system.
- cryptonector 9y agoFirst, as to SIGPIPE, I just don't want even to generate it (and try hard not to), much less catch it -- so if I can then I set its disposition to SIG_IGN. Second, as to exit conditions, I always set a sig_atomic_t variable to indicate that an exit has been requested via a signal, and I check it in the event loop, so even if the pipe is full, the event loop will see this and cannot miss it. Third, if need be I use multiple pipes, one per-signal. I've only ever done this for SIGCHLD and SIGUSR1/2 -- that is, I'll have a pipe for all the exit-request signals (e.g., SIGTERM), one for SIGCHLD (if the process creates children processes), and one for SIGUSR1 and/or SIGUSR2. Fourth, the pollable signal notification interfaces have caveats. I don't even need to think about or know them as long as I have a reliable way to handle signals (which the self-pipe pattern is). Fifth, signals are not reliable. Generally, signal events can be coalesced (yes, I know...) -- coalescing them in the self-pipe pattern by simply ignoring EAGAIN conditions is just as well (provided you leave evidence of the signal in a sig_atomic_t if you need that evidence). Sixth, select()/poll() suck, a lot, but they are portable, and more so than pselect()/ppoll(). And if you're using select() or poll() then the self-pipe pattern works very well and yields portable code. Of course, whenever possible I prefer to use kqueue, epoll, or Solaris event ports. Since I have to work with Linux (sadness) I have to work with epoll (much much sadness), and for all of epoll's warts, it's fantastically better than select()/poll(). Even so, I prefer to self-pipe than to use signalfd() or what have you. I've been using the self-pipe trick for well over a decade and it's never been a problem. It's true that I don't usually implement language run-times where I need to handle synchronous signals such as SIGSEGV or SIGBUS in creative ways, and if I did I might well make use of longjump() and such in signal handlers -- I'd probably write an async-signal-safe stack walker even, and stack trace printer, and I've looked into that in the past and know it can be and has been done. I do maintain a run-time, jq's, but it's as a toy by comparison to, say, a JVM, and it does not need to play such games.
- cryptonector 9y agoI didn't downvote you, fyi. You're right that longjump()... is on the POSIX async-signal-safe list [0]. And I/O to sockets using system calls like sendmsg(2), is also permitted in async signal handlers because those are also on the list of async-signal-safe functions. But it's still the case that the best thing to do (in 99% of cases) is to "self-pipe". If you don't have an event loop, then maybe you want to do something else, but it can get hairy right quick, and I'd much rather give advice most programmers can follow that will keep them out of trouble. [0] http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2...