1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29
30#include <QtCore>
31#include <QtTest/QtTest>
32
33#include "emulationdetector.h"
34
35enum { OneMinute = 60 * 1000,
36 TwoMinutes = OneMinute * 2 };
37
38
39struct Functor
40{
41 void operator()() const {};
42};
43
44class tst_QObjectRace: public QObject
45{
46 Q_OBJECT
47private slots:
48 void moveToThreadRace();
49 void destroyRace();
50 void disconnectRace();
51};
52
53class RaceObject : public QObject
54{
55 Q_OBJECT
56 QList<QThread *> threads;
57 int count;
58
59public:
60 RaceObject()
61 : count(0)
62 { }
63
64 void addThread(QThread *thread)
65 { threads.append(t: thread); }
66
67public slots:
68 void theSlot()
69 {
70 enum { step = 35 };
71 if ((++count % step) == 0) {
72 QThread *nextThread = threads.at(i: (count / step) % threads.size());
73 moveToThread(thread: nextThread);
74 }
75 }
76
77 void destroSlot() {
78 emit theSignal();
79 }
80signals:
81 void theSignal();
82};
83
84class RaceThread : public QThread
85{
86 Q_OBJECT
87 RaceObject *object;
88 QElapsedTimer stopWatch;
89
90public:
91 RaceThread()
92 : object(0)
93 { }
94
95 void setObject(RaceObject *o)
96 {
97 object = o;
98 object->addThread(thread: this);
99 }
100
101 void start() {
102 stopWatch.start();
103 QThread::start();
104 }
105
106 void run() {
107 QTimer zeroTimer;
108 connect(sender: &zeroTimer, SIGNAL(timeout()), receiver: object, SLOT(theSlot()));
109 connect(sender: &zeroTimer, SIGNAL(timeout()), receiver: this, SLOT(checkStopWatch()), Qt::DirectConnection);
110 zeroTimer.start(msec: 0);
111 (void) exec();
112 }
113
114signals:
115 void theSignal();
116
117private slots:
118 void checkStopWatch()
119 {
120 if (stopWatch.elapsed() >= 5000)
121 quit();
122
123 QObject o;
124 connect(sender: &o, SIGNAL(destroyed()) , receiver: object, SLOT(destroSlot()));
125 connect(sender: object, SIGNAL(destroyed()) , receiver: &o, SLOT(deleteLater()));
126 }
127};
128
129void tst_QObjectRace::moveToThreadRace()
130{
131 RaceObject *object = new RaceObject;
132
133 enum { ThreadCount = 6 };
134 RaceThread *threads[ThreadCount];
135 for (int i = 0; i < ThreadCount; ++i) {
136 threads[i] = new RaceThread;
137 threads[i]->setObject(object);
138 }
139
140 object->moveToThread(thread: threads[0]);
141
142 for (int i = 0; i < ThreadCount; ++i)
143 threads[i]->start();
144
145 while(!threads[0]->isFinished()) {
146 QPointer<RaceObject> foo (object);
147 QObject o;
148 connect(sender: &o, SIGNAL(destroyed()) , receiver: object, SLOT(destroSlot()));
149 connect(sender: object, SIGNAL(destroyed()) , receiver: &o, SLOT(deleteLater()));
150 QTest::qWait(ms: 10);
151 }
152 // the other threads should finish pretty quickly now
153 for (int i = 1; i < ThreadCount; ++i)
154 QVERIFY(threads[i]->wait(300));
155
156 for (int i = 0; i < ThreadCount; ++i)
157 delete threads[i];
158 delete object;
159}
160
161
162class MyObject : public QObject
163{ Q_OBJECT
164 bool ok;
165 public:
166 MyObject() : ok(true) {}
167 ~MyObject() { Q_ASSERT(ok); ok = false; }
168 public slots:
169 void slot1() { Q_ASSERT(ok); }
170 void slot2() { Q_ASSERT(ok); }
171 void slot3() { Q_ASSERT(ok); }
172 void slot4() { Q_ASSERT(ok); }
173 void slot5() { Q_ASSERT(ok); }
174 void slot6() { Q_ASSERT(ok); }
175 void slot7() { Q_ASSERT(ok); }
176 signals:
177 void signal1();
178 void signal2();
179 void signal3();
180 void signal4();
181 void signal5();
182 void signal6();
183 void signal7();
184};
185
186namespace {
187const char *_slots[] = { SLOT(slot1()) , SLOT(slot2()) , SLOT(slot3()),
188 SLOT(slot4()) , SLOT(slot5()) , SLOT(slot6()),
189 SLOT(slot7()) };
190
191const char *_signals[] = { SIGNAL(signal1()), SIGNAL(signal2()), SIGNAL(signal3()),
192 SIGNAL(signal4()), SIGNAL(signal5()), SIGNAL(signal6()),
193 SIGNAL(signal7()) };
194
195typedef void (MyObject::*PMFType)();
196const PMFType _slotsPMF[] = { &MyObject::slot1, &MyObject::slot2, &MyObject::slot3,
197 &MyObject::slot4, &MyObject::slot5, &MyObject::slot6,
198 &MyObject::slot7 };
199
200const PMFType _signalsPMF[] = { &MyObject::signal1, &MyObject::signal2, &MyObject::signal3,
201 &MyObject::signal4, &MyObject::signal5, &MyObject::signal6,
202 &MyObject::signal7 };
203
204}
205
206class DestroyThread : public QThread
207{
208 Q_OBJECT
209 MyObject **objects;
210 int number;
211
212public:
213 void setObjects(MyObject **o, int n)
214 {
215 objects = o;
216 number = n;
217 for(int i = 0; i < number; i++)
218 objects[i]->moveToThread(thread: this);
219 }
220
221 void run() {
222 for (int i = number-1; i >= 0; --i) {
223 /* Do some more connection and disconnection between object in this thread that have not been destroyed yet */
224
225 const int nAlive = i+1;
226 connect (sender: objects[((i+1)*31) % nAlive], signal: _signals[(12*i)%7], receiver: objects[((i+2)*37) % nAlive], member: _slots[(15*i+2)%7] );
227 disconnect(sender: objects[((i+1)*31) % nAlive], signal: _signals[(12*i)%7], receiver: objects[((i+2)*37) % nAlive], member: _slots[(15*i+2)%7] );
228
229 connect (sender: objects[((i+4)*41) % nAlive], signal: _signalsPMF[(18*i)%7], receiver: objects[((i+5)*43) % nAlive], slot: _slotsPMF[(19*i+2)%7] );
230 disconnect(sender: objects[((i+4)*41) % nAlive], signal: _signalsPMF[(18*i)%7], receiver: objects[((i+5)*43) % nAlive], slot: _slotsPMF[(19*i+2)%7] );
231
232 QMetaObject::Connection c = connect(sender: objects[((i+5)*43) % nAlive], signal: _signalsPMF[(9*i+1)%7], slot: Functor());
233
234 for (int f = 0; f < 7; ++f)
235 emit (objects[i]->*_signalsPMF[f])();
236
237 disconnect(c);
238
239 disconnect(sender: objects[i], signal: _signalsPMF[(10*i+5)%7], receiver: 0, zero: 0);
240 disconnect(sender: objects[i], signal: _signals[(11*i+6)%7], receiver: 0, member: 0);
241
242 disconnect(sender: objects[i], signal: 0, receiver: objects[(i*17+6) % nAlive], member: 0);
243 if (i%4 == 1) {
244 disconnect(sender: objects[i], signal: 0, receiver: 0, member: 0);
245 }
246
247 delete objects[i];
248 }
249
250 //run the possible queued slots
251 qApp->processEvents();
252 }
253};
254
255#define EXTRA_THREAD_WAIT 3000
256#define MAIN_THREAD_WAIT TwoMinutes
257
258void tst_QObjectRace::destroyRace()
259{
260 if (EmulationDetector::isRunningArmOnX86())
261 QSKIP("Test is too slow to run on emulator");
262
263 enum { ThreadCount = 10, ObjectCountPerThread = 2777,
264 ObjectCount = ThreadCount * ObjectCountPerThread };
265
266 MyObject *objects[ObjectCount];
267 for (int i = 0; i < ObjectCount; ++i)
268 objects[i] = new MyObject;
269
270
271 for (int i = 0; i < ObjectCount * 17; ++i) {
272 connect(sender: objects[(i*13) % ObjectCount], signal: _signals[(2*i)%7],
273 receiver: objects[((i+2)*17) % ObjectCount], member: _slots[(3*i+2)%7] );
274 connect(sender: objects[((i+6)*23) % ObjectCount], signal: _signals[(5*i+4)%7],
275 receiver: objects[((i+8)*41) % ObjectCount], member: _slots[(i+6)%7] );
276
277 connect(sender: objects[(i*67) % ObjectCount], signal: _signalsPMF[(2*i)%7],
278 receiver: objects[((i+1)*71) % ObjectCount], slot: _slotsPMF[(3*i+2)%7] );
279 connect(sender: objects[((i+3)*73) % ObjectCount], signal: _signalsPMF[(5*i+4)%7],
280 context: objects[((i+5)*79) % ObjectCount], slot: Functor() );
281 }
282
283 DestroyThread *threads[ThreadCount];
284 for (int i = 0; i < ThreadCount; ++i) {
285 threads[i] = new DestroyThread;
286 threads[i]->setObjects(o: objects + i*ObjectCountPerThread, n: ObjectCountPerThread);
287 }
288
289 for (int i = 0; i < ThreadCount; ++i)
290 threads[i]->start();
291
292 QVERIFY(threads[0]->wait(MAIN_THREAD_WAIT));
293 // the other threads should finish pretty quickly now
294 for (int i = 1; i < ThreadCount; ++i)
295 QVERIFY(threads[i]->wait(EXTRA_THREAD_WAIT));
296
297 for (int i = 0; i < ThreadCount; ++i)
298 delete threads[i];
299}
300
301static QAtomicInteger<unsigned> countedStructObjectsCount;
302struct CountedFunctor
303{
304 CountedFunctor() : destroyed(false) { countedStructObjectsCount.fetchAndAddRelaxed(valueToAdd: 1); }
305 CountedFunctor(const CountedFunctor &) : destroyed(false) { countedStructObjectsCount.fetchAndAddRelaxed(valueToAdd: 1); }
306 CountedFunctor &operator=(const CountedFunctor &) { return *this; }
307 ~CountedFunctor() { destroyed = true; countedStructObjectsCount.fetchAndAddRelaxed(valueToAdd: -1);}
308 void operator()() const {QCOMPARE(destroyed, false);}
309
310private:
311 bool destroyed;
312};
313
314class DisconnectRaceSenderObject : public QObject
315{
316 Q_OBJECT
317signals:
318 void theSignal();
319};
320
321class DisconnectRaceThread : public QThread
322{
323 Q_OBJECT
324
325 DisconnectRaceSenderObject *sender;
326 bool emitSignal;
327public:
328 DisconnectRaceThread(DisconnectRaceSenderObject *s, bool emitIt)
329 : QThread(), sender(s), emitSignal(emitIt)
330 {
331 }
332
333 void run()
334 {
335 while (!isInterruptionRequested()) {
336 QMetaObject::Connection conn = connect(sender, signal: &DisconnectRaceSenderObject::theSignal,
337 context: sender, slot: CountedFunctor(), type: Qt::BlockingQueuedConnection);
338 if (emitSignal)
339 emit sender->theSignal();
340 disconnect(conn);
341 yieldCurrentThread();
342 }
343 }
344};
345
346class DeleteReceiverRaceSenderThread : public QThread
347{
348 Q_OBJECT
349
350 DisconnectRaceSenderObject *sender;
351public:
352 DeleteReceiverRaceSenderThread(DisconnectRaceSenderObject *s)
353 : QThread(), sender(s)
354 {
355 }
356
357 void run()
358 {
359 while (!isInterruptionRequested()) {
360 emit sender->theSignal();
361 yieldCurrentThread();
362 }
363 }
364};
365
366class DeleteReceiverRaceReceiver : public QObject
367{
368 Q_OBJECT
369
370 DisconnectRaceSenderObject *sender;
371 QObject *receiver;
372 QTimer *timer;
373public:
374 DeleteReceiverRaceReceiver(DisconnectRaceSenderObject *s)
375 : QObject(), sender(s), receiver(0)
376 {
377 timer = new QTimer(this);
378 connect(sender: timer, signal: &QTimer::timeout, receiver: this, slot: &DeleteReceiverRaceReceiver::onTimeout);
379 timer->start(msec: 1);
380 }
381 ~DeleteReceiverRaceReceiver()
382 {
383 delete receiver;
384 }
385
386 void onTimeout()
387 {
388 if (receiver)
389 delete receiver;
390 receiver = new QObject;
391 connect(sender, signal: &DisconnectRaceSenderObject::theSignal, context: receiver, slot: CountedFunctor(), type: Qt::BlockingQueuedConnection);
392 }
393};
394
395class DeleteReceiverRaceReceiverThread : public QThread
396{
397 Q_OBJECT
398
399 DisconnectRaceSenderObject *sender;
400public:
401 DeleteReceiverRaceReceiverThread(DisconnectRaceSenderObject *s)
402 : QThread(), sender(s)
403 {
404 }
405
406 void run()
407 {
408 QScopedPointer<DeleteReceiverRaceReceiver> receiver(new DeleteReceiverRaceReceiver(sender));
409 exec();
410 }
411};
412
413void tst_QObjectRace::disconnectRace()
414{
415 enum { ThreadCount = 20, TimeLimit = 3000 };
416
417 QCOMPARE(countedStructObjectsCount.loadRelaxed(), 0u);
418
419 {
420 QScopedPointer<DisconnectRaceSenderObject> sender(new DisconnectRaceSenderObject());
421 QScopedPointer<QThread> senderThread(new QThread());
422 senderThread->start();
423 sender->moveToThread(thread: senderThread.data());
424
425 DisconnectRaceThread *threads[ThreadCount];
426 for (int i = 0; i < ThreadCount; ++i) {
427 threads[i] = new DisconnectRaceThread(sender.data(), !(i % 10));
428 threads[i]->start();
429 }
430
431 QTest::qWait(ms: TimeLimit);
432
433 for (int i = 0; i < ThreadCount; ++i) {
434 threads[i]->requestInterruption();
435 QVERIFY(threads[i]->wait());
436 delete threads[i];
437 }
438
439 senderThread->quit();
440 QVERIFY(senderThread->wait());
441 }
442
443 QCOMPARE(countedStructObjectsCount.loadRelaxed(), 0u);
444
445 {
446 QScopedPointer<DisconnectRaceSenderObject> sender(new DisconnectRaceSenderObject());
447 QScopedPointer<DeleteReceiverRaceSenderThread> senderThread(new DeleteReceiverRaceSenderThread(sender.data()));
448 senderThread->start();
449 sender->moveToThread(thread: senderThread.data());
450
451 DeleteReceiverRaceReceiverThread *threads[ThreadCount];
452 for (int i = 0; i < ThreadCount; ++i) {
453 threads[i] = new DeleteReceiverRaceReceiverThread(sender.data());
454 threads[i]->start();
455 }
456
457 QTest::qWait(ms: TimeLimit);
458
459 senderThread->requestInterruption();
460 QVERIFY(senderThread->wait());
461
462 for (int i = 0; i < ThreadCount; ++i) {
463 threads[i]->quit();
464 QVERIFY(threads[i]->wait());
465 delete threads[i];
466 }
467 }
468
469 QCOMPARE(countedStructObjectsCount.loadRelaxed(), 0u);
470}
471
472QTEST_MAIN(tst_QObjectRace)
473#include "tst_qobjectrace.moc"
474

source code of qtbase/tests/auto/other/qobjectrace/tst_qobjectrace.cpp