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