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.
This commit is contained in:
Tomasz Moń 2022-07-23 19:03:30 +02:00
parent 5a8977acd2
commit 18e08d04d1
No known key found for this signature in database
GPG Key ID: 92BA8820D4D517C8
6 changed files with 192 additions and 0 deletions

View File

@ -175,6 +175,7 @@ set(WIRESHARK_QT_HEADERS
../qt/funnel_string_dialog.h
../qt/funnel_text_dialog.h
../qt/geometry_state_dialog.h
../qt/glib_mainloop_on_qeventloop.h
../qt/import_text_dialog.h
../qt/interface_frame.h
../qt/interface_toolbar_reader.h
@ -394,6 +395,7 @@ set(WIRESHARK_QT_SRC
../qt/funnel_string_dialog.cpp
../qt/funnel_text_dialog.cpp
../qt/geometry_state_dialog.cpp
../qt/glib_mainloop_on_qeventloop.cpp
../qt/import_text_dialog.cpp
../qt/interface_frame.cpp
../qt/interface_toolbar_reader.cpp

View File

@ -81,6 +81,7 @@
#include "ui/qt/utils/color_utils.h"
#include "ui/qt/coloring_rules_dialog.h"
#include "ui/qt/endpoint_dialog.h"
#include "ui/qt/glib_mainloop_on_qeventloop.h"
#include "ui/logray/logray_main_window.h"
#include "ui/qt/simple_dialog.h"
#include "ui/qt/simple_statistics_dialog.h"
@ -687,6 +688,8 @@ int main(int argc, char *qt_argv[])
// Init the main window (and splash)
main_w = new(LograyMainWindow);
main_w->show();
// Setup GLib mainloop on Qt event loop to enable GLib and GIO watches
GLibMainloopOnQEventLoop::setup(main_w);
// We may not need a queued connection here but it would seem to make sense
// to force the issue.
main_w->connect(&ls_app, SIGNAL(openCaptureFile(QString,QString,unsigned int)),

View File

@ -178,6 +178,7 @@ set(WIRESHARK_QT_HEADERS
funnel_string_dialog.h
funnel_text_dialog.h
geometry_state_dialog.h
glib_mainloop_on_qeventloop.h
gsm_map_summary_dialog.h
iax2_analysis_dialog.h
import_text_dialog.h
@ -422,6 +423,7 @@ set(WIRESHARK_QT_SRC
funnel_string_dialog.cpp
funnel_text_dialog.cpp
geometry_state_dialog.cpp
glib_mainloop_on_qeventloop.cpp
iax2_analysis_dialog.cpp
import_text_dialog.cpp
interface_frame.cpp

View File

@ -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);
}
});
}

View File

@ -0,0 +1,57 @@
/** @file
*
* Copyright 2022 Tomasz Mon <desowin@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef GLIB_MAINLOOP_ON_QEVENTLOOP_H
#define GLIB_MAINLOOP_ON_QEVENTLOOP_H
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include <glib.h>
class GLibPoller : public QThread
{
Q_OBJECT
protected:
explicit GLibPoller(GMainContext *context);
~GLibPoller();
void run() override;
QMutex mutex_;
QWaitCondition dispatched_;
GMainContext *ctx_;
gint priority_;
GPollFD *fds_;
gint allocated_fds_, nfds_;
signals:
void polled(void);
friend class GLibMainloopOnQEventLoop;
};
class GLibMainloopOnQEventLoop : public QObject
{
Q_OBJECT
protected:
explicit GLibMainloopOnQEventLoop(QObject *parent);
~GLibMainloopOnQEventLoop();
protected slots:
void checkAndDispatch();
public:
static void setup(QObject *parent);
protected:
GLibPoller poller_;
};
#endif /* GLIB_MAINLOOP_ON_QEVENTLOOP_H */

View File

@ -81,6 +81,7 @@
#include "ui/qt/utils/color_utils.h"
#include "ui/qt/coloring_rules_dialog.h"
#include "ui/qt/endpoint_dialog.h"
#include "ui/qt/glib_mainloop_on_qeventloop.h"
#include "ui/qt/wireshark_main_window.h"
#include "ui/qt/response_time_delay_dialog.h"
#include "ui/qt/service_response_time_dialog.h"
@ -689,6 +690,8 @@ int main(int argc, char *qt_argv[])
// Init the main window (and splash)
main_w = new(WiresharkMainWindow);
main_w->show();
// Setup GLib mainloop on Qt event loop to enable GLib and GIO watches
GLibMainloopOnQEventLoop::setup(main_w);
// We may not need a queued connection here but it would seem to make sense
// to force the issue.
main_w->connect(&ws_app, SIGNAL(openCaptureFile(QString,QString,unsigned int)),