Commit 9a577f80 authored by Max Kellermann's avatar Max Kellermann

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
parent d75a0d71
ver 0.21.18 (not yet released) ver 0.21.18 (not yet released)
* output
- alsa: fix hang bug with ALSA "null" outputs
* reduce unnecessary CPU wakeups * reduce unnecessary CPU wakeups
ver 0.21.17 (2019/12/16) ver 0.21.17 (2019/12/16)
......
...@@ -22,6 +22,10 @@ ...@@ -22,6 +22,10 @@
#include <algorithm> #include <algorithm>
#ifdef USE_EPOLL
#include <errno.h>
#endif
#ifndef _WIN32 #ifndef _WIN32
#include <poll.h> #include <poll.h>
#endif #endif
...@@ -37,6 +41,9 @@ MultiSocketMonitor::Reset() noexcept ...@@ -37,6 +41,9 @@ MultiSocketMonitor::Reset() noexcept
assert(GetEventLoop().IsInside()); assert(GetEventLoop().IsInside());
fds.clear(); fds.clear();
#ifdef USE_EPOLL
always_ready_fds.clear();
#endif
IdleMonitor::Cancel(); IdleMonitor::Cancel();
timeout_event.Cancel(); timeout_event.Cancel();
ready = refresh = false; ready = refresh = false;
...@@ -49,6 +56,13 @@ MultiSocketMonitor::AddSocket(SocketDescriptor fd, unsigned events) noexcept ...@@ -49,6 +56,13 @@ MultiSocketMonitor::AddSocket(SocketDescriptor fd, unsigned events) noexcept
bool success = fds.front().Schedule(events); bool success = fds.front().Schedule(events);
if (!success) { if (!success) {
fds.pop_front(); 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; return success;
...@@ -60,6 +74,9 @@ MultiSocketMonitor::ClearSocketList() noexcept ...@@ -60,6 +74,9 @@ MultiSocketMonitor::ClearSocketList() noexcept
assert(GetEventLoop().IsInside()); assert(GetEventLoop().IsInside());
fds.clear(); fds.clear();
#ifdef USE_EPOLL
always_ready_fds.clear();
#endif
} }
#ifndef _WIN32 #ifndef _WIN32
...@@ -67,6 +84,10 @@ MultiSocketMonitor::ClearSocketList() noexcept ...@@ -67,6 +84,10 @@ MultiSocketMonitor::ClearSocketList() noexcept
void void
MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n) noexcept MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n) noexcept
{ {
#ifdef USE_EPOLL
always_ready_fds.clear();
#endif
pollfd *const end = pfds + n; pollfd *const end = pfds + n;
UpdateSocketList([pfds, end](SocketDescriptor fd) -> unsigned { UpdateSocketList([pfds, end](SocketDescriptor fd) -> unsigned {
...@@ -89,7 +110,20 @@ MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n) noexcept ...@@ -89,7 +110,20 @@ MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n) noexcept
void void
MultiSocketMonitor::Prepare() noexcept 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()) if (timeout >= timeout.zero())
timeout_event.Schedule(timeout); timeout_event.Schedule(timeout);
else else
......
...@@ -102,6 +102,21 @@ class MultiSocketMonitor : IdleMonitor ...@@ -102,6 +102,21 @@ class MultiSocketMonitor : IdleMonitor
std::forward_list<SingleFD> fds; 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: public:
static constexpr unsigned READ = SocketMonitor::READ; static constexpr unsigned READ = SocketMonitor::READ;
static constexpr unsigned WRITE = SocketMonitor::WRITE; static constexpr unsigned WRITE = SocketMonitor::WRITE;
...@@ -198,6 +213,11 @@ public: ...@@ -198,6 +213,11 @@ public:
i.ClearReturnedEvents(); i.ClearReturnedEvents();
} }
} }
#ifdef USE_EPOLL
for (const auto &i : always_ready_fds)
f(i.fd, i.revents);
#endif
} }
protected: protected:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment