Brian Silverman | 74b92d5 | 2021-10-14 13:12:02 -0700 | [diff] [blame] | 1 | #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 | |
| 11 | namespace aos { |
| 12 | |
| 13 | class 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. |
| 21 | template <typename T> |
| 22 | class 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 | |
| 73 | template <typename T> |
| 74 | class 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. |
| 87 | template <typename... Args> |
| 88 | class 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. |
| 120 | class 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 | |
| 199 | template <typename T> |
| 200 | inline 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 | |
| 209 | template <typename T> |
| 210 | inline 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 | |
| 225 | template <typename T> |
| 226 | GlibSourceCallbackRefcount<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 | |
| 235 | template <typename T> |
| 236 | GlibSourceCallbackRefcount<T>::~GlibSourceCallbackRefcount() { |
| 237 | glib_main_loop_->RemoveChild(); |
| 238 | } |
| 239 | |
| 240 | template <typename... Args> |
| 241 | GlibSignalCallback<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 Schuh | 99f7c6a | 2024-06-25 22:07:44 -0700 | [diff] [blame^] | 250 | CHECK_GT(signal_handler_id_, 0u); |
Brian Silverman | 74b92d5 | 2021-10-14 13:12:02 -0700 | [diff] [blame] | 251 | 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 | |
| 257 | template <typename... Args> |
| 258 | GlibSignalCallback<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 | |
| 265 | template <typename... Args> |
| 266 | void 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_ |