diff --git a/NEWS b/NEWS index 169a9610b..7804e0d20 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,6 @@ ver 0.21.18 (not yet released) +* output + - alsa: fix hang bug with ALSA "null" outputs * reduce unnecessary CPU wakeups ver 0.21.17 (2019/12/16) diff --git a/src/event/MultiSocketMonitor.cxx b/src/event/MultiSocketMonitor.cxx index 98d57332c..7704bd8e8 100644 --- a/src/event/MultiSocketMonitor.cxx +++ b/src/event/MultiSocketMonitor.cxx @@ -22,6 +22,10 @@ #include +#ifdef USE_EPOLL +#include +#endif + #ifndef _WIN32 #include #endif @@ -37,6 +41,9 @@ MultiSocketMonitor::Reset() noexcept assert(GetEventLoop().IsInside()); fds.clear(); +#ifdef USE_EPOLL + always_ready_fds.clear(); +#endif IdleMonitor::Cancel(); timeout_event.Cancel(); ready = refresh = false; @@ -49,6 +56,13 @@ MultiSocketMonitor::AddSocket(SocketDescriptor fd, unsigned events) noexcept bool success = fds.front().Schedule(events); if (!success) { fds.pop_front(); + +#ifdef USE_EPOLL + if (errno == EPERM) + /* not supported by epoll (e.g. "/dev/null"): + add it to the "always ready" list */ + always_ready_fds.push_front({fd, events}); +#endif } return success; @@ -60,6 +74,9 @@ MultiSocketMonitor::ClearSocketList() noexcept assert(GetEventLoop().IsInside()); fds.clear(); +#ifdef USE_EPOLL + always_ready_fds.clear(); +#endif } #ifndef _WIN32 @@ -67,6 +84,10 @@ MultiSocketMonitor::ClearSocketList() noexcept void MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n) noexcept { +#ifdef USE_EPOLL + always_ready_fds.clear(); +#endif + pollfd *const end = pfds + n; UpdateSocketList([pfds, end](SocketDescriptor fd) -> unsigned { @@ -89,7 +110,20 @@ MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n) noexcept void MultiSocketMonitor::Prepare() noexcept { - const auto timeout = PrepareSockets(); + auto timeout = PrepareSockets(); + +#ifdef USE_EPOLL + if (!always_ready_fds.empty()) { + /* if there was at least one file descriptor not + supported by epoll, install a very short timeout + because we assume it's always ready */ + constexpr std::chrono::steady_clock::duration ready_timeout = + std::chrono::milliseconds(1); + if (timeout < timeout.zero() || timeout > ready_timeout) + timeout = ready_timeout; + } +#endif + if (timeout >= timeout.zero()) timeout_event.Schedule(timeout); else diff --git a/src/event/MultiSocketMonitor.hxx b/src/event/MultiSocketMonitor.hxx index 11308d505..04f3bee8a 100644 --- a/src/event/MultiSocketMonitor.hxx +++ b/src/event/MultiSocketMonitor.hxx @@ -102,6 +102,21 @@ class MultiSocketMonitor : IdleMonitor std::forward_list fds; +#ifdef USE_EPOLL + struct AlwaysReady { + const SocketDescriptor fd; + const unsigned revents; + }; + + /** + * A list of file descriptors which are always ready. This is + * a kludge needed because the ALSA output plugin gives us a + * file descriptor to /dev/null, which is incompatible with + * epoll (epoll_ctl() returns -EPERM). + */ + std::forward_list always_ready_fds; +#endif + public: static constexpr unsigned READ = SocketMonitor::READ; static constexpr unsigned WRITE = SocketMonitor::WRITE; @@ -198,6 +213,11 @@ public: i.ClearReturnedEvents(); } } + +#ifdef USE_EPOLL + for (const auto &i : always_ready_fds) + f(i.fd, i.revents); +#endif } protected: