diff options
author | Tomasz Moń <desowin@gmail.com> | 2022-07-23 19:03:30 +0200 |
---|---|---|
committer | Tomasz Moń <desowin@gmail.com> | 2022-07-24 20:57:18 +0200 |
commit | 18e08d04d1f04f027fb2a911abfadb2117f8d10d (patch) | |
tree | c0be31e3842ac56a2c89b0d968638028dcbd3314 /ui/qt/glib_mainloop_on_qeventloop.cpp | |
parent | 5a8977acd278616ddb8c6604d31355b8dd369c7d (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.cpp | 125 |
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); + } + }); +} |