1 /* 2 * Copyright 2003-2021 The Music Player Daemon Project 3 * http://www.musicpd.org 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License along 16 * with this program; if not, write to the Free Software Foundation, Inc., 17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 */ 19 20 #ifndef MPD_MULTI_SOCKET_MONITOR_HXX 21 #define MPD_MULTI_SOCKET_MONITOR_HXX 22 23 #include "IdleEvent.hxx" 24 #include "FineTimerEvent.hxx" 25 #include "SocketEvent.hxx" 26 #include "event/Features.h" 27 28 #include <cassert> 29 #include <forward_list> 30 #include <iterator> 31 32 #ifndef _WIN32 33 struct pollfd; 34 #endif 35 36 class EventLoop; 37 38 /** 39 * Similar to #SocketEvent, but monitors multiple sockets. To use 40 * it, implement the methods PrepareSockets() and DispatchSockets(). 41 * In PrepareSockets(), use UpdateSocketList() and AddSocket(). 42 * DispatchSockets() will be called if at least one socket is ready. 43 */ 44 class MultiSocketMonitor 45 { 46 class SingleFD final { 47 MultiSocketMonitor &multi; 48 49 SocketEvent event; 50 51 unsigned revents; 52 53 public: SingleFD(MultiSocketMonitor & _multi,SocketDescriptor _fd)54 SingleFD(MultiSocketMonitor &_multi, 55 SocketDescriptor _fd) noexcept 56 :multi(_multi), 57 event(multi.GetEventLoop(), 58 BIND_THIS_METHOD(OnSocketReady), _fd), 59 revents(0) {} 60 GetSocket() const61 SocketDescriptor GetSocket() const noexcept { 62 return event.GetSocket(); 63 } 64 GetEvents() const65 unsigned GetEvents() const noexcept { 66 return event.GetScheduledFlags(); 67 } 68 SetEvents(unsigned _events)69 void SetEvents(unsigned _events) noexcept { 70 revents &= _events; 71 event.Schedule(_events); 72 } 73 Schedule(unsigned events)74 bool Schedule(unsigned events) noexcept { 75 return event.Schedule(events); 76 } 77 GetReturnedEvents() const78 unsigned GetReturnedEvents() const noexcept { 79 return revents; 80 } 81 ClearReturnedEvents()82 void ClearReturnedEvents() noexcept { 83 revents = 0; 84 } 85 86 private: OnSocketReady(unsigned flags)87 void OnSocketReady(unsigned flags) noexcept { 88 revents = flags; 89 multi.SetReady(); 90 } 91 }; 92 93 IdleEvent idle_event; 94 95 // TODO: switch to CoarseTimerEvent? ... not yet because the ALSA plugin needs exact timeouts 96 FineTimerEvent timeout_event; 97 98 /** 99 * DispatchSockets() should be called. 100 */ 101 bool ready = false; 102 103 /** 104 * PrepareSockets() should be called. 105 * 106 * Note that this doesn't need to be initialized by the 107 * constructor; this class is activated with the first 108 * InvalidateSockets() call, which initializes this flag. 109 */ 110 bool refresh; 111 112 std::forward_list<SingleFD> fds; 113 114 #ifdef USE_EPOLL 115 struct AlwaysReady { 116 const SocketDescriptor fd; 117 const unsigned revents; 118 }; 119 120 /** 121 * A list of file descriptors which are always ready. This is 122 * a kludge needed because the ALSA output plugin gives us a 123 * file descriptor to /dev/null, which is incompatible with 124 * epoll (epoll_ctl() returns -EPERM). 125 */ 126 std::forward_list<AlwaysReady> always_ready_fds; 127 #endif 128 129 public: 130 MultiSocketMonitor(EventLoop &_loop) noexcept; 131 GetEventLoop() const132 EventLoop &GetEventLoop() const noexcept { 133 return idle_event.GetEventLoop(); 134 } 135 136 /** 137 * Clear the socket list and disable all #EventLoop 138 * registrations. Run this in the #EventLoop thread before 139 * destroying this object. 140 * 141 * Later, this object can be reused and reactivated by calling 142 * InvalidateSockets(). 143 * 144 * Note that this class doesn't have a destructor which calls 145 * this method, because this would be racy and thus pointless: 146 * at the time ~MultiSocketMonitor() is called, our virtual 147 * methods have been morphed to be pure again, and in the 148 * meantime the #EventLoop thread could invoke those pure 149 * methods. 150 */ 151 void Reset() noexcept; 152 153 /** 154 * Invalidate the socket list. A call to PrepareSockets() is 155 * scheduled which will then update the list. 156 */ InvalidateSockets()157 void InvalidateSockets() noexcept { 158 refresh = true; 159 idle_event.Schedule(); 160 } 161 162 /** 163 * Add one socket to the list of monitored sockets. 164 * 165 * May only be called from PrepareSockets(). 166 */ 167 bool AddSocket(SocketDescriptor fd, unsigned events) noexcept; 168 169 /** 170 * Remove all sockets. 171 * 172 * May only be called from PrepareSockets(). 173 */ 174 void ClearSocketList() noexcept; 175 176 /** 177 * Update the known sockets by invoking the given function for 178 * each one; its return value is the events bit mask. A 179 * return value of 0 means the socket will be removed from the 180 * list. 181 * 182 * May only be called from PrepareSockets(). 183 */ 184 template<typename E> UpdateSocketList(E && e)185 void UpdateSocketList(E &&e) noexcept { 186 for (auto prev = fds.before_begin(), end = fds.end(), 187 i = std::next(prev); 188 i != end; i = std::next(prev)) { 189 assert(i->GetEvents() != 0); 190 191 unsigned events = e(i->GetSocket()); 192 if (events != 0) { 193 i->SetEvents(events); 194 prev = i; 195 } else { 196 fds.erase_after(prev); 197 } 198 } 199 } 200 201 #ifndef _WIN32 202 /** 203 * Replace the socket list with the given file descriptors. 204 * The given pollfd array will be modified by this method. 205 * 206 * May only be called from PrepareSockets(). 207 */ 208 void ReplaceSocketList(pollfd *pfds, unsigned n) noexcept; 209 #endif 210 211 /** 212 * Invoke a function for each socket which has become ready. 213 */ 214 template<typename F> ForEachReturnedEvent(F && f)215 void ForEachReturnedEvent(F &&f) noexcept { 216 for (auto &i : fds) { 217 if (i.GetReturnedEvents() != 0) { 218 f(i.GetSocket(), i.GetReturnedEvents()); 219 i.ClearReturnedEvents(); 220 } 221 } 222 223 #ifdef USE_EPOLL 224 for (const auto &i : always_ready_fds) 225 f(i.fd, i.revents); 226 #endif 227 } 228 229 protected: 230 /** 231 * Override this method and update the socket registrations. 232 * To do that, call AddSocket(), ClearSocketList(), 233 * UpdateSocketList() and ReplaceSocketList(). 234 * 235 * @return timeout or a negative value for no timeout 236 */ 237 virtual Event::Duration PrepareSockets() noexcept = 0; 238 239 /** 240 * At least one socket is ready or the timeout has expired. 241 * This method should be used to perform I/O. 242 */ 243 virtual void DispatchSockets() noexcept = 0; 244 245 private: SetReady()246 void SetReady() noexcept { 247 ready = true; 248 idle_event.Schedule(); 249 } 250 251 void Prepare() noexcept; 252 OnTimeout()253 void OnTimeout() noexcept { 254 SetReady(); 255 } 256 257 void OnIdle() noexcept; 258 }; 259 260 #endif 261