blob: 782043b58344797908d516348010ba2875372dfc [file] [log] [blame]
Brian Silverman74b92d52021-10-14 13:12:02 -07001#ifndef AOS_EVENTS_GLIB_MAIN_LOOP_H_
2#define AOS_EVENTS_GLIB_MAIN_LOOP_H_
3
4#include <glib-object.h>
5#include <glib.h>
6
7#include <unordered_set>
8
9#include "aos/events/shm_event_loop.h"
10
11namespace aos {
12
13class GlibMainLoop;
14
15// Adapts a std::function to a g_source-style callback.
16//
17// T is the function pointer type.
18//
19// This doesn't interact with a GlibMainLoop, so it's safe to use from any
20// thread, but it also won't catch lifetime bugs cleanly.
21template <typename T>
22class GlibSourceCallback {
23 private:
24 template <typename TResult, typename... TArgs>
25 struct helper {
26 static TResult Invoke(TArgs... args, gpointer user_data) {
27 GlibSourceCallback *const pointer =
28 reinterpret_cast<GlibSourceCallback *>(user_data);
29 CHECK(g_main_context_is_owner(pointer->g_main_context_))
30 << ": Callback being called from the wrong thread";
31 return pointer->function_(args...);
32 }
33 using Function = std::function<TResult(TArgs...)>;
34 };
35 // A helper to deduce template arguments (type template arguments can't be
36 // deduced, so we need a function).
37 template <typename TResult>
38 static helper<TResult> MakeHelper(TResult (*)(gpointer)) {
39 return helper<TResult>();
40 }
41
42 using HelperType =
43 decltype(GlibSourceCallback::MakeHelper(std::declval<T>()));
44
45 protected:
46 using Function = typename HelperType::Function;
47
48 public:
49 // May be called from any thread.
50 GlibSourceCallback(Function function, GSource *source,
51 GMainContext *g_main_context);
52 // May only be called from the main thread.
53 ~GlibSourceCallback();
54
55 // Instances may not be moved because a pointer to the instance gets passed
56 // around.
57 GlibSourceCallback(const GlibSourceCallback &) = delete;
58 GlibSourceCallback &operator=(const GlibSourceCallback &) = delete;
59
60 private:
61 GSourceFunc g_source_func() const {
62 return reinterpret_cast<GSourceFunc>(&HelperType::Invoke);
63 }
64 gpointer user_data() const {
65 return const_cast<gpointer>(reinterpret_cast<const void *>(this));
66 }
67
68 const Function function_;
69 GSource *const source_;
70 GMainContext *const g_main_context_;
71};
72
73template <typename T>
74class GlibSourceCallbackRefcount : public GlibSourceCallback<T> {
75 public:
76 GlibSourceCallbackRefcount(typename GlibSourceCallback<T>::Function function,
77 GSource *source, GlibMainLoop *glib_main_loop);
78 ~GlibSourceCallbackRefcount();
79
80 private:
81 GlibMainLoop *const glib_main_loop_;
82};
83
84// Adapts a std::function to a g_signal-style callback. This includes calling
85// the std::function the main thread, vs the g_signal callback is invoked on an
86// arbitrary thread.
87template <typename... Args>
88class GlibSignalCallback {
89 public:
90 GlibSignalCallback(std::function<void(Args...)> function,
91 GlibMainLoop *glib_main_loop, gpointer instance,
92 const char *detailed_signal);
93 ~GlibSignalCallback();
94
95 // Instances may not be moved because a pointer to the instance gets passed
96 // around.
97 GlibSignalCallback(const GlibSignalCallback &) = delete;
98 GlibSignalCallback &operator=(const GlibSignalCallback &) = delete;
99
100 private:
101 static void InvokeSignal(Args... args, gpointer user_data);
102 gpointer user_data() const {
103 return const_cast<gpointer>(reinterpret_cast<const void *>(this));
104 }
105
106 const std::function<void(Args...)> function_;
107 GlibMainLoop *const glib_main_loop_;
108 const gpointer instance_;
109 const gulong signal_handler_id_;
110
111 // Protects changes to invocations_ and idle_callback_.
112 aos::stl_mutex lock_;
113 std::vector<std::tuple<Args...>> invocations_;
114 std::optional<GlibSourceCallback<GSourceFunc>> idle_callback_;
115};
116
117// Manages a GMainLoop attached to a ShmEventLoop.
118//
119// Also provides C++ RAII wrappers around the related glib objects.
120class GlibMainLoop {
121 public:
122 GlibMainLoop(ShmEventLoop *event_loop);
123 ~GlibMainLoop();
124 GlibMainLoop(const GlibMainLoop &) = delete;
125 GlibMainLoop &operator=(const GlibMainLoop &) = delete;
126
127 GMainContext *g_main_context() { return g_main_context_; }
128 GMainLoop *g_main_loop() { return g_main_loop_; }
129
130 auto AddIdle(std::function<gboolean()> callback) {
131 return GlibSourceCallbackRefcount<GSourceFunc>(std::move(callback),
132 g_idle_source_new(), this);
133 }
134
135 auto AddTimeout(std::function<gboolean()> callback, guint interval) {
136 return GlibSourceCallbackRefcount<GSourceFunc>(
137 std::move(callback), g_timeout_source_new(interval), this);
138 }
139
140 // Connects a glib signal to a callback. Note that this is NOT a Unix signal.
141 //
142 // Note that the underlying signal handler is called in one of gstreamer's
143 // thread, but callback will be called in the main thread. This means that any
144 // objects being passed in with the expectation of the handler incrementing
145 // their refcount need special handling. This also means any glib signal
146 // handlers which need to return a value cannot use this abstraction.
147 //
148 // It's recommended to pass an actual std::function (NOT something with a
149 // user-defined conversion to a std::function, such as a lambda) as the first
150 // argument, which allows the template arguments to be deduced.
151 //
152 // Passing a lambda with explicit template arguments doesn't work
153 // unfortunately... I think it's because the variadic template argument could
154 // be extended beyond anything you explicitly pass in, so it's always doing
155 // deduction, and deduction doesn't consider the user-defined conversion
156 // between the lambda's type and the relevant std::function type. C++ sucks,
157 // sorry.
158 template <typename... Args>
159 auto ConnectSignal(std::function<void(Args...)> callback, gpointer instance,
160 const char *detailed_signal) {
161 return GlibSignalCallback<Args...>(std::move(callback), this, instance,
162 detailed_signal);
163 }
164
165 void AddChild() { ++children_; }
166
167 void RemoveChild() {
168 CHECK_GT(children_, 0);
169 --children_;
170 }
171
172 private:
173 void RemoveAllFds();
174 void BeforeWait();
175
176 // fds which we have added to the epoll object.
177 std::unordered_set<int> added_fds_;
178
179 ShmEventLoop *const event_loop_;
180 TimerHandler *const timeout_timer_;
181 GMainContext *const g_main_context_;
182 GMainLoop *const g_main_loop_;
183
184 // The list of FDs and priority received from glib on the latest
185 // g_main_context_query call, so we can pass them to the g_main_context_check
186 // next time around.
187 std::vector<GPollFD> gpoll_fds_;
188 gint last_query_max_priority_;
189
190 // Tracks whether we did the call to acquire g_main_context_.
191 bool acquired_context_ = false;
192
193 // Tracking all the child glib objects we create. None of them should outlive
194 // us, and asserting that helps catch bugs in application code that leads to
195 // use-after-frees.
196 int children_ = 0;
197};
198
199template <typename T>
200inline GlibSourceCallback<T>::GlibSourceCallback(Function function,
201 GSource *source,
202 GMainContext *g_main_context)
203 : function_(function), source_(source), g_main_context_(g_main_context) {
204 g_source_set_callback(source_, g_source_func(), user_data(), nullptr);
205 CHECK_GT(g_source_attach(source_, g_main_context_), 0u);
206 VLOG(1) << "Attached source " << source_ << " to " << g_main_context_;
207}
208
209template <typename T>
210inline GlibSourceCallback<T>::~GlibSourceCallback() {
211 CHECK(g_main_context_is_owner(g_main_context_))
212 << ": May only be destroyed from the main thread";
213
214 g_source_destroy(source_);
215 VLOG(1) << "Destroyed source " << source_;
216 // Now, the callback won't be called any more (because this source is no
217 // longer attached to a context), even if refcounts remain that hold the
218 // source itself alive. That's not safe in a multithreaded context, but we
219 // only allow this operation in the main thread, which means it synchronizes
220 // with any other code in the main thread that might call the callback.
221
222 g_source_unref(source_);
223}
224
225template <typename T>
226GlibSourceCallbackRefcount<T>::GlibSourceCallbackRefcount(
227 typename GlibSourceCallback<T>::Function function, GSource *source,
228 GlibMainLoop *glib_main_loop)
229 : GlibSourceCallback<T>(std::move(function), source,
230 glib_main_loop->g_main_context()),
231 glib_main_loop_(glib_main_loop) {
232 glib_main_loop_->AddChild();
233}
234
235template <typename T>
236GlibSourceCallbackRefcount<T>::~GlibSourceCallbackRefcount() {
237 glib_main_loop_->RemoveChild();
238}
239
240template <typename... Args>
241GlibSignalCallback<Args...>::GlibSignalCallback(
242 std::function<void(Args...)> function, GlibMainLoop *glib_main_loop,
243 gpointer instance, const char *detailed_signal)
244 : function_(std::move(function)),
245 glib_main_loop_(glib_main_loop),
246 instance_(instance),
247 signal_handler_id_(g_signal_connect(
248 instance, detailed_signal,
249 G_CALLBACK(&GlibSignalCallback::InvokeSignal), user_data())) {
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700250 CHECK_GT(signal_handler_id_, 0u);
Brian Silverman74b92d52021-10-14 13:12:02 -0700251 VLOG(1) << this << " connected glib signal with " << user_data() << " as "
252 << signal_handler_id_ << " on " << instance << ": "
253 << detailed_signal;
254 glib_main_loop_->AddChild();
255}
256
257template <typename... Args>
258GlibSignalCallback<Args...>::~GlibSignalCallback() {
259 g_signal_handler_disconnect(instance_, signal_handler_id_);
260 VLOG(1) << this << " disconnected glib signal on " << instance_ << ": "
261 << signal_handler_id_;
262 glib_main_loop_->RemoveChild();
263}
264
265template <typename... Args>
266void GlibSignalCallback<Args...>::InvokeSignal(Args... args,
267 gpointer user_data) {
268 CHECK(user_data != nullptr) << ": invalid glib signal callback";
269 GlibSignalCallback *const pointer =
270 reinterpret_cast<GlibSignalCallback *>(user_data);
271 VLOG(1) << "Adding invocation of signal " << pointer;
272 std::unique_lock<aos::stl_mutex> locker(pointer->lock_);
273 CHECK_EQ(!!pointer->idle_callback_, !pointer->invocations_.empty());
274 if (!pointer->idle_callback_) {
275 // If we don't already have a callback set, then schedule a new one.
276 pointer->idle_callback_.emplace(
277 [pointer]() {
278 std::unique_lock<aos::stl_mutex> locker(pointer->lock_);
279 for (const auto &args : pointer->invocations_) {
280 VLOG(1) << "Calling signal handler for " << pointer;
281 std::apply(pointer->function_, args);
282 }
283 pointer->invocations_.clear();
284 pointer->idle_callback_.reset();
285 return false;
286 },
287 g_idle_source_new(), pointer->glib_main_loop_->g_main_context());
288 }
289 pointer->invocations_.emplace_back(
290 std::make_tuple<Args...>(std::forward<Args>(args)...));
291}
292
293} // namespace aos
294
295#endif // AOS_EVENTS_GLIB_MAIN_LOOP_H_