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 EVENT_LOOP_HXX
21 #define EVENT_LOOP_HXX
22 
23 #include "Chrono.hxx"
24 #include "TimerWheel.hxx"
25 #include "TimerList.hxx"
26 #include "Backend.hxx"
27 #include "SocketEvent.hxx"
28 #include "event/Features.h"
29 #include "time/ClockCache.hxx"
30 #include "util/IntrusiveList.hxx"
31 
32 #ifdef HAVE_THREADED_EVENT_LOOP
33 #include "WakeFD.hxx"
34 #include "thread/Id.hxx"
35 #include "thread/Mutex.hxx"
36 
37 #include <boost/intrusive/list.hpp>
38 #endif
39 
40 #include <atomic>
41 #include <cassert>
42 
43 #include "io/uring/Features.h"
44 #ifdef HAVE_URING
45 #include <memory>
46 namespace Uring { class Queue; class Manager; }
47 #endif
48 
49 class DeferEvent;
50 class InjectEvent;
51 
52 /**
53  * An event loop that polls for events on file/socket descriptors.
54  *
55  * This class is not thread-safe, all methods must be called from the
56  * thread that runs it, except where explicitly documented as
57  * thread-safe.
58  *
59  * @see SocketEvent, MultiSocketMonitor, TimerEvent, DeferEvent, InjectEvent
60  */
61 class EventLoop final
62 {
63 #ifdef HAVE_THREADED_EVENT_LOOP
64 	WakeFD wake_fd;
65 	SocketEvent wake_event{*this, BIND_THIS_METHOD(OnSocketReady), wake_fd.GetSocket()};
66 #endif
67 
68 	TimerWheel coarse_timers;
69 	TimerList timers;
70 
71 	using DeferList = IntrusiveList<DeferEvent>;
72 
73 	DeferList defer;
74 
75 	/**
76 	 * This is like #defer, but gets invoked when the loop is idle.
77 	 */
78 	DeferList idle;
79 
80 #ifdef HAVE_THREADED_EVENT_LOOP
81 	Mutex mutex;
82 
83 	using InjectList =
84 		boost::intrusive::list<InjectEvent,
85 				       boost::intrusive::base_hook<boost::intrusive::list_base_hook<>>,
86 				       boost::intrusive::constant_time_size<false>>;
87 	InjectList inject;
88 #endif
89 
90 	using SocketList = IntrusiveList<SocketEvent>;
91 
92 	/**
93 	 * A list of scheduled #SocketEvent instances, without those
94 	 * which are ready (these are in #ready_sockets).
95 	 */
96 	SocketList sockets;
97 
98 	/**
99 	 * A linked list of #SocketEvent instances which have a
100 	 * non-zero "ready_flags" field, and need to be dispatched.
101 	 */
102 	SocketList ready_sockets;
103 
104 #ifdef HAVE_URING
105 	std::unique_ptr<Uring::Manager> uring;
106 #endif
107 
108 #ifdef HAVE_THREADED_EVENT_LOOP
109 	/**
110 	 * A reference to the thread that is currently inside Run().
111 	 */
112 	ThreadId thread = ThreadId::Null();
113 
114 	/**
115 	 * Is this #EventLoop alive, i.e. can events be scheduled?
116 	 * This is used by BlockingCall() to determine whether
117 	 * schedule in the #EventThread or to call directly (if
118 	 * there's no #EventThread yet/anymore).
119 	 */
120 	bool alive;
121 #endif
122 
123 	std::atomic_bool quit{false};
124 
125 	/**
126 	 * True when the object has been modified and another check is
127 	 * necessary before going to sleep via EventPollBackend::ReadEvents().
128 	 */
129 	bool again;
130 
131 #ifdef HAVE_THREADED_EVENT_LOOP
132 	/**
133 	 * True when handling callbacks, false when waiting for I/O or
134 	 * timeout.
135 	 *
136 	 * Protected with #mutex.
137 	 */
138 	bool busy = true;
139 #endif
140 
141 #ifdef HAVE_URING
142 	bool uring_initialized = false;
143 #endif
144 
145 	EventPollBackend poll_backend;
146 
147 	ClockCache<std::chrono::steady_clock> steady_clock_cache;
148 
149 public:
150 	/**
151 	 * Throws on error.
152 	 */
153 #ifdef HAVE_THREADED_EVENT_LOOP
154 	explicit EventLoop(ThreadId _thread);
155 
EventLoop()156 	EventLoop():EventLoop(ThreadId::GetCurrent()) {}
157 #else
158 	EventLoop();
159 #endif
160 
161 	~EventLoop() noexcept;
162 
163 	EventLoop(const EventLoop &other) = delete;
164 	EventLoop &operator=(const EventLoop &other) = delete;
165 
GetSteadyClockCache() const166 	const auto &GetSteadyClockCache() const noexcept {
167 		return steady_clock_cache;
168 	}
169 
170 	/**
171 	 * Caching wrapper for std::chrono::steady_clock::now().  The
172 	 * real clock is queried at most once per event loop
173 	 * iteration, because it is assumed that the event loop runs
174 	 * for a negligible duration.
175 	 */
176 	[[gnu::pure]]
SteadyNow() const177 	const auto &SteadyNow() const noexcept {
178 #ifdef HAVE_THREADED_EVENT_LOOP
179 		assert(IsInside());
180 #endif
181 
182 		return steady_clock_cache.now();
183 	}
184 
185 #ifdef HAVE_URING
186 	[[gnu::pure]]
187 	Uring::Queue *GetUring() noexcept;
188 #endif
189 
190 	/**
191 	 * Stop execution of this #EventLoop at the next chance.  This
192 	 * method is thread-safe and non-blocking: after returning, it
193 	 * is not guaranteed that the EventLoop has really stopped.
194 	 */
195 	void Break() noexcept;
196 
197 	bool AddFD(int fd, unsigned events, SocketEvent &event) noexcept;
198 	bool ModifyFD(int fd, unsigned events, SocketEvent &event) noexcept;
199 	bool RemoveFD(int fd, SocketEvent &event) noexcept;
200 
201 	/**
202 	 * Remove the given #SocketEvent after the file descriptor
203 	 * has been closed.  This is like RemoveFD(), but does not
204 	 * attempt to use #EPOLL_CTL_DEL.
205 	 */
206 	bool AbandonFD(SocketEvent &event) noexcept;
207 
208 	void Insert(CoarseTimerEvent &t) noexcept;
209 	void Insert(FineTimerEvent &t) noexcept;
210 
211 	/**
212 	 * Schedule a call to DeferEvent::RunDeferred().
213 	 */
214 	void AddDefer(DeferEvent &d) noexcept;
215 	void AddIdle(DeferEvent &e) noexcept;
216 
217 #ifdef HAVE_THREADED_EVENT_LOOP
218 	/**
219 	 * Schedule a call to the InjectEvent.
220 	 *
221 	 * This method is thread-safe.
222 	 */
223 	void AddInject(InjectEvent &d) noexcept;
224 
225 	/**
226 	 * Cancel a pending call to the InjectEvent.
227 	 * However after returning, the call may still be running.
228 	 *
229 	 * This method is thread-safe.
230 	 */
231 	void RemoveInject(InjectEvent &d) noexcept;
232 #endif
233 
234 	/**
235 	 * The main function of this class.  It will loop until
236 	 * Break() gets called.  Can be called only once.
237 	 */
238 	void Run() noexcept;
239 
240 private:
241 	void RunDeferred() noexcept;
242 
243 	/**
244 	 * Invoke one "idle" #DeferEvent.
245 	 *
246 	 * @return false if there was no such event
247 	 */
248 	bool RunOneIdle() noexcept;
249 
250 #ifdef HAVE_THREADED_EVENT_LOOP
251 	/**
252 	 * Invoke all pending InjectEvents.
253 	 *
254 	 * Caller must lock the mutex.
255 	 */
256 	void HandleInject() noexcept;
257 #endif
258 
259 	/**
260 	 * Invoke all expired #TimerEvent instances and return the
261 	 * duration until the next timer expires.  Returns a negative
262 	 * duration if there is no timeout.
263 	 */
264 	Event::Duration HandleTimers() noexcept;
265 
266 	/**
267 	 * Call epoll_wait() and pass all returned events to
268 	 * SocketEvent::SetReadyFlags().
269 	 *
270 	 * @return true if one or more sockets have become ready
271 	 */
272 	bool Wait(Event::Duration timeout) noexcept;
273 
274 #ifdef HAVE_THREADED_EVENT_LOOP
275 	void OnSocketReady(unsigned flags) noexcept;
276 #endif
277 
278 public:
279 #ifdef HAVE_THREADED_EVENT_LOOP
SetAlive(bool _alive)280 	void SetAlive(bool _alive) noexcept {
281 		alive = _alive;
282 	}
283 
IsAlive() const284 	bool IsAlive() const noexcept {
285 		return alive;
286 	}
287 #endif
288 
289 	/**
290 	 * Are we currently running inside this EventLoop's thread?
291 	 */
292 	[[gnu::pure]]
IsInside() const293 	bool IsInside() const noexcept {
294 #ifdef HAVE_THREADED_EVENT_LOOP
295 		return thread.IsInside();
296 #else
297 		return true;
298 #endif
299 	}
300 };
301 
302 #endif /* MAIN_NOTIFY_H */
303