diff options
author | Max Kellermann <max@musicpd.org> | 2019-12-20 13:54:16 +0100 |
---|---|---|
committer | Max Kellermann <max@musicpd.org> | 2019-12-22 12:08:44 +0100 |
commit | 9a577f8060413f7d5d2b557f7067dad4bceeeb36 (patch) | |
tree | 027046c7d5845008ef7065f1b669bbb24eb53221 | |
parent | d75a0d714ec966e63f75c9e81bd82dfae10d84a0 (diff) |
event/MultiSocketMonitor: add workaround for /dev/null
The ALSA "null" driver opens /dev/null and returns the file handle
from snd_pcm_poll_descriptors(), but /dev/null cannot be used with
epoll, the epoll_ctl() system call returns -EPERM. This means that
the ALSA output hangs, eventually freezing the whole MPD process.
This commit adds a workaround to the MultiSocketMonitor class which is
used by the ALSA output plugin.
Closes https://github.com/MusicPlayerDaemon/MPD/issues/695
-rw-r--r-- | NEWS | 2 | ||||
-rw-r--r-- | src/event/MultiSocketMonitor.cxx | 36 | ||||
-rw-r--r-- | src/event/MultiSocketMonitor.hxx | 20 |
3 files changed, 57 insertions, 1 deletions
@@ -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 <algorithm> +#ifdef USE_EPOLL +#include <errno.h> +#endif + #ifndef _WIN32 #include <poll.h> #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<SingleFD> 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<AlwaysReady> 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: |