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:
parent
d75a0d714e
commit
9a577f8060
2
NEWS
2
NEWS
@ -1,4 +1,6 @@
|
|||||||
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 @@
|
|||||||
|
|
||||||
#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
|
|||||||
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
|
|||||||
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
|
|||||||
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
|
|||||||
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
|
|||||||
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
|
|||||||
|
|
||||||
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:
|
|||||||
i.ClearReturnedEvents();
|
i.ClearReturnedEvents();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_EPOLL
|
||||||
|
for (const auto &i : always_ready_fds)
|
||||||
|
f(i.fd, i.revents);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
Loading…
Reference in New Issue
Block a user