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 | |
16 | QT_BEGIN_NAMESPACE |
17 | |
18 | class QQmlThreadPrivate : public QThread |
19 | { |
20 | public: |
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 | |
47 | protected: |
48 | bool event(QEvent *) override; |
49 | |
50 | private: |
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 | |
62 | QQmlThreadPrivate::MainObject::MainObject(QQmlThreadPrivate *p) |
63 | : p(p) |
64 | { |
65 | } |
66 | |
67 | // Trigger mainEvent in main thread. Must be called from thread. |
68 | void 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. |
77 | void 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 | |
85 | bool QQmlThreadPrivate::MainObject::event(QEvent *e) |
86 | { |
87 | if (e->type() == QEvent::User) |
88 | p->mainEvent(); |
89 | return QObject::event(event: e); |
90 | } |
91 | |
92 | QQmlThreadPrivate::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 | |
102 | bool QQmlThreadPrivate::event(QEvent *e) |
103 | { |
104 | if (e->type() == QEvent::User) |
105 | threadEvent(); |
106 | return QThread::event(event: e); |
107 | } |
108 | |
109 | void 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 | |
136 | void 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 | |
171 | QQmlThread::QQmlThread() |
172 | : d(new QQmlThreadPrivate(this)) |
173 | { |
174 | } |
175 | |
176 | QQmlThread::~QQmlThread() |
177 | { |
178 | delete d; |
179 | } |
180 | |
181 | /*! |
182 | \internal |
183 | Starts the actual worker thread. |
184 | */ |
185 | void QQmlThread::startup() |
186 | { |
187 | d->start(); |
188 | d->moveToThread(thread: d); |
189 | } |
190 | |
191 | void 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 | |
218 | bool QQmlThread::isShutdown() const |
219 | { |
220 | return d->m_shutdown; |
221 | } |
222 | |
223 | QMutex &QQmlThread::mutex() |
224 | { |
225 | return d->mutex(); |
226 | } |
227 | |
228 | void QQmlThread::lock() |
229 | { |
230 | d->lock(); |
231 | } |
232 | |
233 | void QQmlThread::unlock() |
234 | { |
235 | d->unlock(); |
236 | } |
237 | |
238 | void QQmlThread::wakeOne() |
239 | { |
240 | d->wakeOne(); |
241 | } |
242 | |
243 | void QQmlThread::wait() |
244 | { |
245 | d->wait(); |
246 | } |
247 | |
248 | bool QQmlThread::isThisThread() const |
249 | { |
250 | return QThread::currentThreadId() == static_cast<QThreadPrivate *>(QObjectPrivate::get(o: d))->threadData.loadRelaxed()->threadId.loadRelaxed(); |
251 | } |
252 | |
253 | QThread *QQmlThread::thread() const |
254 | { |
255 | return const_cast<QThread *>(static_cast<const QThread *>(d)); |
256 | } |
257 | |
258 | void 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 | */ |
302 | void 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 | |
337 | void 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 | |
352 | void 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 | */ |
376 | void 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 | |
403 | QT_END_NAMESPACE |
404 | |