1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
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);
}
});
}
|