Another weirdness about signalfd: read() from a signalfd returns signals for the calling process, regardless of what process created the signalfd (e.g. it could have been inherited through fork()). That's arguably usually what you want, but is inconsistent with usual file descriptor semantics, which say that it doesn't matter who read()s.
One place where the inconsistency gets weird is when you use signalfd with epoll. The epoll will flag events on the signalfd based on the process where the signalfd was registered with epoll, not the process where the epoll is being used. One case where this can be surprising is if you set up a signalfd and an epoll and then fork() for the purpose of daemonizing -- now you will find that your epoll mysteriously doesn't deliver any events for the signalfd despite the signalfd otherwise appearing to function as expected. That took me a day or two to debug. :(
With all that said, at the end of the day I disagree with Geoff. I would rather use signalfd than signal handlers. The "self-pipe trick" is ugly, involves a lot of unnecessary overhead, and runs the risk of deadlocking if you receive enough signals to fill the pipe buffer before you read them back (which can be solved with additional synchronization, but ick). In fact, in my own code, on systems that don't have signalfd or any similar mechanism, I tend to block signals except when I'm about to call poll(), and then siglongjmp() out of the signal handler to avoid the usual race condition. (See pselect(2) for discussion of said race condition.)
I think it's just a fact of life that you need to clear your signal mask between fork() and exec(), and yeah no one does this, whoops.
BTW, for the specific problem of dealing with child processes, I really hope Linux adopts the Capsicum interface as FreeBSD has:
> The "self-pipe trick" is ugly, involves a lot of unnecessary overhead, and runs the risk of deadlocking if you receive enough signals to fill the pipe buffer before you read them back
The unfortunate terseness of the original "self-pipe trick" description makes the solution to this difficult to see. As far as I've figured out there are two things to notice:
1) You're supposed to set the pipe to be non-blocking. Presumably you also then don't check the return code of the write(2) call in the signal handler. While this solves the case of a signal handler blocking forever, it does mean you might have dropped writes that correspond to signal receptions. That leads us to:
2) The self-pipe trick specifically calls out handling SIGCHLD (probably because it's one signal that you don't want to ignore!) But given the chances of dropping a byte as described in 1) and the fact that SIGCHLD and fork are explicitly called out, I can only assume that the lesson here is: only have one pipe per signal you intend to handle. Since multiple signals sent to a process may result in a single signal being delivered, your real signal handling code (the stuff that's watching the other end of the pipe) already has to deal with this situation.
As for Capsicum, I can't wait til they implement pdwait(2)! Until then, at least pdfork(2) ensures that the parent process' death kills the child process...
> 1) You're supposed to set the pipe to be non-blocking. Presumably you also then don't check the return code of the write(2) call in the signal handler. While this solves the case of a signal handler blocking forever, it does mean you might have dropped writes that correspond to signal receptions.
That doesn't matter. You're not supposed to have a byte in the pipe for every signal. What matters is having at least one byte any time there are unprocessed signals. The only function of the pipe is to wake the select(2) up. You still need bookkeeping elsewhere.
> That leads us to:
> 2) The self-pipe trick specifically calls out handling SIGCHLD (probably because it's one signal that you don't want to ignore!) But given the chances of dropping a byte as described in 1) and the fact that SIGCHLD and fork are explicitly called out, I can only assume that the lesson here is: only have one pipe per signal you intend to handle. Since multiple signals sent to a process may result in a single signal being delivered, your real signal handling code (the stuff that's watching the other end of the pipe) already has to deal with this situation.
Meh. Just have one pipe and a sig_atomic_t for each different types of signal you're interested in.
>One place where the inconsistency gets weird is when you use signalfd with epoll. The epoll will flag events on the signalfd based on the process where the signalfd was registered with epoll, not the process where the epoll is being used. One case where this can be surprising is if you set up a signalfd and an epoll and then fork() for the purpose of daemonizing -- now you will find that your epoll mysteriously doesn't deliver any events for the signalfd despite the signalfd otherwise appearing to function as expected. That took me a day or two to debug.
Is this what libuv does? I'm pretty sure it reads signals using epoll on linux, so in theory - if it does it this way - this bug could be be underlying all of node.js.
Does running the self-pipe trick on a separate thread solve that issue? It seems like it's basically equivalent to signalfd (neither worse nor better, unless you're worried about platform-specific thread bugs): you end up with a signal mask on your main thread, but you also avoid EINTR on your main thread. Any possible pipe lockup just happens on the signal-handling thread, so the mainloop can keep running and eventually dequeue signals.
> Does running the self-pipe trick on a separate thread solve that issue? It seems like it's basically equivalent to signalfd (neither worse nor better, unless you're worried about platform-specific thread bugs): you end up with a signal mask on your main thread, but you also avoid EINTR on your main thread. Any possible pipe lockup just happens on the signal-handling thread, so the mainloop can keep running and eventually dequeue signals.
There's no need for threads. Set the pipe to non-blocking and ignore the write() error if it's EAGAIN/EWOULDBLOCK. See my response above for why dropping writes if a byte already exists in the pipe is okay.
Sure, but that doesn't solve the EINTR problem. If you accept signal-handler interruptions on threads where you actually do work, then you risk interrupting system calls on those threads, and even SA_RESTART isn't guaranteed to work all of the time. That's what a separate thread (or signalfd) wins you.
> Does running the self-pipe trick on a separate thread solve that issue?
Perhaps but then you're mixing signals and threads and you're in for a whole new world of hurt. :)
E.g. I've found that OSX does not always behave correctly when delivering signals to a process where one thread has blocked the signal but another hasn't, though I cannot remember the exact details. And of course on any system there is such a thing as signals addressed to a specific thread rather than a whole process (ptherad_kill()).
I've heard claims of badness with signals and threads, but I've failed to track down concrete problems -- I would really like to know what they are. Meanwhile I've heard of people successfully using dedicated signal-handling threads in production, at least on Linux.
I'm not really sure that thread-directed signals are in scope for the sorts of things where you must use signals (SIGINT, SIGTSTP, etc. from a terminal, SIGCHLD from child termination, etc.) Those should all be process-directed. If you design your own API that involves signals, then sure, but that's a problem of your own making.
One place where the inconsistency gets weird is when you use signalfd with epoll. The epoll will flag events on the signalfd based on the process where the signalfd was registered with epoll, not the process where the epoll is being used. One case where this can be surprising is if you set up a signalfd and an epoll and then fork() for the purpose of daemonizing -- now you will find that your epoll mysteriously doesn't deliver any events for the signalfd despite the signalfd otherwise appearing to function as expected. That took me a day or two to debug. :(
With all that said, at the end of the day I disagree with Geoff. I would rather use signalfd than signal handlers. The "self-pipe trick" is ugly, involves a lot of unnecessary overhead, and runs the risk of deadlocking if you receive enough signals to fill the pipe buffer before you read them back (which can be solved with additional synchronization, but ick). In fact, in my own code, on systems that don't have signalfd or any similar mechanism, I tend to block signals except when I'm about to call poll(), and then siglongjmp() out of the signal handler to avoid the usual race condition. (See pselect(2) for discussion of said race condition.)
I think it's just a fact of life that you need to clear your signal mask between fork() and exec(), and yeah no one does this, whoops.
BTW, for the specific problem of dealing with child processes, I really hope Linux adopts the Capsicum interface as FreeBSD has:
https://www.freebsd.org/cgi/man.cgi?query=pdfork&sektion=2
Until then, you simply can't expect to reap children via signals. You use the signal to let you know that it's time to call wait().