aboutsummaryrefslogtreecommitdiffstats
path: root/ui/qt/glib_mainloop_on_qeventloop.cpp
diff options
context:
space:
mode:
authorTomasz Moń <desowin@gmail.com>2022-07-23 19:03:30 +0200
committerTomasz Moń <desowin@gmail.com>2022-07-24 20:57:18 +0200
commit18e08d04d1f04f027fb2a911abfadb2117f8d10d (patch)
treec0be31e3842ac56a2c89b0d968638028dcbd3314 /ui/qt/glib_mainloop_on_qeventloop.cpp
parent5a8977acd278616ddb8c6604d31355b8dd369c7d (diff)
Qt: Setup GLib mainloop when needed
GLib watches and timeouts require GLib mainloop iterations. If the GLib mainloop is not running, then GLib watches and timeouts won't trigger. Back in the GTK+ days, then GLib mainloop was running on all systems. Since the Qt transition, GLib mainloop only runs on Linux when Qt does support it and environment variable QT_NO_GLIB=1 is not set. Start polling GLib mainloop in separate thread if Qt is not running GLib mainloop. Note that only the polling is handled in separate thread, the dispatch and thus all user callbacks execute in the main thread. Running GLib mainloop when needed enables full GLib functionality on all platforms and thus allows us to simplify our code by using GLib platform specific code.
Diffstat (limited to 'ui/qt/glib_mainloop_on_qeventloop.cpp')
-rw-r--r--ui/qt/glib_mainloop_on_qeventloop.cpp125
1 files changed, 125 insertions, 0 deletions
diff --git a/ui/qt/glib_mainloop_on_qeventloop.cpp b/ui/qt/glib_mainloop_on_qeventloop.cpp
new file mode 100644
index 0000000000..f715bbc728
--- /dev/null
+++ b/ui/qt/glib_mainloop_on_qeventloop.cpp
@@ -0,0 +1,125 @@
+/** @file
+ *
+ * Copyright 2022 Tomasz Mon <desowin@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <QTimer>
+#include "glib_mainloop_on_qeventloop.h"
+
+GLibPoller::GLibPoller(GMainContext *context) :
+ mutex_(), dispatched_(),
+ ctx_(context), priority_(0),
+ fds_(g_new(GPollFD, 1)), allocated_fds_(1), nfds_(0)
+{
+ g_main_context_ref(ctx_);
+}
+
+GLibPoller::~GLibPoller()
+{
+ g_main_context_unref(ctx_);
+ g_free(fds_);
+}
+
+void GLibPoller::run()
+{
+ gint timeout;
+
+ mutex_.lock();
+ while (!isInterruptionRequested())
+ {
+ while (!g_main_context_acquire(ctx_))
+ {
+ /* In normal circumstances context is acquired right away */
+ }
+ g_main_context_prepare(ctx_, &priority_);
+ while ((nfds_ = g_main_context_query(ctx_, priority_, &timeout, fds_,
+ allocated_fds_)) > allocated_fds_)
+ {
+ g_free(fds_);
+ fds_ = g_new(GPollFD, nfds_);
+ allocated_fds_ = nfds_;
+ }
+ /* Blocking g_poll() call is the reason for separate polling thread */
+ g_poll(fds_, nfds_, timeout);
+ g_main_context_release(ctx_);
+
+ /* Polling has finished, dispatch events (if any) in main thread so we
+ * don't have to worry about concurrency issues in GLib callbacks.
+ */
+ emit polled();
+ /* Wait for the main thread to finish dispatching before next poll */
+ dispatched_.wait(&mutex_);
+ }
+ mutex_.unlock();
+}
+
+GLibMainloopOnQEventLoop::GLibMainloopOnQEventLoop(QObject *parent) :
+ QObject(parent),
+ poller_(g_main_context_default())
+{
+ connect(&poller_, &GLibPoller::polled,
+ this, &GLibMainloopOnQEventLoop::checkAndDispatch);
+ poller_.setObjectName("GLibPoller");
+ poller_.start();
+}
+
+GLibMainloopOnQEventLoop::~GLibMainloopOnQEventLoop()
+{
+ poller_.requestInterruption();
+ /* Wakeup poller thread in case it is blocked on g_poll(). Wakeup does not
+ * cause any problem if poller thread is already waiting on dispatched wait
+ * condition.
+ */
+ g_main_context_wakeup(poller_.ctx_);
+ /* Wakeup poller thread without actually dispatching */
+ poller_.mutex_.lock();
+ poller_.dispatched_.wakeOne();
+ poller_.mutex_.unlock();
+ /* Poller thread will quit, wait for it to avoid warning */
+ poller_.wait();
+}
+
+void GLibMainloopOnQEventLoop::checkAndDispatch()
+{
+ poller_.mutex_.lock();
+ while (!g_main_context_acquire(poller_.ctx_))
+ {
+ /* In normal circumstances context is acquired right away */
+ }
+ if (g_main_depth() > 0)
+ {
+ /* This should not happen, but if it does warn about nested event loops
+ * so the issue can be fixed before the harm is done. To identify root
+ * cause, put breakpoint here and take backtrace when it hits. Look for
+ * calls to exec() and processEvents() functions. Refactor the code so
+ * it does not spin additional event loops.
+ *
+ * Ignoring this warning will lead to very strange and hard to debug
+ * problems in the future.
+ */
+ qWarning("Nested GLib event loop detected");
+ }
+ if (g_main_context_check(poller_.ctx_, poller_.priority_,
+ poller_.fds_, poller_.nfds_))
+ {
+ g_main_context_dispatch(poller_.ctx_);
+ }
+ g_main_context_release(poller_.ctx_);
+ /* Start next iteration in GLibPoller thread */
+ poller_.dispatched_.wakeOne();
+ poller_.mutex_.unlock();
+}
+
+void GLibMainloopOnQEventLoop::setup(QObject *parent)
+{
+ /* Schedule event loop action so we can check if Qt runs GLib mainloop */
+ QTimer::singleShot(0, [parent]() {
+ if (g_main_depth() == 0)
+ {
+ /* Not running inside GLib mainloop, actually setup */
+ new GLibMainloopOnQEventLoop(parent);
+ }
+ });
+}