Merge pull request #279 from apple/mad/greedy-signalfd
Fix improper double-fire of signal sources on Linux
diff --git a/src/event/event_epoll.c b/src/event/event_epoll.c
index c003bac..c86421b 100644
--- a/src/event/event_epoll.c
+++ b/src/event/event_epoll.c
@@ -474,12 +474,24 @@
{
dispatch_unote_linkage_t dul, dul_next;
struct signalfd_siginfo si;
+ ssize_t rc;
- dispatch_assume(read(dmn->dmn_fd, &si, sizeof(si)) == sizeof(si));
-
- TAILQ_FOREACH_SAFE(dul, &dmn->dmn_readers_head, du_link, dul_next) {
- dispatch_unote_t du = _dispatch_unote_linkage_get_unote(dul);
- dux_merge_evt(du._du, EV_ADD|EV_ENABLE|EV_CLEAR, 1, 0, 0);
+ // Linux has the weirdest semantics around signals: if it finds a thread
+ // that has not masked a process wide-signal, it may deliver it to this
+ // thread, meaning that the signalfd may have been made readable, but the
+ // signal consumed through the legacy delivery mechanism.
+ //
+ // Because of this we can get a misfire of the signalfd yielding EAGAIN the
+ // first time around. The _dispatch_muxnote_signal_block_and_raise() hack
+ // will kick in, the thread with the wrong mask will be fixed up, and the
+ // signal delivered to us again properly.
+ if ((rc = read(dmn->dmn_fd, &si, sizeof(si))) == sizeof(si)) {
+ TAILQ_FOREACH_SAFE(dul, &dmn->dmn_readers_head, du_link, dul_next) {
+ dispatch_unote_t du = _dispatch_unote_linkage_get_unote(dul);
+ dux_merge_evt(du._du, EV_ADD|EV_ENABLE|EV_CLEAR, 1, 0, 0);
+ }
+ } else {
+ dispatch_assume(rc == -1 && errno == EAGAIN);
}
}