1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qqmlthread_p.h"
5
6#include <private/qfieldlist_p.h>
7
8#include <QtCore/qmutex.h>
9#include <QtCore/qthread.h>
10#include <QtCore/qcoreevent.h>
11#include <QtCore/qwaitcondition.h>
12#include <QtCore/qcoreapplication.h>
13
14#include <QtCore/private/qthread_p.h>
15
16QT_BEGIN_NAMESPACE
17
18class QQmlThreadPrivate : public QThread
19{
20public:
21 QQmlThreadPrivate(QQmlThread *);
22 QQmlThread *q;
23
24 inline QMutex &mutex() { return _mutex; }
25 inline void lock() { _mutex.lock(); }
26 inline void unlock() { _mutex.unlock(); }
27 inline void wait() { _wait.wait(lockedMutex: &_mutex); }
28 inline void wakeOne() { _wait.wakeOne(); }
29
30 bool m_threadProcessing; // Set when the thread is processing messages
31 bool m_mainProcessing; // Set when the main thread is processing messages
32 bool m_shutdown; // Set by main thread to request a shutdown
33 bool m_mainThreadWaiting; // Set by main thread if it is waiting for the message queue to empty
34
35 typedef QFieldList<QQmlThread::Message, &QQmlThread::Message::next> MessageList;
36 MessageList threadList;
37 MessageList mainList;
38
39 QQmlThread::Message *mainSync;
40
41 void triggerMainEvent();
42 void triggerThreadEvent();
43
44 void mainEvent();
45 void threadEvent();
46
47protected:
48 bool event(QEvent *) override;
49
50private:
51 struct MainObject : public QObject {
52 MainObject(QQmlThreadPrivate *p);
53 bool event(QEvent *e) override;
54 QQmlThreadPrivate *p;
55 };
56 MainObject m_mainObject;
57
58 QMutex _mutex;
59 QWaitCondition _wait;
60};
61
62QQmlThreadPrivate::MainObject::MainObject(QQmlThreadPrivate *p)
63: p(p)
64{
65}
66
67// Trigger mainEvent in main thread. Must be called from thread.
68void QQmlThreadPrivate::triggerMainEvent()
69{
70#if QT_CONFIG(thread)
71 Q_ASSERT(q->isThisThread());
72#endif
73 QCoreApplication::postEvent(receiver: &m_mainObject, event: new QEvent(QEvent::User));
74}
75
76// Trigger even in thread. Must be called from main thread.
77void QQmlThreadPrivate::triggerThreadEvent()
78{
79#if QT_CONFIG(thread)
80 Q_ASSERT(!q->isThisThread());
81#endif
82 QCoreApplication::postEvent(receiver: this, event: new QEvent(QEvent::User));
83}
84
85bool QQmlThreadPrivate::MainObject::event(QEvent *e)
86{
87 if (e->type() == QEvent::User)
88 p->mainEvent();
89 return QObject::event(event: e);
90}
91
92QQmlThreadPrivate::QQmlThreadPrivate(QQmlThread *q)
93: q(q), m_threadProcessing(false), m_mainProcessing(false), m_shutdown(false),
94 m_mainThreadWaiting(false), mainSync(nullptr), m_mainObject(this)
95{
96 setObjectName(QStringLiteral("QQmlThread"));
97 // This size is aligned with the recursion depth limits in the parser/codegen. In case of
98 // absurd content we want to hit the recursion checks instead of running out of stack.
99 setStackSize(8 * 1024 * 1024);
100}
101
102bool QQmlThreadPrivate::event(QEvent *e)
103{
104 if (e->type() == QEvent::User)
105 threadEvent();
106 return QThread::event(event: e);
107}
108
109void QQmlThreadPrivate::mainEvent()
110{
111 lock();
112
113 m_mainProcessing = true;
114
115 while (!mainList.isEmpty() || mainSync) {
116 bool isSync = mainSync != nullptr;
117 QQmlThread::Message *message = isSync?mainSync:mainList.takeFirst();
118 unlock();
119
120 message->call(q);
121 delete message;
122
123 lock();
124
125 if (isSync) {
126 mainSync = nullptr;
127 wakeOne();
128 }
129 }
130
131 m_mainProcessing = false;
132
133 unlock();
134}
135
136void QQmlThreadPrivate::threadEvent()
137{
138 lock();
139
140 for (;;) {
141 if (!threadList.isEmpty()) {
142 m_threadProcessing = true;
143
144 QQmlThread::Message *message = threadList.first();
145
146 unlock();
147
148 message->call(q);
149
150 lock();
151
152 delete threadList.takeFirst();
153 } else if (m_shutdown) {
154 quit();
155 wakeOne();
156 unlock();
157
158 return;
159 } else {
160 wakeOne();
161
162 m_threadProcessing = false;
163
164 unlock();
165
166 return;
167 }
168 }
169}
170
171QQmlThread::QQmlThread()
172: d(new QQmlThreadPrivate(this))
173{
174}
175
176QQmlThread::~QQmlThread()
177{
178 delete d;
179}
180
181/*!
182 \internal
183 Starts the actual worker thread.
184 */
185void QQmlThread::startup()
186{
187 d->start();
188 d->moveToThread(thread: d);
189}
190
191void QQmlThread::shutdown()
192{
193 d->lock();
194 Q_ASSERT(!d->m_shutdown);
195
196 d->m_shutdown = true;
197 for (;;) {
198 if (d->mainSync || !d->mainList.isEmpty()) {
199 d->unlock();
200 d->mainEvent();
201 d->lock();
202 } else if (!d->threadList.isEmpty()) {
203 d->wait();
204 } else {
205 break;
206 }
207 }
208
209 if (QCoreApplication::closingDown())
210 d->quit();
211 else
212 d->triggerThreadEvent();
213
214 d->unlock();
215 d->QThread::wait();
216}
217
218bool QQmlThread::isShutdown() const
219{
220 return d->m_shutdown;
221}
222
223QMutex &QQmlThread::mutex()
224{
225 return d->mutex();
226}
227
228void QQmlThread::lock()
229{
230 d->lock();
231}
232
233void QQmlThread::unlock()
234{
235 d->unlock();
236}
237
238void QQmlThread::wakeOne()
239{
240 d->wakeOne();
241}
242
243void QQmlThread::wait()
244{
245 d->wait();
246}
247
248bool QQmlThread::isThisThread() const
249{
250 return QThread::currentThreadId() == static_cast<QThreadPrivate *>(QObjectPrivate::get(o: d))->threadData.loadRelaxed()->threadId.loadRelaxed();
251}
252
253QThread *QQmlThread::thread() const
254{
255 return const_cast<QThread *>(static_cast<const QThread *>(d));
256}
257
258void QQmlThread::internalCallMethodInThread(Message *message)
259{
260#if !QT_CONFIG(thread)
261 message->call(this);
262 delete message;
263 return;
264#endif
265
266 Q_ASSERT(!isThisThread());
267 d->lock();
268 Q_ASSERT(d->m_mainThreadWaiting == false);
269
270 bool wasEmpty = d->threadList.isEmpty();
271 d->threadList.append(v: message);
272 if (wasEmpty && d->m_threadProcessing == false)
273 d->triggerThreadEvent();
274
275 d->m_mainThreadWaiting = true;
276
277 do {
278 if (d->mainSync) {
279 QQmlThread::Message *message = d->mainSync;
280 unlock();
281 message->call(this);
282 delete message;
283 lock();
284 d->mainSync = nullptr;
285 wakeOne();
286 } else {
287 d->wait();
288 }
289 } while (d->mainSync || !d->threadList.isEmpty());
290
291 d->m_mainThreadWaiting = false;
292 d->unlock();
293}
294
295/*!
296 \internal
297 \note This method needs to run in the worker/QQmlThread
298
299 This runs \a message in the main thread, and blocks the
300 worker thread until the call has completed
301 */
302void QQmlThread::internalCallMethodInMain(Message *message)
303{
304#if !QT_CONFIG(thread)
305 message->call(this);
306 delete message;
307 return;
308#endif
309
310 Q_ASSERT(isThisThread());
311
312 d->lock();
313
314 Q_ASSERT(d->mainSync == nullptr);
315 d->mainSync = message;
316
317 if (d->m_mainThreadWaiting) {
318 d->wakeOne();
319 } else if (d->m_mainProcessing) {
320 // Do nothing - it is already looping
321 } else {
322 d->triggerMainEvent();
323 }
324
325 while (d->mainSync) {
326 if (d->m_shutdown) {
327 delete d->mainSync;
328 d->mainSync = nullptr;
329 break;
330 }
331 d->wait();
332 }
333
334 d->unlock();
335}
336
337void QQmlThread::internalPostMethodToThread(Message *message)
338{
339#if !QT_CONFIG(thread)
340 internalPostMethodToMain(message);
341 return;
342#endif
343 Q_ASSERT(!isThisThread());
344 d->lock();
345 bool wasEmpty = d->threadList.isEmpty();
346 d->threadList.append(v: message);
347 if (wasEmpty && d->m_threadProcessing == false)
348 d->triggerThreadEvent();
349 d->unlock();
350}
351
352void QQmlThread::internalPostMethodToMain(Message *message)
353{
354#if QT_CONFIG(thread)
355 Q_ASSERT(isThisThread());
356#endif
357 d->lock();
358 bool wasEmpty = d->mainList.isEmpty();
359 d->mainList.append(v: message);
360 if (wasEmpty && d->m_mainProcessing == false)
361 d->triggerMainEvent();
362 d->unlock();
363}
364
365/*!
366 \internal
367 \note This method must be called in the main thread
368 \warning This method requires that the lock is held!
369
370 A call to this method will either:
371 - run a message requested to run synchronously on the main thread if there is one
372 (and return afterrwards),
373 - wait for the worker thread to notify it if the worker thread has pending work,
374 - or simply return if neither of the conditions above hold
375 */
376void QQmlThread::waitForNextMessage()
377{
378#if QT_CONFIG(thread)
379 Q_ASSERT(!isThisThread());
380#endif
381 Q_ASSERT(d->m_mainThreadWaiting == false);
382
383 d->m_mainThreadWaiting = true;
384
385 if (d->mainSync || !d->threadList.isEmpty()) {
386 if (d->mainSync) {
387 QQmlThread::Message *message = d->mainSync;
388 unlock();
389 message->call(this);
390 delete message;
391 lock();
392 d->mainSync = nullptr;
393 wakeOne();
394 } else {
395 d->wait();
396 }
397 }
398
399 d->m_mainThreadWaiting = false;
400}
401
402
403QT_END_NAMESPACE
404

source code of qtdeclarative/src/qml/qml/ftw/qqmlthread.cpp