diff options
Diffstat (limited to 'src/libutil/monitor-fd.hh')
-rw-r--r-- | src/libutil/monitor-fd.hh | 73 |
1 files changed, 52 insertions, 21 deletions
diff --git a/src/libutil/monitor-fd.hh b/src/libutil/monitor-fd.hh index 228fb13f8..ea6e89017 100644 --- a/src/libutil/monitor-fd.hh +++ b/src/libutil/monitor-fd.hh @@ -10,6 +10,8 @@ #include <unistd.h> #include <signal.h> +#include "error.hh" +#include "file-descriptor.hh" #include "signals.hh" namespace nix { @@ -19,28 +21,46 @@ class MonitorFdHup { private: std::thread thread; + /** + * Pipe used to interrupt the poll()ing in the monitoring thread. + */ + Pipe terminatePipe; + std::atomic_bool quit = false; public: MonitorFdHup(int fd) { - thread = std::thread([fd]() { - while (true) { + terminatePipe.create(); + auto &quit_ = this->quit; + int terminateFd = terminatePipe.readSide.get(); + thread = std::thread([fd, terminateFd, &quit_]() { + while (!quit_) { /* Wait indefinitely until a POLLHUP occurs. */ - struct pollfd fds[1]; + struct pollfd fds[2]; fds[0].fd = fd; - /* Polling for no specific events (i.e. just waiting - for an error/hangup) doesn't work on macOS - anymore. So wait for read events and ignore - them. */ - fds[0].events = - #ifdef __APPLE__ - POLLRDNORM - #else - 0 - #endif - ; - auto count = poll(fds, 1, -1); - if (count == -1) abort(); // can't happen + // There is a POSIX violation on macOS: you have to listen for + // at least POLLHUP to receive HUP events for a FD. POSIX says + // this is not so, and you should just receive them regardless, + // however, as of our testing on macOS 14.5, the events do not + // get delivered in such a case. + // + // This is allegedly filed as rdar://37537852. + // + // Relevant code, which backs this up: + // https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/bsd/kern/sys_generic.c#L1751-L1758 + fds[0].events = POLLHUP; + fds[1].fd = terminateFd; + fds[1].events = POLLIN; + + auto count = poll(fds, 2, -1); + if (count == -1) { + if (errno == EINTR || errno == EAGAIN) { + // These are best dealt with by just trying again. + continue; + } else { + throw SysError("in MonitorFdHup poll()"); + } + } /* This shouldn't happen, but can on macOS due to a bug. See rdar://37550628. @@ -53,9 +73,16 @@ public: triggerInterrupt(); break; } - /* This will only happen on macOS. We sleep a bit to - avoid waking up too often if the client is sending - input. */ + // No reason to actually look at the pipe FD if that's what + // woke us, the only thing that actually matters is the quit + // flag. + if (quit_) { + break; + } + // On macOS, it is possible (although not observed on macOS + // 14.5) that in some limited cases on buggy kernel versions, + // all the non-POLLHUP events for the socket get delivered. + // Sleeping avoids pointlessly spinning a thread on those. sleep(1); } }); @@ -63,8 +90,12 @@ public: ~MonitorFdHup() { - pthread_cancel(thread.native_handle()); - thread.join(); + quit = true; + // Poke the thread out of its poll wait + writeFull(terminatePipe.writeSide.get(), "*", false); + if (thread.joinable()) { + thread.join(); + } } }; |