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
This commit is contained in:
Max Kellermann 2019-12-20 13:54:16 +01:00
parent d75a0d714e
commit 9a577f8060
3 changed files with 57 additions and 1 deletions

2
NEWS
View File

@ -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)

View File

@ -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

View File

@ -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: