diff options
Diffstat (limited to 'src/event')
27 files changed, 981 insertions, 774 deletions
diff --git a/src/event/BufferedSocket.cxx b/src/event/BufferedSocket.cxx index 92e350e85..0f71bd941 100644 --- a/src/event/BufferedSocket.cxx +++ b/src/event/BufferedSocket.cxx @@ -22,6 +22,9 @@ #include "system/SocketError.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" +#include "Compiler.h" + +#include <algorithm> BufferedSocket::ssize_t BufferedSocket::DirectRead(void *data, size_t length) diff --git a/src/event/BufferedSocket.hxx b/src/event/BufferedSocket.hxx index db920f981..c31d7953e 100644 --- a/src/event/BufferedSocket.hxx +++ b/src/event/BufferedSocket.hxx @@ -23,13 +23,12 @@ #include "check.h" #include "SocketMonitor.hxx" #include "util/FifoBuffer.hxx" -#include "Compiler.h" #include <assert.h> #include <stdint.h> -struct fifo_buffer; class Error; +class EventLoop; /** * A #SocketMonitor specialization that adds an input buffer. diff --git a/src/event/Call.cxx b/src/event/Call.cxx index ab1d5ffbd..bc6106bf7 100644 --- a/src/event/Call.cxx +++ b/src/event/Call.cxx @@ -28,9 +28,7 @@ #include <assert.h> class BlockingCallMonitor final -#ifndef USE_EPOLL : DeferredMonitor -#endif { const std::function<void()> f; @@ -40,24 +38,13 @@ class BlockingCallMonitor final bool done; public: -#ifdef USE_EPOLL - BlockingCallMonitor(EventLoop &loop, std::function<void()> &&_f) - :f(std::move(_f)), done(false) { - loop.AddCall([this](){ - this->DoRun(); - }); - } -#else BlockingCallMonitor(EventLoop &_loop, std::function<void()> &&_f) :DeferredMonitor(_loop), f(std::move(_f)), done(false) {} -#endif void Run() { -#ifndef USE_EPOLL assert(!done); Schedule(); -#endif mutex.lock(); while (!done) @@ -65,16 +52,8 @@ public: mutex.unlock(); } -#ifndef USE_EPOLL private: virtual void RunDeferred() override { - DoRun(); - } - -#else -public: -#endif - void DoRun() { assert(!done); f(); diff --git a/src/event/DeferredMonitor.cxx b/src/event/DeferredMonitor.cxx index 4ffffaa89..ef7a98911 100644 --- a/src/event/DeferredMonitor.cxx +++ b/src/event/DeferredMonitor.cxx @@ -24,58 +24,11 @@ void DeferredMonitor::Cancel() { -#ifdef USE_EPOLL - pending = false; -#else - const auto id = source_id.exchange(0); - if (id != 0) - g_source_remove(id); -#endif + loop.RemoveDeferred(*this); } void DeferredMonitor::Schedule() { -#ifdef USE_EPOLL - if (!pending.exchange(true)) - fd.Write(); -#else - const unsigned id = loop.AddIdle(Callback, this); - const auto old_id = source_id.exchange(id); - if (old_id != 0) - g_source_remove(old_id); -#endif + loop.AddDeferred(*this); } - -#ifdef USE_EPOLL - -bool -DeferredMonitor::OnSocketReady(unsigned) -{ - fd.Read(); - - if (pending.exchange(false)) - RunDeferred(); - - return true; -} - -#else - -void -DeferredMonitor::Run() -{ - const auto id = source_id.exchange(0); - if (id != 0) - RunDeferred(); -} - -gboolean -DeferredMonitor::Callback(gpointer data) -{ - DeferredMonitor &monitor = *(DeferredMonitor *)data; - monitor.Run(); - return false; -} - -#endif diff --git a/src/event/DeferredMonitor.hxx b/src/event/DeferredMonitor.hxx index 2380fb66f..293fc5ea2 100644 --- a/src/event/DeferredMonitor.hxx +++ b/src/event/DeferredMonitor.hxx @@ -23,61 +23,31 @@ #include "check.h" #include "Compiler.h" -#ifdef USE_EPOLL -#include "SocketMonitor.hxx" -#include "WakeFD.hxx" -#else -#include <glib.h> -#endif - #include <atomic> class EventLoop; /** * Defer execution of an event into an #EventLoop. + * + * This class is thread-safe. */ -class DeferredMonitor -#ifdef USE_EPOLL - : private SocketMonitor -#endif -{ -#ifdef USE_EPOLL - std::atomic_bool pending; - WakeFD fd; -#else +class DeferredMonitor { EventLoop &loop; - std::atomic<guint> source_id; -#endif + friend class EventLoop; + bool pending; public: -#ifdef USE_EPOLL DeferredMonitor(EventLoop &_loop) - :SocketMonitor(_loop), pending(false) { - SocketMonitor::Open(fd.Get()); - SocketMonitor::Schedule(SocketMonitor::READ); - } -#else - DeferredMonitor(EventLoop &_loop) - :loop(_loop), source_id(0) {} -#endif + :loop(_loop), pending(false) {} ~DeferredMonitor() { -#ifdef USE_EPOLL - /* avoid closing the WakeFD twice */ - SocketMonitor::Steal(); -#else Cancel(); -#endif } EventLoop &GetEventLoop() { -#ifdef USE_EPOLL - return SocketMonitor::GetEventLoop(); -#else return loop; -#endif } void Schedule(); @@ -85,14 +55,6 @@ public: protected: virtual void RunDeferred() = 0; - -private: -#ifdef USE_EPOLL - virtual bool OnSocketReady(unsigned flags) override final; -#else - void Run(); - static gboolean Callback(gpointer data); -#endif }; #endif /* MAIN_NOTIFY_H */ diff --git a/src/event/FullyBufferedSocket.cxx b/src/event/FullyBufferedSocket.cxx index 8b57b1308..375132e34 100644 --- a/src/event/FullyBufferedSocket.cxx +++ b/src/event/FullyBufferedSocket.cxx @@ -20,9 +20,9 @@ #include "config.h" #include "FullyBufferedSocket.hxx" #include "system/SocketError.hxx" -#include "util/fifo_buffer.h" #include "util/Error.hxx" #include "util/Domain.hxx" +#include "Compiler.h" #include <assert.h> #include <stdint.h> @@ -59,15 +59,14 @@ FullyBufferedSocket::Flush() { assert(IsDefined()); - size_t length; - const void *data = output.Read(&length); - if (data == nullptr) { + const auto data = output.Read(); + if (data.IsEmpty()) { IdleMonitor::Cancel(); CancelWrite(); return true; } - auto nbytes = DirectWrite(data, length); + auto nbytes = DirectWrite(data.data, data.size); if (gcc_unlikely(nbytes <= 0)) return nbytes == 0; diff --git a/src/event/FullyBufferedSocket.hxx b/src/event/FullyBufferedSocket.hxx index c50bb5f61..083fab15e 100644 --- a/src/event/FullyBufferedSocket.hxx +++ b/src/event/FullyBufferedSocket.hxx @@ -24,7 +24,6 @@ #include "BufferedSocket.hxx" #include "IdleMonitor.hxx" #include "util/PeakBuffer.hxx" -#include "Compiler.h" /** * A #BufferedSocket specialization that adds an output buffer. diff --git a/src/event/IdleMonitor.cxx b/src/event/IdleMonitor.cxx index c99c66b26..dd8d64f94 100644 --- a/src/event/IdleMonitor.cxx +++ b/src/event/IdleMonitor.cxx @@ -21,38 +21,31 @@ #include "IdleMonitor.hxx" #include "Loop.hxx" +#include <assert.h> + void IdleMonitor::Cancel() { - assert(loop.IsInside()); + assert(loop.IsInsideOrNull()); if (!IsActive()) return; -#ifdef USE_EPOLL active = false; loop.RemoveIdle(*this); -#else - g_source_remove(source_id); - source_id = 0; -#endif } void IdleMonitor::Schedule() { - assert(loop.IsInside()); + assert(loop.IsInsideOrVirgin()); if (IsActive()) /* already scheduled */ return; -#ifdef USE_EPOLL active = true; loop.AddIdle(*this); -#else - source_id = loop.AddIdle(Callback, this); -#endif } void @@ -60,25 +53,8 @@ IdleMonitor::Run() { assert(loop.IsInside()); -#ifdef USE_EPOLL assert(active); active = false; -#else - assert(source_id != 0); - source_id = 0; -#endif OnIdle(); } - -#ifndef USE_EPOLL - -gboolean -IdleMonitor::Callback(gpointer data) -{ - IdleMonitor &monitor = *(IdleMonitor *)data; - monitor.Run(); - return false; -} - -#endif diff --git a/src/event/IdleMonitor.hxx b/src/event/IdleMonitor.hxx index c8e79eb1d..dac7ab5b0 100644 --- a/src/event/IdleMonitor.hxx +++ b/src/event/IdleMonitor.hxx @@ -22,41 +22,35 @@ #include "check.h" -#ifndef USE_EPOLL -#include <glib.h> -#endif - class EventLoop; /** * An event that runs when the EventLoop has become idle, before * waiting for more events. This class is not thread-safe; all * methods must be run from EventLoop's thread. + * + * This class is not thread-safe, all methods must be called from the + * thread that runs the #EventLoop, except where explicitly documented + * as thread-safe. */ class IdleMonitor { -#ifdef USE_EPOLL friend class EventLoop; -#endif EventLoop &loop; -#ifdef USE_EPOLL bool active; -#else - guint source_id; -#endif public: -#ifdef USE_EPOLL IdleMonitor(EventLoop &_loop) :loop(_loop), active(false) {} -#else - IdleMonitor(EventLoop &_loop) - :loop(_loop), source_id(0) {} -#endif ~IdleMonitor() { - Cancel(); +#ifndef NDEBUG + /* this check is redundant, it is only here to avoid + the assertion in Cancel() */ + if (IsActive()) +#endif + Cancel(); } EventLoop &GetEventLoop() const { @@ -64,11 +58,7 @@ public: } bool IsActive() const { -#ifdef USE_EPOLL return active; -#else - return source_id != 0; -#endif } void Schedule(); @@ -79,9 +69,6 @@ protected: private: void Run(); -#ifndef USE_EPOLL - static gboolean Callback(gpointer data); -#endif }; #endif /* MAIN_NOTIFY_H */ diff --git a/src/event/Loop.cxx b/src/event/Loop.cxx index 5aa24aea2..9c76c422c 100644 --- a/src/event/Loop.cxx +++ b/src/event/Loop.cxx @@ -20,20 +20,21 @@ #include "config.h" #include "Loop.hxx" -#ifdef USE_EPOLL - #include "system/Clock.hxx" #include "TimeoutMonitor.hxx" #include "SocketMonitor.hxx" #include "IdleMonitor.hxx" +#include "DeferredMonitor.hxx" #include <algorithm> EventLoop::EventLoop(Default) :SocketMonitor(*this), now_ms(::MonotonicClockMS()), - quit(false), - n_events(0), + quit(false), busy(true), +#ifndef NDEBUG + virgin(true), +#endif thread(ThreadId::Null()) { SocketMonitor::Open(wake_fd.Get()); @@ -45,45 +46,51 @@ EventLoop::~EventLoop() assert(idle.empty()); assert(timers.empty()); - /* avoid closing the WakeFD twice */ - SocketMonitor::Steal(); + /* this is necessary to get a well-defined destruction + order */ + SocketMonitor::Cancel(); } void EventLoop::Break() { - if (IsInside()) - quit = true; - else - AddCall([this]() { Break(); }); + quit = true; + wake_fd.Write(); } -void -EventLoop::Abandon(SocketMonitor &m) +bool +EventLoop::Abandon(int _fd, SocketMonitor &m) { - for (unsigned i = 0, n = n_events; i < n; ++i) - if (events[i].data.ptr == &m) - events[i].events = 0; + assert(IsInsideOrVirgin()); + + poll_result.Clear(&m); + return poll_group.Abandon(_fd); } bool EventLoop::RemoveFD(int _fd, SocketMonitor &m) { - Abandon(m); - return epoll.Remove(_fd); + assert(IsInsideOrNull()); + + poll_result.Clear(&m); + return poll_group.Remove(_fd); } void EventLoop::AddIdle(IdleMonitor &i) { + assert(IsInsideOrVirgin()); assert(std::find(idle.begin(), idle.end(), &i) == idle.end()); idle.push_back(&i); + again = true; } void EventLoop::RemoveIdle(IdleMonitor &i) { + assert(IsInsideOrVirgin()); + auto it = std::find(idle.begin(), idle.end(), &i); assert(it != idle.end()); @@ -93,12 +100,19 @@ EventLoop::RemoveIdle(IdleMonitor &i) void EventLoop::AddTimer(TimeoutMonitor &t, unsigned ms) { + /* can't use IsInsideOrVirgin() here because libavahi-client + modifies the timeout during avahi_client_free() */ + assert(IsInsideOrNull()); + timers.insert(TimerRecord(t, now_ms + ms)); + again = true; } void EventLoop::CancelTimer(TimeoutMonitor &t) { + assert(IsInsideOrNull()); + for (auto i = timers.begin(), end = timers.end(); i != end; ++i) { if (&i->timer == &t) { timers.erase(i); @@ -107,19 +121,24 @@ EventLoop::CancelTimer(TimeoutMonitor &t) } } -#endif - void EventLoop::Run() { assert(thread.IsNull()); + assert(virgin); + +#ifndef NDEBUG + virgin = false; +#endif + thread = ThreadId::GetCurrent(); -#ifdef USE_EPOLL assert(!quit); + assert(busy); do { now_ms = ::MonotonicClockMS(); + again = false; /* invoke timers */ @@ -146,7 +165,6 @@ EventLoop::Run() /* invoke idle */ - const bool idle_empty = idle.empty(); while (!idle.empty()) { IdleMonitor &m = *idle.front(); idle.pop_front(); @@ -156,7 +174,14 @@ EventLoop::Run() return; } - if (!idle_empty) + /* try to handle DeferredMonitors without WakeFD + overhead */ + mutex.lock(); + HandleDeferred(); + busy = false; + mutex.unlock(); + + if (again) /* re-evaluate timers because one of the IdleMonitors may have added a new timeout */ @@ -164,101 +189,107 @@ EventLoop::Run() /* wait for new event */ - const int n = epoll.Wait(events, MAX_EVENTS, timeout_ms); - n_events = std::max(n, 0); + poll_group.ReadEvents(poll_result, timeout_ms); now_ms = ::MonotonicClockMS(); - assert(!quit); + mutex.lock(); + busy = true; + mutex.unlock(); /* invoke sockets */ - - for (int i = 0; i < n; ++i) { - const auto &e = events[i]; - - if (e.events != 0) { - SocketMonitor &m = *(SocketMonitor *)e.data.ptr; - m.Dispatch(e.events); - + for (int i = 0; i < poll_result.GetSize(); ++i) { + auto events = poll_result.GetEvents(i); + if (events != 0) { if (quit) break; + + auto m = (SocketMonitor *)poll_result.GetObject(i); + m->Dispatch(events); } } - n_events = 0; + poll_result.Reset(); + } while (!quit); -#else - g_main_loop_run(loop); -#endif +#ifndef NDEBUG + assert(busy); assert(thread.IsInside()); + thread = ThreadId::Null(); +#endif } -#ifdef USE_EPOLL - void -EventLoop::AddCall(std::function<void()> &&f) +EventLoop::AddDeferred(DeferredMonitor &d) { mutex.lock(); - calls.push_back(f); + if (d.pending) { + mutex.unlock(); + return; + } + + assert(std::find(deferred.begin(), + deferred.end(), &d) == deferred.end()); + + /* we don't need to wake up the EventLoop if another + DeferredMonitor has already done it */ + const bool must_wake = !busy && deferred.empty(); + + d.pending = true; + deferred.push_back(&d); + again = true; mutex.unlock(); - wake_fd.Write(); + if (must_wake) + wake_fd.Write(); } -bool -EventLoop::OnSocketReady(gcc_unused unsigned flags) +void +EventLoop::RemoveDeferred(DeferredMonitor &d) { - assert(!quit); + const ScopeLock protect(mutex); - wake_fd.Read(); + if (!d.pending) { + assert(std::find(deferred.begin(), + deferred.end(), &d) == deferred.end()); + return; + } - mutex.lock(); + d.pending = false; - while (!calls.empty() && !quit) { - auto f = std::move(calls.front()); - calls.pop_front(); + auto i = std::find(deferred.begin(), deferred.end(), &d); + assert(i != deferred.end()); + + deferred.erase(i); +} + +void +EventLoop::HandleDeferred() +{ + while (!deferred.empty() && !quit) { + DeferredMonitor &m = *deferred.front(); + assert(m.pending); + + deferred.pop_front(); + m.pending = false; mutex.unlock(); - f(); + m.RunDeferred(); mutex.lock(); } - - mutex.unlock(); - - return true; } -#else - -guint -EventLoop::AddIdle(GSourceFunc function, gpointer data) +bool +EventLoop::OnSocketReady(gcc_unused unsigned flags) { - GSource *source = g_idle_source_new(); - g_source_set_callback(source, function, data, nullptr); - guint id = g_source_attach(source, GetContext()); - g_source_unref(source); - return id; -} + assert(IsInside()); -GSource * -EventLoop::AddTimeout(guint interval_ms, - GSourceFunc function, gpointer data) -{ - GSource *source = g_timeout_source_new(interval_ms); - g_source_set_callback(source, function, data, nullptr); - g_source_attach(source, GetContext()); - return source; -} + wake_fd.Read(); -GSource * -EventLoop::AddTimeoutSeconds(guint interval_s, - GSourceFunc function, gpointer data) -{ - GSource *source = g_timeout_source_new_seconds(interval_s); - g_source_set_callback(source, function, data, nullptr); - g_source_attach(source, GetContext()); - return source; -} + mutex.lock(); + HandleDeferred(); + mutex.unlock(); -#endif + return true; +} diff --git a/src/event/Loop.hxx b/src/event/Loop.hxx index 62e733747..d03259fb0 100644 --- a/src/event/Loop.hxx +++ b/src/event/Loop.hxx @@ -24,33 +24,32 @@ #include "thread/Id.hxx" #include "Compiler.h" -#ifdef USE_EPOLL -#include "system/EPollFD.hxx" +#include "PollGroup.hxx" #include "thread/Mutex.hxx" #include "WakeFD.hxx" #include "SocketMonitor.hxx" -#include <functional> #include <list> #include <set> -#else -#include <glib.h> -#endif -#ifdef USE_EPOLL class TimeoutMonitor; class IdleMonitor; +class DeferredMonitor; class SocketMonitor; -#endif #include <assert.h> -class EventLoop final -#ifdef USE_EPOLL - : private SocketMonitor -#endif +/** + * An event loop that polls for events on file/socket descriptors. + * + * This class is not thread-safe, all methods must be called from the + * thread that runs it, except where explicitly documented as + * thread-safe. + * + * @see SocketMonitor, MultiSocketMonitor, TimeoutMonitor, IdleMonitor + */ +class EventLoop final : SocketMonitor { -#ifdef USE_EPOLL struct TimerRecord { /** * Projected monotonic_clock_ms() value when this @@ -73,52 +72,80 @@ class EventLoop final } }; - EPollFD epoll; - WakeFD wake_fd; std::multiset<TimerRecord> timers; std::list<IdleMonitor *> idle; Mutex mutex; - std::list<std::function<void()>> calls; + std::list<DeferredMonitor *> deferred; unsigned now_ms; bool quit; - static constexpr unsigned MAX_EVENTS = 16; - unsigned n_events; - epoll_event events[MAX_EVENTS]; -#else - GMainContext *context; - GMainLoop *loop; + /** + * True when the object has been modified and another check is + * necessary before going to sleep via PollGroup::ReadEvents(). + */ + bool again; + + /** + * True when handling callbacks, false when waiting for I/O or + * timeout. + * + * Protected with #mutex. + */ + bool busy; + +#ifndef NDEBUG + /** + * True if Run() was never called. This is used for assert() + * calls. + */ + bool virgin; #endif + PollGroup poll_group; + PollResult poll_result; + /** * A reference to the thread that is currently inside Run(). */ ThreadId thread; public: -#ifdef USE_EPOLL struct Default {}; EventLoop(Default dummy=Default()); ~EventLoop(); + /** + * A caching wrapper for MonotonicClockMS(). + */ unsigned GetTimeMS() const { + assert(IsInside()); + return now_ms; } + /** + * Stop execution of this #EventLoop at the next chance. This + * method is thread-safe and non-blocking: after returning, it + * is not guaranteed that the EventLoop has really stopped. + */ void Break(); bool AddFD(int _fd, unsigned flags, SocketMonitor &m) { - return epoll.Add(_fd, flags, &m); + assert(thread.IsNull() || thread.IsInside()); + + return poll_group.Add(_fd, flags, &m); } bool ModifyFD(int _fd, unsigned flags, SocketMonitor &m) { - return epoll.Modify(_fd, flags, &m); + assert(IsInside()); + + return poll_group.Modify(_fd, flags, &m); } /** @@ -126,7 +153,7 @@ public: * has been closed. This is like RemoveFD(), but does not * attempt to use #EPOLL_CTL_DEL. */ - void Abandon(SocketMonitor &m); + bool Abandon(int fd, SocketMonitor &m); bool RemoveFD(int fd, SocketMonitor &m); @@ -136,53 +163,38 @@ public: void AddTimer(TimeoutMonitor &t, unsigned ms); void CancelTimer(TimeoutMonitor &t); - void AddCall(std::function<void()> &&f); + /** + * Schedule a call to DeferredMonitor::RunDeferred(). + * + * This method is thread-safe. + */ + void AddDeferred(DeferredMonitor &d); + + /** + * Cancel a pending call to DeferredMonitor::RunDeferred(). + * However after returning, the call may still be running. + * + * This method is thread-safe. + */ + void RemoveDeferred(DeferredMonitor &d); + /** + * The main function of this class. It will loop until + * Break() gets called. Can be called only once. + */ void Run(); private: + /** + * Invoke all pending DeferredMonitors. + * + * Caller must lock the mutex. + */ + void HandleDeferred(); + virtual bool OnSocketReady(unsigned flags) override; public: -#else - EventLoop() - :context(g_main_context_new()), - loop(g_main_loop_new(context, false)), - thread(ThreadId::Null()) {} - - struct Default {}; - EventLoop(gcc_unused Default _dummy) - :context(g_main_context_ref(g_main_context_default())), - loop(g_main_loop_new(context, false)), - thread(ThreadId::Null()) {} - - ~EventLoop() { - g_main_loop_unref(loop); - g_main_context_unref(context); - } - - GMainContext *GetContext() { - return context; - } - - void WakeUp() { - g_main_context_wakeup(context); - } - - void Break() { - g_main_loop_quit(loop); - } - - void Run(); - - guint AddIdle(GSourceFunc function, gpointer data); - - GSource *AddTimeout(guint interval_ms, - GSourceFunc function, gpointer data); - - GSource *AddTimeoutSeconds(guint interval_s, - GSourceFunc function, gpointer data); -#endif /** * Are we currently running inside this EventLoop's thread? @@ -193,6 +205,20 @@ public: return thread.IsInside(); } + +#ifndef NDEBUG + gcc_pure + bool IsInsideOrVirgin() const { + return virgin || IsInside(); + } +#endif + +#ifndef NDEBUG + gcc_pure + bool IsInsideOrNull() const { + return thread.IsNull() || thread.IsInside(); + } +#endif }; #endif /* MAIN_NOTIFY_H */ diff --git a/src/event/MultiSocketMonitor.cxx b/src/event/MultiSocketMonitor.cxx index bd1aa6fef..5ef8b98af 100644 --- a/src/event/MultiSocketMonitor.cxx +++ b/src/event/MultiSocketMonitor.cxx @@ -20,12 +20,12 @@ #include "config.h" #include "MultiSocketMonitor.hxx" #include "Loop.hxx" -#include "system/fd_util.h" -#include "Compiler.h" -#include <assert.h> +#include <algorithm> -#ifdef USE_EPOLL +#ifndef WIN32 +#include <poll.h> +#endif MultiSocketMonitor::MultiSocketMonitor(EventLoop &_loop) :IdleMonitor(_loop), TimeoutMonitor(_loop), ready(false) { @@ -37,6 +37,40 @@ MultiSocketMonitor::~MultiSocketMonitor() } void +MultiSocketMonitor::ClearSocketList() +{ + assert(GetEventLoop().IsInsideOrNull()); + + fds.clear(); +} + +#ifndef WIN32 + +void +MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n) +{ + pollfd *const end = pfds + n; + + UpdateSocketList([pfds, end](int fd) -> unsigned { + auto i = std::find_if(pfds, end, [fd](const struct pollfd &pfd){ + return pfd.fd == fd; + }); + if (i == end) + return 0; + + auto events = i->events; + i->events = 0; + return events; + }); + + for (auto i = pfds; i != end; ++i) + if (i->events != 0) + AddSocket(i->fd, i->events); +} + +#endif + +void MultiSocketMonitor::Prepare() { int timeout_ms = PrepareSockets(); @@ -64,100 +98,3 @@ MultiSocketMonitor::OnIdle() Prepare(); } } - -#else - -/** - * The vtable for our GSource implementation. Unfortunately, we - * cannot declare it "const", because g_source_new() takes a non-const - * pointer, for whatever reason. - */ -static GSourceFuncs multi_socket_monitor_source_funcs = { - MultiSocketMonitor::Prepare, - MultiSocketMonitor::Check, - MultiSocketMonitor::Dispatch, - nullptr, - nullptr, - nullptr, -}; - -MultiSocketMonitor::MultiSocketMonitor(EventLoop &_loop) - :loop(_loop), - source((Source *)g_source_new(&multi_socket_monitor_source_funcs, - sizeof(*source))), - absolute_timeout_us(-1) { - source->monitor = this; - - g_source_attach(&source->base, loop.GetContext()); -} - -MultiSocketMonitor::~MultiSocketMonitor() -{ - g_source_destroy(&source->base); - g_source_unref(&source->base); - source = nullptr; -} - -bool -MultiSocketMonitor::Prepare(gint *timeout_r) -{ - int timeout_ms = *timeout_r = PrepareSockets(); - absolute_timeout_us = timeout_ms < 0 - ? uint64_t(-1) - : GetTime() + uint64_t(timeout_ms) * 1000; - - return false; -} - -bool -MultiSocketMonitor::Check() const -{ - if (GetTime() >= absolute_timeout_us) - return true; - - for (const auto &i : fds) - if (i.GetReturnedEvents() != 0) - return true; - - return false; -} - -/* - * GSource methods - * - */ - -gboolean -MultiSocketMonitor::Prepare(GSource *_source, gint *timeout_r) -{ - Source &source = *(Source *)_source; - MultiSocketMonitor &monitor = *source.monitor; - assert(_source == &monitor.source->base); - - return monitor.Prepare(timeout_r); -} - -gboolean -MultiSocketMonitor::Check(GSource *_source) -{ - const Source &source = *(const Source *)_source; - const MultiSocketMonitor &monitor = *source.monitor; - assert(_source == &monitor.source->base); - - return monitor.Check(); -} - -gboolean -MultiSocketMonitor::Dispatch(GSource *_source, - gcc_unused GSourceFunc callback, - gcc_unused gpointer user_data) -{ - Source &source = *(Source *)_source; - MultiSocketMonitor &monitor = *source.monitor; - assert(_source == &monitor.source->base); - - monitor.Dispatch(); - return true; -} - -#endif diff --git a/src/event/MultiSocketMonitor.hxx b/src/event/MultiSocketMonitor.hxx index 8ee81a508..150f7baad 100644 --- a/src/event/MultiSocketMonitor.hxx +++ b/src/event/MultiSocketMonitor.hxx @@ -21,40 +21,38 @@ #define MPD_MULTI_SOCKET_MONITOR_HXX #include "check.h" -#include "Compiler.h" - -#ifdef USE_EPOLL #include "IdleMonitor.hxx" #include "TimeoutMonitor.hxx" #include "SocketMonitor.hxx" -#else -#include <glib.h> -#endif +#include "Compiler.h" #include <forward_list> +#include <iterator> #include <assert.h> -#include <stdint.h> #ifdef WIN32 -/* ERRORis a WIN32 macro that poisons our namespace; this is a - kludge to allow us to use it anyway */ +/* ERROR is a WIN32 macro that poisons our namespace; this is a kludge + to allow us to use it anyway */ #ifdef ERROR #undef ERROR #endif #endif +#ifndef WIN32 +struct pollfd; +#endif + class EventLoop; /** - * Monitor multiple sockets. + * Similar to #SocketMonitor, but monitors multiple sockets. To use + * it, implement the methods PrepareSockets() and DispatchSockets(). + * In PrepareSockets(), use UpdateSocketList() and AddSocket(). + * DispatchSockets() will be called if at least one socket is ready. */ -class MultiSocketMonitor -#ifdef USE_EPOLL - : private IdleMonitor, private TimeoutMonitor -#endif +class MultiSocketMonitor : IdleMonitor, TimeoutMonitor { -#ifdef USE_EPOLL class SingleFD final : public SocketMonitor { MultiSocketMonitor &multi; @@ -99,93 +97,45 @@ class MultiSocketMonitor friend class SingleFD; bool ready, refresh; -#else - struct Source { - GSource base; - - MultiSocketMonitor *monitor; - }; - - struct SingleFD { - GPollFD pfd; - - constexpr SingleFD(gcc_unused MultiSocketMonitor &m, - int fd, unsigned events) - :pfd{fd, gushort(events), 0} {} - - constexpr int GetFD() const { - return pfd.fd; - } - - constexpr unsigned GetEvents() const { - return pfd.events; - } - - constexpr unsigned GetReturnedEvents() const { - return pfd.revents; - } - - void SetEvents(unsigned _events) { - pfd.events = _events; - } - }; - - EventLoop &loop; - Source *source; - uint64_t absolute_timeout_us; -#endif std::forward_list<SingleFD> fds; public: -#ifdef USE_EPOLL static constexpr unsigned READ = SocketMonitor::READ; static constexpr unsigned WRITE = SocketMonitor::WRITE; static constexpr unsigned ERROR = SocketMonitor::ERROR; static constexpr unsigned HANGUP = SocketMonitor::HANGUP; -#else - static constexpr unsigned READ = G_IO_IN; - static constexpr unsigned WRITE = G_IO_OUT; - static constexpr unsigned ERROR = G_IO_ERR; - static constexpr unsigned HANGUP = G_IO_HUP; -#endif MultiSocketMonitor(EventLoop &_loop); ~MultiSocketMonitor(); -#ifdef USE_EPOLL using IdleMonitor::GetEventLoop; -#else - EventLoop &GetEventLoop() { - return loop; - } -#endif public: -#ifndef USE_EPOLL - gcc_pure - uint64_t GetTime() const { - return g_source_get_time(&source->base); - } -#endif - + /** + * Invalidate the socket list. A call to PrepareSockets() is + * scheduled which will then update the list. + */ void InvalidateSockets() { -#ifdef USE_EPOLL refresh = true; IdleMonitor::Schedule(); -#else - /* no-op because GLib always calls the GSource's - "prepare" method before each poll() anyway */ -#endif } void AddSocket(int fd, unsigned events) { fds.emplace_front(*this, fd, events); -#ifndef USE_EPOLL - g_source_add_poll(&source->base, &fds.front().pfd); -#endif } + /** + * Remove all sockets. + */ + void ClearSocketList(); + + /** + * Update the known sockets by invoking the given function for + * each one; its return value is the events bit mask. A + * return value of 0 means the socket will be removed from the + * list. + */ template<typename E> void UpdateSocketList(E &&e) { for (auto prev = fds.before_begin(), end = fds.end(), @@ -198,16 +148,19 @@ public: i->SetEvents(events); prev = i; } else { -#ifdef USE_EPOLL - i->Steal(); -#else - g_source_remove_poll(&source->base, &i->pfd); -#endif fds.erase_after(prev); } } } +#ifndef WIN32 + /** + * Replace the socket list with the given file descriptors. + * The given pollfd array will be modified by this method. + */ + void ReplaceSocketList(pollfd *pfds, unsigned n); +#endif + protected: /** * @return timeout [ms] or -1 for no timeout @@ -215,7 +168,6 @@ protected: virtual int PrepareSockets() = 0; virtual void DispatchSockets() = 0; -#ifdef USE_EPOLL private: void SetReady() { ready = true; @@ -230,23 +182,6 @@ private: } virtual void OnIdle() final; - -#else -public: - /* GSource callbacks */ - static gboolean Prepare(GSource *source, gint *timeout_r); - static gboolean Check(GSource *source); - static gboolean Dispatch(GSource *source, GSourceFunc callback, - gpointer user_data); - -private: - bool Prepare(gint *timeout_r); - bool Check() const; - - void Dispatch() { - DispatchSockets(); - } -#endif }; #endif diff --git a/src/event/PollGroup.hxx b/src/event/PollGroup.hxx new file mode 100644 index 000000000..038ffc13e --- /dev/null +++ b/src/event/PollGroup.hxx @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2013 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. + */ + +#ifndef MPD_EVENT_POLLGROUP_HXX +#define MPD_EVENT_POLLGROUP_HXX + +#ifdef USE_EPOLL +#include "PollGroupEPoll.hxx" +typedef PollResultEPoll PollResult; +typedef PollGroupEPoll PollGroup; +#endif + +#ifdef USE_WINSELECT +#include "PollGroupWinSelect.hxx" +typedef PollResultGeneric PollResult; +typedef PollGroupWinSelect PollGroup; +#endif + +#ifdef USE_POLL +#include "PollGroupPoll.hxx" +typedef PollResultGeneric PollResult; +typedef PollGroupPoll PollGroup; +#endif + +#endif diff --git a/src/event/PollGroupEPoll.hxx b/src/event/PollGroupEPoll.hxx new file mode 100644 index 000000000..21bb3a322 --- /dev/null +++ b/src/event/PollGroupEPoll.hxx @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2003-2013 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. + */ + +#ifndef MPD_EVENT_POLLGROUP_EPOLL_HXX +#define MPD_EVENT_POLLGROUP_EPOLL_HXX + +#include "check.h" + +#include "Compiler.h" +#include "system/EPollFD.hxx" + +#include <array> +#include <algorithm> + +class PollResultEPoll +{ + friend class PollGroupEPoll; + + std::array<epoll_event, 16> events; + int n_events; +public: + PollResultEPoll() : n_events(0) { } + + int GetSize() const { return n_events; } + unsigned GetEvents(int i) const { return events[i].events; } + void *GetObject(int i) const { return events[i].data.ptr; } + void Reset() { n_events = 0; } + + void Clear(void *obj) { + for (int i = 0; i < n_events; ++i) + if (events[i].data.ptr == obj) + events[i].events = 0; + } +}; + +class PollGroupEPoll +{ + EPollFD epoll; + + PollGroupEPoll(PollGroupEPoll &) = delete; + PollGroupEPoll &operator=(PollGroupEPoll &) = delete; +public: + static constexpr unsigned READ = EPOLLIN; + static constexpr unsigned WRITE = EPOLLOUT; + static constexpr unsigned ERROR = EPOLLERR; + static constexpr unsigned HANGUP = EPOLLHUP; + + PollGroupEPoll() = default; + + void ReadEvents(PollResultEPoll &result, int timeout_ms) { + int ret = epoll.Wait(result.events.data(), result.events.size(), + timeout_ms); + result.n_events = std::max(0, ret); + } + + bool Add(int fd, unsigned events, void *obj) { + return epoll.Add(fd, events, obj); + } + + bool Modify(int fd, unsigned events, void *obj) { + return epoll.Modify(fd, events, obj); + } + + bool Remove(int fd) { + return epoll.Remove(fd); + } + + bool Abandon(gcc_unused int fd) { + // Nothing to do in this implementation. + // Closed descriptors are automatically unregistered. + return true; + } +}; + +#endif diff --git a/src/event/PollGroupPoll.cxx b/src/event/PollGroupPoll.cxx new file mode 100644 index 000000000..89d09d995 --- /dev/null +++ b/src/event/PollGroupPoll.cxx @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2003-2013 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 "config.h" + +#ifdef USE_POLL + +#include "PollGroupPoll.hxx" + +#include <assert.h> + +PollGroupPoll::PollGroupPoll() { } +PollGroupPoll::~PollGroupPoll() { } + +bool PollGroupPoll::Add(int fd, unsigned events, void *obj) +{ + assert(items.find(fd) == items.end()); + + const size_t index = poll_events.size(); + poll_events.resize(index + 1); + auto &e = poll_events[index]; + e.fd = fd; + e.events = events; + e.revents = 0; + auto &item = items[fd]; + item.index = index; + item.obj = obj; + return true; +} + +bool PollGroupPoll::Modify(int fd, unsigned events, void *obj) +{ + auto item_iter = items.find(fd); + assert(item_iter != items.end()); + auto &item = item_iter->second; + item.obj = obj; + auto &e = poll_events[item.index]; + e.events = events; + e.revents &= events; + return true; +} + +bool PollGroupPoll::Remove(int fd) +{ + auto item_iter = items.find(fd); + assert(item_iter != items.end()); + auto &item = item_iter->second; + size_t index = item.index; + size_t last_index = poll_events.size() - 1; + if (index != last_index) { + std::swap(poll_events[index], poll_events[last_index]); + items[poll_events[index].fd].index = index; + } + poll_events.pop_back(); + items.erase(item_iter); + return true; +} + +void PollGroupPoll::ReadEvents(PollResultGeneric &result, int timeout_ms) +{ + int n = poll(poll_events.empty() ? nullptr : &poll_events[0], + poll_events.size(), timeout_ms); + + for (size_t i = 0; n > 0 && i < poll_events.size(); ++i) { + const auto &e = poll_events[i]; + if (e.revents != 0) { + result.Add(e.revents, items[e.fd].obj); + --n; + } + } +} + +#endif diff --git a/src/event/PollGroupPoll.hxx b/src/event/PollGroupPoll.hxx new file mode 100644 index 000000000..6c69787ac --- /dev/null +++ b/src/event/PollGroupPoll.hxx @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2003-2013 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. + */ + +#ifndef MPD_EVENT_POLLGROUP_POLL_HXX +#define MPD_EVENT_POLLGROUP_POLL_HXX + +#include "check.h" +#include "PollResultGeneric.hxx" + +#include <vector> +#include <unordered_map> + +#include <stddef.h> +#include <sys/poll.h> + +class PollGroupPoll +{ + struct Item + { + size_t index; + void *obj; + }; + + std::vector<pollfd> poll_events; + std::unordered_map<int, Item> items; + + PollGroupPoll(PollGroupPoll &) = delete; + PollGroupPoll &operator=(PollGroupPoll &) = delete; +public: + static constexpr unsigned READ = POLLIN; + static constexpr unsigned WRITE = POLLOUT; + static constexpr unsigned ERROR = POLLERR; + static constexpr unsigned HANGUP = POLLHUP; + + PollGroupPoll(); + ~PollGroupPoll(); + + void ReadEvents(PollResultGeneric &result, int timeout_ms); + bool Add(int fd, unsigned events, void *obj); + bool Modify(int fd, unsigned events, void *obj); + bool Remove(int fd); + bool Abandon(int fd) { + return Remove(fd); + } +}; + +#endif diff --git a/src/event/PollGroupWinSelect.cxx b/src/event/PollGroupWinSelect.cxx new file mode 100644 index 000000000..b184ff2b2 --- /dev/null +++ b/src/event/PollGroupWinSelect.cxx @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2003-2013 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 "config.h" + +#ifdef USE_WINSELECT + +#include "PollGroupWinSelect.hxx" + +constexpr int EVENT_READ = 0; +constexpr int EVENT_WRITE = 1; + +static inline bool HasEvent(unsigned events, int event_id) +{ + return (events & (1 << event_id)) != 0; +} + +PollGroupWinSelect::PollGroupWinSelect() { } +PollGroupWinSelect::~PollGroupWinSelect() { } + +bool PollGroupWinSelect::CanModify(PollGroupWinSelect::Item &item, + unsigned events, int event_id) +{ + if (item.index[event_id] < 0 && HasEvent(events, event_id)) + return !event_set[event_id].IsFull(); + return true; +} + +void PollGroupWinSelect::Modify(PollGroupWinSelect::Item &item, int fd, + unsigned events, int event_id) +{ + int index = item.index[event_id]; + auto &set = event_set[event_id]; + + if (index < 0 && HasEvent(events, event_id)) + item.index[event_id] = set.Add(fd); + else if (index >= 0 && !HasEvent(events, event_id)) { + if (index != set.Size() - 1) { + set.MoveToEnd(index); + items[set[index]].index[event_id] = index; + } + set.RemoveLast(); + item.index[event_id] = -1; + } +} + +bool PollGroupWinSelect::Add(int fd, unsigned events, void *obj) +{ + assert(items.find(fd) == items.end()); + auto &item = items[fd]; + + item.index[EVENT_READ] = -1; + item.index[EVENT_WRITE] = -1; + item.obj = obj; + item.events = 0; + + if (!CanModify(item, events, EVENT_READ)) { + items.erase(fd); + return false; + } + if (!CanModify(item, events, EVENT_WRITE)) { + items.erase(fd); + return false; + } + + Modify(item, fd, events, EVENT_READ); + Modify(item, fd, events, EVENT_WRITE); + return true; +} + +bool PollGroupWinSelect::Modify(int fd, unsigned events, void *obj) +{ + auto item_iter = items.find(fd); + assert(item_iter != items.end()); + auto &item = item_iter->second; + + if (!CanModify(item, events, EVENT_READ)) + return false; + if (!CanModify(item, events, EVENT_WRITE)) + return false; + + item.obj = obj; + Modify(item, fd, events, EVENT_READ); + Modify(item, fd, events, EVENT_WRITE); + return true; +} + +bool PollGroupWinSelect::Remove(int fd) +{ + auto item_iter = items.find(fd); + assert(item_iter != items.end()); + auto &item = item_iter->second; + + Modify(item, fd, 0, EVENT_READ); + Modify(item, fd, 0, EVENT_WRITE); + items.erase(item_iter); + return true; +} + +void PollGroupWinSelect::ReadEvents(PollResultGeneric &result, int timeout_ms) +{ + bool use_sleep = event_set[EVENT_READ].IsEmpty() && + event_set[EVENT_WRITE].IsEmpty(); + + if (use_sleep) { + Sleep(timeout_ms < 0 ? INFINITE : (DWORD) timeout_ms); + return; + } + + SocketSet read_set(event_set[EVENT_READ]); + SocketSet write_set(event_set[EVENT_WRITE]); + SocketSet except_set(event_set[EVENT_WRITE]); + + timeval tv; + if (timeout_ms >= 0) { + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + } + + int ret = select(0, + read_set.IsEmpty() ? nullptr : read_set.GetPtr(), + write_set.IsEmpty() ? nullptr : write_set.GetPtr(), + except_set.IsEmpty() ? nullptr : except_set.GetPtr(), + timeout_ms < 0 ? nullptr : &tv); + + if (ret == 0 || ret == SOCKET_ERROR) + return; + + for (int i = 0; i < read_set.Size(); ++i) + items[read_set[i]].events |= READ; + + for (int i = 0; i < write_set.Size(); ++i) + items[write_set[i]].events |= WRITE; + + for (int i = 0; i < except_set.Size(); ++i) + items[except_set[i]].events |= WRITE; + + for (auto i = items.begin(); i != items.end(); ++i) + if (i->second.events != 0) { + result.Add(i->second.events, i->second.obj); + i->second.events = 0; + } +} + +#endif diff --git a/src/event/PollGroupWinSelect.hxx b/src/event/PollGroupWinSelect.hxx new file mode 100644 index 000000000..7a6d12d8a --- /dev/null +++ b/src/event/PollGroupWinSelect.hxx @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2003-2013 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. + */ + +#ifndef MPD_EVENT_POLLGROUP_WINSELECT_HXX +#define MPD_EVENT_POLLGROUP_WINSELECT_HXX + +#include "check.h" + +#include "PollResultGeneric.hxx" + +#include <assert.h> +#include <string.h> + +#include <unordered_map> + +#include <windows.h> +#include <winsock2.h> + +#ifdef ERROR +#undef ERROR +#endif + +class SocketSet +{ + fd_set set; +public: + SocketSet() { set.fd_count = 0; } + SocketSet(SocketSet &other) { + set.fd_count = other.set.fd_count; + memcpy(set.fd_array, + other.set.fd_array, + sizeof (SOCKET) * set.fd_count); + } + + fd_set *GetPtr() { return &set; } + int Size() { return set.fd_count; } + bool IsEmpty() { return set.fd_count == 0; } + bool IsFull() { return set.fd_count == FD_SETSIZE; } + + int operator[](int index) { + assert(index >= 0 && (u_int)index < set.fd_count); + return set.fd_array[index]; + } + + int Add(int fd) { + assert(!IsFull()); + set.fd_array[set.fd_count] = fd; + return set.fd_count++; + } + + void MoveToEnd(int index) { + assert(index >= 0 && (u_int)index < set.fd_count); + std::swap(set.fd_array[index], set.fd_array[set.fd_count - 1]); + } + + void RemoveLast() { + assert(!IsEmpty()); + --set.fd_count; + } +}; + +class PollGroupWinSelect +{ + struct Item + { + int index[2]; + void *obj; + unsigned events; + }; + + SocketSet event_set[2]; + std::unordered_map<int, Item> items; + + bool CanModify(Item &item, unsigned events, int event_id); + void Modify(Item &item, int fd, unsigned events, int event_id); + + PollGroupWinSelect(PollGroupWinSelect &) = delete; + PollGroupWinSelect &operator=(PollGroupWinSelect &) = delete; +public: + static constexpr unsigned READ = 1; + static constexpr unsigned WRITE = 2; + static constexpr unsigned ERROR = 0; + static constexpr unsigned HANGUP = 0; + + PollGroupWinSelect(); + ~PollGroupWinSelect(); + + void ReadEvents(PollResultGeneric &result, int timeout_ms); + bool Add(int fd, unsigned events, void *obj); + bool Modify(int fd, unsigned events, void *obj); + bool Remove(int fd); + bool Abandon(int fd) { return Remove(fd); } +}; + +#endif diff --git a/src/event/PollResultGeneric.hxx b/src/event/PollResultGeneric.hxx new file mode 100644 index 000000000..1c2c0d00b --- /dev/null +++ b/src/event/PollResultGeneric.hxx @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2003-2013 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. + */ + +#ifndef MPD_EVENT_POLLRESULT_GENERIC_HXX +#define MPD_EVENT_POLLRESULT_GENERIC_HXX + +#include "check.h" + +#include <vector> + +class PollResultGeneric +{ + struct Item + { + unsigned events; + void *obj; + + Item() = default; + Item(unsigned _events, void *_obj) + : events(_events), obj(_obj) { } + }; + + std::vector<Item> items; +public: + int GetSize() const { return items.size(); } + unsigned GetEvents(int i) const { return items[i].events; } + void *GetObject(int i) const { return items[i].obj; } + void Reset() { items.clear(); } + + void Clear(void *obj) { + for (auto i = items.begin(); i != items.end(); ++i) + if (i->obj == obj) + i->events = 0; + } + + void Add(unsigned events, void *obj) { + items.emplace_back(events, obj); + } +}; + +#endif diff --git a/src/event/ServerSocket.cxx b/src/event/ServerSocket.cxx index 781d29181..11592b4fb 100644 --- a/src/event/ServerSocket.cxx +++ b/src/event/ServerSocket.cxx @@ -31,13 +31,13 @@ #include "system/fd_util.h" #include "fs/AllocatedPath.hxx" #include "fs/FileSystem.hxx" +#include "util/Alloc.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" -#include <glib.h> - #include <string> +#include <algorithm> #include <sys/types.h> #include <sys/stat.h> @@ -78,7 +78,7 @@ public: parent(_parent), serial(_serial), path(AllocatedPath::Null()), address_length(_address_length), - address((sockaddr *)g_memdup(_address, _address_length)) + address((sockaddr *)xmemdup(_address, _address_length)) { assert(_address != nullptr); assert(_address_length > 0); @@ -88,7 +88,10 @@ public: OneServerSocket &operator=(const OneServerSocket &other) = delete; ~OneServerSocket() { - g_free(address); + free(address); + + if (IsDefined()) + Close(); } unsigned GetSerial() const { @@ -106,7 +109,10 @@ public: using SocketMonitor::IsDefined; using SocketMonitor::Close; - char *ToString() const; + gcc_pure + std::string ToString() const { + return sockaddr_to_string(address, address_length); + } void SetFD(int _fd) { SocketMonitor::Open(_fd); @@ -121,18 +127,6 @@ private: static constexpr Domain server_socket_domain("server_socket"); -/** - * Wraper for sockaddr_to_string() which never fails. - */ -char * -OneServerSocket::ToString() const -{ - char *p = sockaddr_to_string(address, address_length, IgnoreError()); - if (p == nullptr) - p = g_strdup("[unknown]"); - return p; -} - static int get_remote_uid(int fd) { @@ -242,23 +236,21 @@ ServerSocket::Open(Error &error) Error error2; if (!i.Open(error2)) { if (good != nullptr && good->GetSerial() == i.GetSerial()) { - char *address_string = i.ToString(); - char *good_string = good->ToString(); + const auto address_string = i.ToString(); + const auto good_string = good->ToString(); FormatWarning(server_socket_domain, "bind to '%s' failed: %s " "(continuing anyway, because " "binding to '%s' succeeded)", - address_string, error2.GetMessage(), - good_string); - g_free(address_string); - g_free(good_string); + address_string.c_str(), + error2.GetMessage(), + good_string.c_str()); } else if (bad == nullptr) { bad = &i; - char *address_string = i.ToString(); + const auto address_string = i.ToString(); error2.FormatPrefix("Failed to bind to '%s': ", - address_string); - g_free(address_string); + address_string.c_str()); last_error = std::move(error2); } diff --git a/src/event/ServerSocket.hxx b/src/event/ServerSocket.hxx index facb10371..05e13ff63 100644 --- a/src/event/ServerSocket.hxx +++ b/src/event/ServerSocket.hxx @@ -36,6 +36,9 @@ typedef void (*server_socket_callback_t)(int fd, class OneServerSocket; +/** + * A socket that accepts incoming stream connections (e.g. TCP). + */ class ServerSocket { friend class OneServerSocket; diff --git a/src/event/SignalMonitor.cxx b/src/event/SignalMonitor.cxx index 8c8527a77..eac011616 100644 --- a/src/event/SignalMonitor.cxx +++ b/src/event/SignalMonitor.cxx @@ -39,6 +39,9 @@ #include <algorithm> +#include <assert.h> +#include <signal.h> + class SignalMonitor final : private SocketMonitor { #ifdef USE_SIGNALFD SignalFD fd; @@ -55,14 +58,6 @@ public: #endif } - ~SignalMonitor() { - /* prevent the descriptor to be closed twice */ -#ifdef USE_SIGNALFD - if (SocketMonitor::IsDefined()) -#endif - SocketMonitor::Steal(); - } - using SocketMonitor::GetEventLoop; #ifdef USE_SIGNALFD diff --git a/src/event/SocketMonitor.cxx b/src/event/SocketMonitor.cxx index 2b97059f7..ef2128684 100644 --- a/src/event/SocketMonitor.cxx +++ b/src/event/SocketMonitor.cxx @@ -28,12 +28,9 @@ #ifdef WIN32 #include <winsock2.h> #else -#include <sys/types.h> #include <sys/socket.h> #endif -#ifdef USE_EPOLL - void SocketMonitor::Dispatch(unsigned flags) { @@ -43,93 +40,19 @@ SocketMonitor::Dispatch(unsigned flags) Cancel(); } -#else - -/* - * GSource methods - * - */ - -gboolean -SocketMonitor::Prepare(gcc_unused GSource *source, gcc_unused gint *timeout_r) -{ - return false; -} - -gboolean -SocketMonitor::Check(GSource *_source) -{ - const Source &source = *(const Source *)_source; - const SocketMonitor &monitor = *source.monitor; - assert(_source == &monitor.source->base); - - return monitor.Check(); -} - -gboolean -SocketMonitor::Dispatch(GSource *_source, - gcc_unused GSourceFunc callback, - gcc_unused gpointer user_data) -{ - Source &source = *(Source *)_source; - SocketMonitor &monitor = *source.monitor; - assert(_source == &monitor.source->base); - - monitor.Dispatch(); - return true; -} - -/** - * The vtable for our GSource implementation. Unfortunately, we - * cannot declare it "const", because g_source_new() takes a non-const - * pointer, for whatever reason. - */ -static GSourceFuncs socket_monitor_source_funcs = { - SocketMonitor::Prepare, - SocketMonitor::Check, - SocketMonitor::Dispatch, - nullptr, - nullptr, - nullptr, -}; - -SocketMonitor::SocketMonitor(int _fd, EventLoop &_loop) - :fd(-1), loop(_loop), - source(nullptr) { - assert(_fd >= 0); - - Open(_fd); -} - -#endif - SocketMonitor::~SocketMonitor() { if (IsDefined()) - Close(); + Cancel(); } void SocketMonitor::Open(int _fd) { assert(fd < 0); -#ifndef USE_EPOLL - assert(source == nullptr); -#endif assert(_fd >= 0); fd = _fd; - -#ifndef USE_EPOLL - poll = {fd, 0, 0}; - - source = (Source *)g_source_new(&socket_monitor_source_funcs, - sizeof(*source)); - source->monitor = this; - - g_source_attach(&source->base, loop.GetContext()); - g_source_add_poll(&source->base, &poll); -#endif } int @@ -142,12 +65,6 @@ SocketMonitor::Steal() int result = fd; fd = -1; -#ifndef USE_EPOLL - g_source_destroy(&source->base); - g_source_unref(&source->base); - source = nullptr; -#endif - return result; } @@ -156,12 +73,9 @@ SocketMonitor::Abandon() { assert(IsDefined()); -#ifdef USE_EPOLL + int old_fd = fd; fd = -1; - loop.Abandon(*this); -#else - Steal(); -#endif + loop.Abandon(old_fd, *this); } void @@ -178,7 +92,6 @@ SocketMonitor::Schedule(unsigned flags) if (flags == GetScheduledFlags()) return; -#ifdef USE_EPOLL if (scheduled_flags == 0) loop.AddFD(fd, flags, *this); else if (flags == 0) @@ -187,12 +100,6 @@ SocketMonitor::Schedule(unsigned flags) loop.ModifyFD(fd, flags, *this); scheduled_flags = flags; -#else - poll.events = flags; - poll.revents &= flags; - - loop.WakeUp(); -#endif } SocketMonitor::ssize_t diff --git a/src/event/SocketMonitor.hxx b/src/event/SocketMonitor.hxx index 5369ddb8a..d0fcf1b57 100644 --- a/src/event/SocketMonitor.hxx +++ b/src/event/SocketMonitor.hxx @@ -21,12 +21,7 @@ #define MPD_SOCKET_MONITOR_HXX #include "check.h" - -#ifdef USE_EPOLL -#include <sys/epoll.h> -#else -#include <glib.h> -#endif +#include "PollGroup.hxx" #include <type_traits> @@ -34,8 +29,8 @@ #include <stddef.h> #ifdef WIN32 -/* ERRORis a WIN32 macro that poisons our namespace; this is a - kludge to allow us to use it anyway */ +/* ERROR is a WIN32 macro that poisons our namespace; this is a kludge + to allow us to use it anyway */ #ifdef ERROR #undef ERROR #endif @@ -43,56 +38,41 @@ class EventLoop; +/** + * Monitor events on a socket. Call Schedule() to announce events + * you're interested in, or Cancel() to cancel your subscription. The + * #EventLoop will invoke virtual method OnSocketReady() as soon as + * any of the subscribed events are ready. + * + * This class does not feel responsible for closing the socket. Call + * Close() to do it manually. + * + * This class is not thread-safe, all methods must be called from the + * thread that runs the #EventLoop, except where explicitly documented + * as thread-safe. + */ class SocketMonitor { -#ifdef USE_EPOLL -#else - struct Source { - GSource base; - - SocketMonitor *monitor; - }; -#endif - int fd; EventLoop &loop; -#ifdef USE_EPOLL /** * A bit mask of events that is currently registered in the EventLoop. */ unsigned scheduled_flags; -#else - Source *source; - GPollFD poll; -#endif public: -#ifdef USE_EPOLL - static constexpr unsigned READ = EPOLLIN; - static constexpr unsigned WRITE = EPOLLOUT; - static constexpr unsigned ERROR = EPOLLERR; - static constexpr unsigned HANGUP = EPOLLHUP; -#else - static constexpr unsigned READ = G_IO_IN; - static constexpr unsigned WRITE = G_IO_OUT; - static constexpr unsigned ERROR = G_IO_ERR; - static constexpr unsigned HANGUP = G_IO_HUP; -#endif + static constexpr unsigned READ = PollGroup::READ; + static constexpr unsigned WRITE = PollGroup::WRITE; + static constexpr unsigned ERROR = PollGroup::ERROR; + static constexpr unsigned HANGUP = PollGroup::HANGUP; typedef std::make_signed<size_t>::type ssize_t; -#ifdef USE_EPOLL SocketMonitor(EventLoop &_loop) :fd(-1), loop(_loop), scheduled_flags(0) {} SocketMonitor(int _fd, EventLoop &_loop) :fd(_fd), loop(_loop), scheduled_flags(0) {} -#else - SocketMonitor(EventLoop &_loop) - :fd(-1), loop(_loop), source(nullptr) {} - - SocketMonitor(int _fd, EventLoop &_loop); -#endif ~SocketMonitor(); @@ -114,7 +94,7 @@ public: /** * "Steal" the socket descriptor. This abandons the socket - * and puts the responsibility for closing it to the caller. + * and returns it. */ int Steal(); @@ -128,11 +108,7 @@ public: unsigned GetScheduledFlags() const { assert(IsDefined()); -#ifdef USE_EPOLL return scheduled_flags; -#else - return poll.events; -#endif } void Schedule(unsigned flags); @@ -167,28 +143,7 @@ protected: virtual bool OnSocketReady(unsigned flags) = 0; public: -#ifdef USE_EPOLL void Dispatch(unsigned flags); -#else - /* GSource callbacks */ - static gboolean Prepare(GSource *source, gint *timeout_r); - static gboolean Check(GSource *source); - static gboolean Dispatch(GSource *source, GSourceFunc callback, - gpointer user_data); - -private: - bool Check() const { - assert(IsDefined()); - - return (poll.revents & poll.events) != 0; - } - - void Dispatch() { - assert(IsDefined()); - - OnSocketReady(poll.revents & poll.events); - } -#endif }; #endif diff --git a/src/event/TimeoutMonitor.cxx b/src/event/TimeoutMonitor.cxx index cffad6b92..55260af2a 100644 --- a/src/event/TimeoutMonitor.cxx +++ b/src/event/TimeoutMonitor.cxx @@ -25,28 +25,19 @@ void TimeoutMonitor::Cancel() { if (IsActive()) { -#ifdef USE_EPOLL active = false; loop.CancelTimer(*this); -#else - g_source_destroy(source); - g_source_unref(source); - source = nullptr; -#endif } } void + TimeoutMonitor::Schedule(unsigned ms) { Cancel(); -#ifdef USE_EPOLL active = true; loop.AddTimer(*this, ms); -#else - source = loop.AddTimeout(ms, Callback, this); -#endif } void @@ -54,31 +45,11 @@ TimeoutMonitor::ScheduleSeconds(unsigned s) { Cancel(); -#ifdef USE_EPOLL Schedule(s * 1000u); -#else - source = loop.AddTimeoutSeconds(s, Callback, this); -#endif } void TimeoutMonitor::Run() { -#ifndef USE_EPOLL - Cancel(); -#endif - OnTimeout(); } - -#ifndef USE_EPOLL - -gboolean -TimeoutMonitor::Callback(gpointer data) -{ - TimeoutMonitor &monitor = *(TimeoutMonitor *)data; - monitor.Run(); - return false; -} - -#endif diff --git a/src/event/TimeoutMonitor.hxx b/src/event/TimeoutMonitor.hxx index 98e4e5564..0e3f4bdb7 100644 --- a/src/event/TimeoutMonitor.hxx +++ b/src/event/TimeoutMonitor.hxx @@ -22,34 +22,27 @@ #include "check.h" -#ifndef USE_EPOLL -#include <glib.h> -#endif - class EventLoop; +/** + * This class monitors a timeout. Use Schedule() to begin the timeout + * or Cancel() to cancel it. + * + * This class is not thread-safe, all methods must be called from the + * thread that runs the #EventLoop, except where explicitly documented + * as thread-safe. + */ class TimeoutMonitor { -#ifdef USE_EPOLL friend class EventLoop; -#endif EventLoop &loop; -#ifdef USE_EPOLL bool active; -#else - GSource *source; -#endif public: -#ifdef USE_EPOLL TimeoutMonitor(EventLoop &_loop) :loop(_loop), active(false) { } -#else - TimeoutMonitor(EventLoop &_loop) - :loop(_loop), source(nullptr) {} -#endif ~TimeoutMonitor() { Cancel(); @@ -60,11 +53,7 @@ public: } bool IsActive() const { -#ifdef USE_EPOLL return active; -#else - return source != nullptr; -#endif } void Schedule(unsigned ms); @@ -76,10 +65,6 @@ protected: private: void Run(); - -#ifndef USE_EPOLL - static gboolean Callback(gpointer data); -#endif }; #endif /* MAIN_NOTIFY_H */ |