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

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