mpd/src/event/Loop.cxx

354 lines
6.7 KiB
C++
Raw Normal View History

/*
2020-01-18 19:22:19 +01:00
* Copyright 2003-2020 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "Loop.hxx"
#include "TimerEvent.hxx"
#include "SocketEvent.hxx"
#include "IdleEvent.hxx"
#include "util/ScopeExit.hxx"
2020-10-14 14:37:28 +02:00
#ifdef HAVE_THREADED_EVENT_LOOP
#include "DeferEvent.hxx"
#endif
2020-05-05 15:18:02 +02:00
#ifdef HAVE_URING
#include "UringManager.hxx"
#include "util/PrintException.hxx"
#include <stdio.h>
#endif
constexpr bool
EventLoop::TimerCompare::operator()(const TimerEvent &a,
const TimerEvent &b) const noexcept
{
return a.due < b.due;
}
EventLoop::EventLoop(
#ifdef HAVE_THREADED_EVENT_LOOP
ThreadId _thread
#endif
)
:
#ifdef HAVE_THREADED_EVENT_LOOP
wake_event(*this, BIND_THIS_METHOD(OnSocketReady)),
thread(_thread),
/* if this instance is hosted by an EventThread (no ThreadId
known yet) then we're not yet alive until the thread is
started; for the main EventLoop instance, we assume it's
already alive, because nobody but EventThread will call
SetAlive() */
alive(!_thread.IsNull()),
#endif
quit(false)
{
#ifdef HAVE_THREADED_EVENT_LOOP
wake_event.Open(SocketDescriptor(wake_fd.Get()));
#endif
}
2018-01-29 21:46:07 +01:00
EventLoop::~EventLoop() noexcept
{
assert(idle.empty());
assert(timers.empty());
}
2020-05-05 15:18:02 +02:00
#ifdef HAVE_URING
Uring::Queue *
EventLoop::GetUring() noexcept
{
if (!uring_initialized) {
uring_initialized = true;
2020-05-05 15:18:02 +02:00
try {
uring = std::make_unique<Uring::Manager>(*this);
} catch (...) {
fprintf(stderr, "Failed to initialize io_uring: ");
PrintException(std::current_exception());
}
}
return uring.get();
}
#endif
void
2018-01-29 21:46:07 +01:00
EventLoop::Break() noexcept
{
if (quit.exchange(true))
return;
#ifdef HAVE_THREADED_EVENT_LOOP
wake_fd.Write();
#endif
}
bool
EventLoop::AbandonFD(int _fd) noexcept
{
#ifdef HAVE_THREADED_EVENT_LOOP
assert(!IsAlive() || IsInside());
#endif
return poll_group.Abandon(_fd);
}
bool
EventLoop::RemoveFD(int _fd) noexcept
{
#ifdef HAVE_THREADED_EVENT_LOOP
assert(!IsAlive() || IsInside());
#endif
return poll_group.Remove(_fd);
}
void
EventLoop::AddIdle(IdleEvent &i) noexcept
{
assert(IsInside());
idle.push_back(i);
again = true;
}
void
EventLoop::RemoveIdle(IdleEvent &i) noexcept
{
assert(IsInside());
idle.erase(idle.iterator_to(i));
}
void
EventLoop::AddTimer(TimerEvent &t, Event::Duration d) noexcept
{
assert(IsInside());
t.due = now + d;
timers.insert(t);
again = true;
}
inline Event::Duration
EventLoop::HandleTimers() noexcept
{
Event::Duration timeout;
while (!quit) {
auto i = timers.begin();
if (i == timers.end())
break;
TimerEvent &t = *i;
timeout = t.due - now;
if (timeout > timeout.zero())
return timeout;
timers.erase(i);
t.Run();
}
return Event::Duration(-1);
}
2016-12-27 23:06:34 +01:00
/**
* Convert the given timeout specification to a milliseconds integer,
* to be used by functions like poll() and epoll_wait(). Any negative
* value (= never times out) is translated to the magic value -1.
*/
static constexpr int
ExportTimeoutMS(Event::Duration timeout)
2016-12-27 23:06:34 +01:00
{
return timeout >= timeout.zero()
/* round up (+1) to avoid unnecessary wakeups */
? int(std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count()) + 1
2016-12-27 23:06:34 +01:00
: -1;
}
void
2018-01-29 21:46:07 +01:00
EventLoop::Run() noexcept
{
#ifdef HAVE_THREADED_EVENT_LOOP
if (thread.IsNull())
thread = ThreadId::GetCurrent();
#endif
assert(IsInside());
assert(!quit);
#ifdef HAVE_THREADED_EVENT_LOOP
assert(alive);
assert(busy);
wake_event.Schedule(SocketEvent::READ);
#endif
2020-05-05 15:18:02 +02:00
#ifdef HAVE_URING
AtScopeExit(this) {
2020-05-05 15:18:02 +02:00
/* make sure that the Uring::Manager gets destructed
from within the EventThread, or else its
destruction in another thread will cause assertion
failures */
uring.reset();
uring_initialized = false;
};
2020-05-05 15:18:02 +02:00
#endif
#ifdef HAVE_THREADED_EVENT_LOOP
AtScopeExit(this) {
wake_event.Cancel();
2018-01-29 22:52:13 +01:00
};
#endif
do {
2016-12-27 23:06:34 +01:00
now = std::chrono::steady_clock::now();
again = false;
/* invoke timers */
const auto timeout = HandleTimers();
if (quit)
break;
/* invoke idle */
while (!idle.empty()) {
IdleEvent &m = idle.front();
idle.pop_front();
m.Run();
if (quit)
return;
}
#ifdef HAVE_THREADED_EVENT_LOOP
/* try to handle DeferEvents without WakeFD
overhead */
2017-02-09 21:26:55 +01:00
{
const std::lock_guard<Mutex> lock(mutex);
HandleDeferred();
busy = false;
if (again)
/* re-evaluate timers because one of
the IdleEvents may have added a
2017-02-09 21:26:55 +01:00
new timeout */
continue;
}
#endif
/* wait for new event */
const auto poll_result =
poll_group.ReadEvents(ExportTimeoutMS(timeout));
ready_sockets.clear();
for (size_t i = 0; i < poll_result.GetSize(); ++i) {
auto &s = *(SocketEvent *)poll_result.GetObject(i);
s.SetReadyFlags(poll_result.GetEvents(i));
ready_sockets.push_back(s);
}
2016-12-27 23:06:34 +01:00
now = std::chrono::steady_clock::now();
#ifdef HAVE_THREADED_EVENT_LOOP
2017-02-09 21:26:55 +01:00
{
const std::lock_guard<Mutex> lock(mutex);
busy = true;
}
#endif
/* invoke sockets */
while (!ready_sockets.empty() && !quit) {
auto &sm = ready_sockets.front();
ready_sockets.pop_front();
sm.Dispatch();
}
} while (!quit);
#ifdef HAVE_THREADED_EVENT_LOOP
#ifndef NDEBUG
assert(thread.IsInside());
#endif
#endif
}
#ifdef HAVE_THREADED_EVENT_LOOP
void
EventLoop::AddDeferred(DeferEvent &d) noexcept
{
2017-02-09 21:26:55 +01:00
bool must_wake;
{
const std::lock_guard<Mutex> lock(mutex);
if (d.IsPending())
2017-02-09 21:26:55 +01:00
return;
2017-02-09 21:26:55 +01:00
/* we don't need to wake up the EventLoop if another
DeferEvent has already done it */
2017-02-09 21:26:55 +01:00
must_wake = !busy && deferred.empty();
deferred.push_back(d);
2017-02-09 21:26:55 +01:00
again = true;
}
if (must_wake)
wake_fd.Write();
}
void
EventLoop::RemoveDeferred(DeferEvent &d) noexcept
{
const std::lock_guard<Mutex> protect(mutex);
if (d.IsPending())
deferred.erase(deferred.iterator_to(d));
}
void
2018-01-29 21:46:07 +01:00
EventLoop::HandleDeferred() noexcept
{
while (!deferred.empty() && !quit) {
auto &m = deferred.front();
assert(m.IsPending());
deferred.pop_front();
2017-02-09 21:26:55 +01:00
const ScopeUnlock unlock(mutex);
m.RunDeferred();
}
}
void
EventLoop::OnSocketReady([[maybe_unused]] unsigned flags) noexcept
{
assert(IsInside());
wake_fd.Read();
2017-02-09 21:26:55 +01:00
const std::lock_guard<Mutex> lock(mutex);
HandleDeferred();
}
#endif