1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Copyright (C) 2016 Intel Corporation.
5** Contact: https://www.qt.io/licensing/
6**
7** This file is part of the test suite of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:GPL-EXCEPT$
10** Commercial License Usage
11** Licensees holding valid commercial Qt licenses may use this file in
12** accordance with the commercial license agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and The Qt Company. For licensing terms
15** and conditions see https://www.qt.io/terms-conditions. For further
16** information use the contact form at https://www.qt.io/contact-us.
17**
18** GNU General Public License Usage
19** Alternatively, this file may be used under the terms of the GNU
20** General Public License version 3 as published by the Free Software
21** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
22** included in the packaging of this file. Please review the following
23** information to ensure the GNU General Public License requirements will
24** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25**
26** $QT_END_LICENSE$
27**
28****************************************************************************/
29#include <QtTest/QtTest>
30#include <qelapsedtimer.h>
31#include <qthreadpool.h>
32#include <qstring.h>
33#include <qmutex.h>
34
35#ifdef Q_OS_UNIX
36#include <unistd.h>
37#endif
38
39typedef void (*FunctionPointer)();
40
41class FunctionPointerTask : public QRunnable
42{
43public:
44 FunctionPointerTask(FunctionPointer function)
45 :function(function) {}
46 void run() { function(); }
47private:
48 FunctionPointer function;
49};
50
51QRunnable *createTask(FunctionPointer pointer)
52{
53 return new FunctionPointerTask(pointer);
54}
55
56class tst_QThreadPool : public QObject
57{
58 Q_OBJECT
59public:
60 tst_QThreadPool();
61 ~tst_QThreadPool();
62
63 static QMutex *functionTestMutex;
64
65private slots:
66 void runFunction();
67 void runFunction2();
68 void createThreadRunFunction();
69 void runMultiple();
70 void waitcomplete();
71 void runTask();
72 void singleton();
73 void destruction();
74 void threadRecycling();
75 void expiryTimeout();
76 void expiryTimeoutRace();
77#ifndef QT_NO_EXCEPTIONS
78 void exceptions();
79#endif
80 void setMaxThreadCount_data();
81 void setMaxThreadCount();
82 void setMaxThreadCountStartsAndStopsThreads();
83 void reserveThread_data();
84 void reserveThread();
85 void releaseThread_data();
86 void releaseThread();
87 void reserveAndStart();
88 void start();
89 void tryStart();
90 void tryStartPeakThreadCount();
91 void tryStartCount();
92 void priorityStart_data();
93 void priorityStart();
94 void waitForDone();
95 void clear();
96 void clearWithAutoDelete();
97#if QT_DEPRECATED_SINCE(5, 9)
98 void cancel();
99#endif
100 void tryTake();
101 void waitForDoneTimeout();
102 void destroyingWaitsForTasksToFinish();
103 void stackSize();
104 void stressTest();
105 void takeAllAndIncreaseMaxThreadCount();
106 void waitForDoneAfterTake();
107 void threadReuse();
108
109private:
110 QMutex m_functionTestMutex;
111};
112
113
114QMutex *tst_QThreadPool::functionTestMutex = 0;
115
116tst_QThreadPool::tst_QThreadPool()
117{
118 tst_QThreadPool::functionTestMutex = &m_functionTestMutex;
119}
120
121tst_QThreadPool::~tst_QThreadPool()
122{
123 tst_QThreadPool::functionTestMutex = 0;
124}
125
126int testFunctionCount;
127
128void sleepTestFunction()
129{
130 QTest::qSleep(ms: 1000);
131 ++testFunctionCount;
132}
133
134void emptyFunct()
135{
136
137}
138
139void noSleepTestFunction()
140{
141 ++testFunctionCount;
142}
143
144void sleepTestFunctionMutex()
145{
146 Q_ASSERT(tst_QThreadPool::functionTestMutex);
147 QTest::qSleep(ms: 1000);
148 tst_QThreadPool::functionTestMutex->lock();
149 ++testFunctionCount;
150 tst_QThreadPool::functionTestMutex->unlock();
151}
152
153void noSleepTestFunctionMutex()
154{
155 Q_ASSERT(tst_QThreadPool::functionTestMutex);
156 tst_QThreadPool::functionTestMutex->lock();
157 ++testFunctionCount;
158 tst_QThreadPool::functionTestMutex->unlock();
159}
160
161void tst_QThreadPool::runFunction()
162{
163 {
164 QThreadPool manager;
165 testFunctionCount = 0;
166 manager.start(functionToRun: noSleepTestFunction);
167 }
168 QCOMPARE(testFunctionCount, 1);
169}
170
171void tst_QThreadPool::runFunction2()
172{
173 int localCount = 0;
174 {
175 QThreadPool manager;
176 manager.start(functionToRun: [&]() { ++localCount; });
177 }
178 QCOMPARE(localCount, 1);
179}
180
181void tst_QThreadPool::createThreadRunFunction()
182{
183 {
184 QThreadPool manager;
185 testFunctionCount = 0;
186 manager.start(functionToRun: noSleepTestFunction);
187 }
188
189 QCOMPARE(testFunctionCount, 1);
190}
191
192void tst_QThreadPool::runMultiple()
193{
194 const int runs = 10;
195
196 {
197 QThreadPool manager;
198 testFunctionCount = 0;
199 for (int i = 0; i < runs; ++i) {
200 manager.start(functionToRun: sleepTestFunctionMutex);
201 }
202 }
203 QCOMPARE(testFunctionCount, runs);
204
205 {
206 QThreadPool manager;
207 testFunctionCount = 0;
208 for (int i = 0; i < runs; ++i) {
209 manager.start(functionToRun: noSleepTestFunctionMutex);
210 }
211 }
212 QCOMPARE(testFunctionCount, runs);
213
214 {
215 QThreadPool manager;
216 for (int i = 0; i < 500; ++i)
217 manager.start(functionToRun: emptyFunct);
218 }
219}
220
221void tst_QThreadPool::waitcomplete()
222{
223 testFunctionCount = 0;
224 const int runs = 500;
225 for (int i = 0; i < 500; ++i) {
226 QThreadPool pool;
227 pool.start(functionToRun: noSleepTestFunction);
228 }
229 QCOMPARE(testFunctionCount, runs);
230}
231
232QAtomicInt ran; // bool
233class TestTask : public QRunnable
234{
235public:
236 void run()
237 {
238 ran.storeRelaxed(newValue: true);
239 }
240};
241
242void tst_QThreadPool::runTask()
243{
244 QThreadPool manager;
245 ran.storeRelaxed(newValue: false);
246 manager.start(runnable: new TestTask());
247 QTRY_VERIFY(ran.loadRelaxed());
248}
249
250/*
251 Test running via QThreadPool::globalInstance()
252*/
253void tst_QThreadPool::singleton()
254{
255 ran.storeRelaxed(newValue: false);
256 QThreadPool::globalInstance()->start(runnable: new TestTask());
257 QTRY_VERIFY(ran.loadRelaxed());
258}
259
260QAtomicInt *value = 0;
261class IntAccessor : public QRunnable
262{
263public:
264 void run()
265 {
266 for (int i = 0; i < 100; ++i) {
267 value->ref();
268 QTest::qSleep(ms: 1);
269 }
270 }
271};
272
273/*
274 Test that the ThreadManager destructor waits until
275 all threads have completed.
276*/
277void tst_QThreadPool::destruction()
278{
279 value = new QAtomicInt;
280 QThreadPool *threadManager = new QThreadPool();
281 threadManager->start(runnable: new IntAccessor());
282 threadManager->start(runnable: new IntAccessor());
283 delete threadManager;
284 delete value;
285 value = 0;
286}
287
288QSemaphore threadRecyclingSemaphore;
289QThread *recycledThread = 0;
290
291class ThreadRecorderTask : public QRunnable
292{
293public:
294 void run()
295 {
296 recycledThread = QThread::currentThread();
297 threadRecyclingSemaphore.release();
298 }
299};
300
301/*
302 Test that the thread pool really reuses threads.
303*/
304void tst_QThreadPool::threadRecycling()
305{
306 QThreadPool threadPool;
307
308 threadPool.start(runnable: new ThreadRecorderTask());
309 threadRecyclingSemaphore.acquire();
310 QThread *thread1 = recycledThread;
311
312 QTest::qSleep(ms: 100);
313
314 threadPool.start(runnable: new ThreadRecorderTask());
315 threadRecyclingSemaphore.acquire();
316 QThread *thread2 = recycledThread;
317 QCOMPARE(thread1, thread2);
318
319 QTest::qSleep(ms: 100);
320
321 threadPool.start(runnable: new ThreadRecorderTask());
322 threadRecyclingSemaphore.acquire();
323 QThread *thread3 = recycledThread;
324 QCOMPARE(thread2, thread3);
325}
326
327class ExpiryTimeoutTask : public QRunnable
328{
329public:
330 QThread *thread;
331 QAtomicInt runCount;
332 QSemaphore semaphore;
333
334 ExpiryTimeoutTask()
335 : thread(0), runCount(0)
336 {
337 setAutoDelete(false);
338 }
339
340 void run()
341 {
342 thread = QThread::currentThread();
343 runCount.ref();
344 semaphore.release();
345 }
346};
347
348void tst_QThreadPool::expiryTimeout()
349{
350 ExpiryTimeoutTask task;
351
352 QThreadPool threadPool;
353 threadPool.setMaxThreadCount(1);
354
355 int expiryTimeout = threadPool.expiryTimeout();
356 threadPool.setExpiryTimeout(1000);
357 QCOMPARE(threadPool.expiryTimeout(), 1000);
358
359 // run the task
360 threadPool.start(runnable: &task);
361 QVERIFY(task.semaphore.tryAcquire(1, 10000));
362 QCOMPARE(task.runCount.loadRelaxed(), 1);
363 QVERIFY(!task.thread->wait(100));
364 // thread should expire
365 QThread *firstThread = task.thread;
366 QVERIFY(task.thread->wait(10000));
367
368 // run task again, thread should be restarted
369 threadPool.start(runnable: &task);
370 QVERIFY(task.semaphore.tryAcquire(1, 10000));
371 QCOMPARE(task.runCount.loadRelaxed(), 2);
372 QVERIFY(!task.thread->wait(100));
373 // thread should expire again
374 QVERIFY(task.thread->wait(10000));
375
376 // thread pool should have reused the expired thread (instead of
377 // starting a new one)
378 QCOMPARE(firstThread, task.thread);
379
380 threadPool.setExpiryTimeout(expiryTimeout);
381 QCOMPARE(threadPool.expiryTimeout(), expiryTimeout);
382}
383
384void tst_QThreadPool::expiryTimeoutRace() // QTBUG-3786
385{
386#ifdef Q_OS_WIN
387 QSKIP("This test is unstable on Windows. See QTBUG-3786.");
388#endif
389 ExpiryTimeoutTask task;
390
391 QThreadPool threadPool;
392 threadPool.setMaxThreadCount(1);
393 threadPool.setExpiryTimeout(50);
394 const int numTasks = 20;
395 for (int i = 0; i < numTasks; ++i) {
396 threadPool.start(runnable: &task);
397 QThread::msleep(50); // exactly the same as the expiry timeout
398 }
399 QVERIFY(task.semaphore.tryAcquire(numTasks, 10000));
400 QCOMPARE(task.runCount.loadRelaxed(), numTasks);
401 QVERIFY(threadPool.waitForDone(2000));
402}
403
404#ifndef QT_NO_EXCEPTIONS
405class ExceptionTask : public QRunnable
406{
407public:
408 void run()
409 {
410 throw new int;
411 }
412};
413
414void tst_QThreadPool::exceptions()
415{
416 ExceptionTask task;
417 {
418 QThreadPool threadPool;
419// Uncomment this for a nice crash.
420// threadPool.start(&task);
421 }
422}
423#endif
424
425void tst_QThreadPool::setMaxThreadCount_data()
426{
427 QTest::addColumn<int>(name: "limit");
428
429 QTest::newRow(dataTag: "1") << 1;
430 QTest::newRow(dataTag: "-1") << -1;
431 QTest::newRow(dataTag: "2") << 2;
432 QTest::newRow(dataTag: "-2") << -2;
433 QTest::newRow(dataTag: "4") << 4;
434 QTest::newRow(dataTag: "-4") << -4;
435 QTest::newRow(dataTag: "0") << 0;
436 QTest::newRow(dataTag: "12345") << 12345;
437 QTest::newRow(dataTag: "-6789") << -6789;
438 QTest::newRow(dataTag: "42") << 42;
439 QTest::newRow(dataTag: "-666") << -666;
440}
441
442void tst_QThreadPool::setMaxThreadCount()
443{
444 QFETCH(int, limit);
445 QThreadPool *threadPool = QThreadPool::globalInstance();
446 int savedLimit = threadPool->maxThreadCount();
447
448 // maxThreadCount() should always return the previous argument to
449 // setMaxThreadCount(), regardless of input
450 threadPool->setMaxThreadCount(limit);
451 QCOMPARE(threadPool->maxThreadCount(), limit);
452
453 // the value returned from maxThreadCount() should always be valid input for setMaxThreadCount()
454 threadPool->setMaxThreadCount(savedLimit);
455 QCOMPARE(threadPool->maxThreadCount(), savedLimit);
456
457 // setting the limit on children should have no effect on the parent
458 {
459 QThreadPool threadPool2(threadPool);
460 savedLimit = threadPool2.maxThreadCount();
461
462 // maxThreadCount() should always return the previous argument to
463 // setMaxThreadCount(), regardless of input
464 threadPool2.setMaxThreadCount(limit);
465 QCOMPARE(threadPool2.maxThreadCount(), limit);
466
467 // the value returned from maxThreadCount() should always be valid input for setMaxThreadCount()
468 threadPool2.setMaxThreadCount(savedLimit);
469 QCOMPARE(threadPool2.maxThreadCount(), savedLimit);
470 }
471}
472
473void tst_QThreadPool::setMaxThreadCountStartsAndStopsThreads()
474{
475 class WaitingTask : public QRunnable
476 {
477 public:
478 QSemaphore waitForStarted, waitToFinish;
479
480 WaitingTask() { setAutoDelete(false); }
481
482 void run()
483 {
484 waitForStarted.release();
485 waitToFinish.acquire();
486 }
487 };
488
489 QThreadPool threadPool;
490 threadPool.setMaxThreadCount(1);
491
492 WaitingTask *task = new WaitingTask;
493 threadPool.start(runnable: task);
494 QVERIFY(task->waitForStarted.tryAcquire(1, 1000));
495
496 // thread limit is 1, cannot start more tasks
497 threadPool.start(runnable: task);
498 QVERIFY(!task->waitForStarted.tryAcquire(1, 1000));
499
500 // increasing the limit by 1 should start the task immediately
501 threadPool.setMaxThreadCount(2);
502 QVERIFY(task->waitForStarted.tryAcquire(1, 1000));
503
504 // ... but we still cannot start more tasks
505 threadPool.start(runnable: task);
506 QVERIFY(!task->waitForStarted.tryAcquire(1, 1000));
507
508 // increasing the limit should be able to start more than one at a time
509 threadPool.start(runnable: task);
510 threadPool.setMaxThreadCount(4);
511 QVERIFY(task->waitForStarted.tryAcquire(2, 1000));
512
513 // ... but we still cannot start more tasks
514 threadPool.start(runnable: task);
515 threadPool.start(runnable: task);
516 QVERIFY(!task->waitForStarted.tryAcquire(2, 1000));
517
518 // decreasing the thread limit should cause the active thread count to go down
519 threadPool.setMaxThreadCount(2);
520 QCOMPARE(threadPool.activeThreadCount(), 4);
521 task->waitToFinish.release(n: 2);
522 QTest::qWait(ms: 1000);
523 QCOMPARE(threadPool.activeThreadCount(), 2);
524
525 // ... and we still cannot start more tasks
526 threadPool.start(runnable: task);
527 threadPool.start(runnable: task);
528 QVERIFY(!task->waitForStarted.tryAcquire(2, 1000));
529
530 // start all remaining tasks
531 threadPool.start(runnable: task);
532 threadPool.start(runnable: task);
533 threadPool.start(runnable: task);
534 threadPool.start(runnable: task);
535 threadPool.setMaxThreadCount(8);
536 QVERIFY(task->waitForStarted.tryAcquire(6, 1000));
537
538 task->waitToFinish.release(n: 10);
539 threadPool.waitForDone();
540 delete task;
541}
542
543void tst_QThreadPool::reserveThread_data()
544{
545 setMaxThreadCount_data();
546}
547
548void tst_QThreadPool::reserveThread()
549{
550 QFETCH(int, limit);
551 QThreadPool *threadpool = QThreadPool::globalInstance();
552 int savedLimit = threadpool->maxThreadCount();
553 threadpool->setMaxThreadCount(limit);
554
555 // reserve up to the limit
556 for (int i = 0; i < limit; ++i)
557 threadpool->reserveThread();
558
559 // reserveThread() should always reserve a thread, regardless of
560 // how many have been previously reserved
561 threadpool->reserveThread();
562 QCOMPARE(threadpool->activeThreadCount(), (limit > 0 ? limit : 0) + 1);
563 threadpool->reserveThread();
564 QCOMPARE(threadpool->activeThreadCount(), (limit > 0 ? limit : 0) + 2);
565
566 // cleanup
567 threadpool->releaseThread();
568 threadpool->releaseThread();
569 for (int i = 0; i < limit; ++i)
570 threadpool->releaseThread();
571
572 // reserving threads in children should not effect the parent
573 {
574 QThreadPool threadpool2(threadpool);
575 threadpool2.setMaxThreadCount(limit);
576
577 // reserve up to the limit
578 for (int i = 0; i < limit; ++i)
579 threadpool2.reserveThread();
580
581 // reserveThread() should always reserve a thread, regardless
582 // of how many have been previously reserved
583 threadpool2.reserveThread();
584 QCOMPARE(threadpool2.activeThreadCount(), (limit > 0 ? limit : 0) + 1);
585 threadpool2.reserveThread();
586 QCOMPARE(threadpool2.activeThreadCount(), (limit > 0 ? limit : 0) + 2);
587
588 threadpool->reserveThread();
589 QCOMPARE(threadpool->activeThreadCount(), 1);
590 threadpool->reserveThread();
591 QCOMPARE(threadpool->activeThreadCount(), 2);
592
593 // cleanup
594 threadpool2.releaseThread();
595 threadpool2.releaseThread();
596 threadpool->releaseThread();
597 threadpool->releaseThread();
598 while (threadpool2.activeThreadCount() > 0)
599 threadpool2.releaseThread();
600 }
601
602 // reset limit on global QThreadPool
603 threadpool->setMaxThreadCount(savedLimit);
604}
605
606void tst_QThreadPool::releaseThread_data()
607{
608 setMaxThreadCount_data();
609}
610
611void tst_QThreadPool::releaseThread()
612{
613 QFETCH(int, limit);
614 QThreadPool *threadpool = QThreadPool::globalInstance();
615 int savedLimit = threadpool->maxThreadCount();
616 threadpool->setMaxThreadCount(limit);
617
618 // reserve up to the limit
619 for (int i = 0; i < limit; ++i)
620 threadpool->reserveThread();
621
622 // release should decrease the number of reserved threads
623 int reserved = threadpool->activeThreadCount();
624 while (reserved-- > 0) {
625 threadpool->releaseThread();
626 QCOMPARE(threadpool->activeThreadCount(), reserved);
627 }
628 QCOMPARE(threadpool->activeThreadCount(), 0);
629
630 // releaseThread() can release more than have been reserved
631 threadpool->releaseThread();
632 QCOMPARE(threadpool->activeThreadCount(), -1);
633 threadpool->reserveThread();
634 QCOMPARE(threadpool->activeThreadCount(), 0);
635
636 // releasing threads in children should not effect the parent
637 {
638 QThreadPool threadpool2(threadpool);
639 threadpool2.setMaxThreadCount(limit);
640
641 // reserve up to the limit
642 for (int i = 0; i < limit; ++i)
643 threadpool2.reserveThread();
644
645 // release should decrease the number of reserved threads
646 int reserved = threadpool2.activeThreadCount();
647 while (reserved-- > 0) {
648 threadpool2.releaseThread();
649 QCOMPARE(threadpool2.activeThreadCount(), reserved);
650 QCOMPARE(threadpool->activeThreadCount(), 0);
651 }
652 QCOMPARE(threadpool2.activeThreadCount(), 0);
653 QCOMPARE(threadpool->activeThreadCount(), 0);
654
655 // releaseThread() can release more than have been reserved
656 threadpool2.releaseThread();
657 QCOMPARE(threadpool2.activeThreadCount(), -1);
658 QCOMPARE(threadpool->activeThreadCount(), 0);
659 threadpool2.reserveThread();
660 QCOMPARE(threadpool2.activeThreadCount(), 0);
661 QCOMPARE(threadpool->activeThreadCount(), 0);
662 }
663
664 // reset limit on global QThreadPool
665 threadpool->setMaxThreadCount(savedLimit);
666}
667
668void tst_QThreadPool::reserveAndStart() // QTBUG-21051
669{
670 class WaitingTask : public QRunnable
671 {
672 public:
673 QAtomicInt count;
674 QSemaphore waitForStarted;
675 QSemaphore waitBeforeDone;
676
677 WaitingTask() { setAutoDelete(false); }
678
679 void run()
680 {
681 count.ref();
682 waitForStarted.release();
683 waitBeforeDone.acquire();
684 }
685 };
686
687 // Set up
688 QThreadPool *threadpool = QThreadPool::globalInstance();
689 int savedLimit = threadpool->maxThreadCount();
690 threadpool->setMaxThreadCount(1);
691 QCOMPARE(threadpool->activeThreadCount(), 0);
692
693 // reserve
694 threadpool->reserveThread();
695 QCOMPARE(threadpool->activeThreadCount(), 1);
696
697 // start a task, to get a running thread
698 WaitingTask *task = new WaitingTask;
699 threadpool->start(runnable: task);
700 QCOMPARE(threadpool->activeThreadCount(), 2);
701 task->waitForStarted.acquire();
702 task->waitBeforeDone.release();
703 QTRY_COMPARE(task->count.loadRelaxed(), 1);
704 QTRY_COMPARE(threadpool->activeThreadCount(), 1);
705
706 // now the thread is waiting, but tryStart() will fail since activeThreadCount() >= maxThreadCount()
707 QVERIFY(!threadpool->tryStart(task));
708 QTRY_COMPARE(threadpool->activeThreadCount(), 1);
709
710 // start() will therefore do a failing tryStart(), followed by enqueueTask()
711 // which will actually wake up the waiting thread.
712 threadpool->start(runnable: task);
713 QTRY_COMPARE(threadpool->activeThreadCount(), 2);
714 task->waitForStarted.acquire();
715 task->waitBeforeDone.release();
716 QTRY_COMPARE(task->count.loadRelaxed(), 2);
717 QTRY_COMPARE(threadpool->activeThreadCount(), 1);
718
719 threadpool->releaseThread();
720 QTRY_COMPARE(threadpool->activeThreadCount(), 0);
721
722 delete task;
723
724 threadpool->setMaxThreadCount(savedLimit);
725}
726
727QAtomicInt count;
728class CountingRunnable : public QRunnable
729{
730 public: void run()
731 {
732 count.ref();
733 }
734};
735
736void tst_QThreadPool::start()
737{
738 const int runs = 1000;
739 count.storeRelaxed(newValue: 0);
740 {
741 QThreadPool threadPool;
742 for (int i = 0; i< runs; ++i) {
743 threadPool.start(runnable: new CountingRunnable());
744 }
745 }
746 QCOMPARE(count.loadRelaxed(), runs);
747}
748
749void tst_QThreadPool::tryStart()
750{
751 class WaitingTask : public QRunnable
752 {
753 public:
754 QSemaphore semaphore;
755
756 WaitingTask() { setAutoDelete(false); }
757
758 void run()
759 {
760 semaphore.acquire();
761 count.ref();
762 }
763 };
764
765 count.storeRelaxed(newValue: 0);
766
767 WaitingTask task;
768 QThreadPool threadPool;
769 for (int i = 0; i < threadPool.maxThreadCount(); ++i) {
770 threadPool.start(runnable: &task);
771 }
772 QVERIFY(!threadPool.tryStart(&task));
773 task.semaphore.release(n: threadPool.maxThreadCount());
774 threadPool.waitForDone();
775 QCOMPARE(count.loadRelaxed(), threadPool.maxThreadCount());
776}
777
778QMutex mutex;
779QAtomicInt activeThreads;
780QAtomicInt peakActiveThreads;
781void tst_QThreadPool::tryStartPeakThreadCount()
782{
783 class CounterTask : public QRunnable
784 {
785 public:
786 CounterTask() { setAutoDelete(false); }
787
788 void run()
789 {
790 {
791 QMutexLocker lock(&mutex);
792 activeThreads.ref();
793 peakActiveThreads.storeRelaxed(newValue: qMax(a: peakActiveThreads.loadRelaxed(), b: activeThreads.loadRelaxed()));
794 }
795
796 QTest::qWait(ms: 100);
797 {
798 QMutexLocker lock(&mutex);
799 activeThreads.deref();
800 }
801 }
802 };
803
804 CounterTask task;
805 QThreadPool threadPool;
806
807 for (int i = 0; i < 4*QThread::idealThreadCount(); ++i) {
808 if (threadPool.tryStart(runnable: &task) == false)
809 QTest::qWait(ms: 10);
810 }
811 QCOMPARE(peakActiveThreads.loadRelaxed(), QThread::idealThreadCount());
812
813 for (int i = 0; i < 20; ++i) {
814 if (threadPool.tryStart(runnable: &task) == false)
815 QTest::qWait(ms: 10);
816 }
817 QCOMPARE(peakActiveThreads.loadRelaxed(), QThread::idealThreadCount());
818}
819
820void tst_QThreadPool::tryStartCount()
821{
822 class SleeperTask : public QRunnable
823 {
824 public:
825 SleeperTask() { setAutoDelete(false); }
826
827 void run()
828 {
829 QTest::qWait(ms: 50);
830 }
831 };
832
833 SleeperTask task;
834 QThreadPool threadPool;
835 const int runs = 5;
836
837 for (int i = 0; i < runs; ++i) {
838 int count = 0;
839 while (threadPool.tryStart(runnable: &task))
840 ++count;
841 QCOMPARE(count, QThread::idealThreadCount());
842
843 QTRY_COMPARE(threadPool.activeThreadCount(), 0);
844 }
845}
846
847void tst_QThreadPool::priorityStart_data()
848{
849 QTest::addColumn<int>(name: "otherCount");
850 QTest::newRow(dataTag: "0") << 0;
851 QTest::newRow(dataTag: "1") << 1;
852 QTest::newRow(dataTag: "2") << 2;
853}
854
855void tst_QThreadPool::priorityStart()
856{
857 class Holder : public QRunnable
858 {
859 public:
860 QSemaphore &sem;
861 Holder(QSemaphore &sem) : sem(sem) {}
862 void run()
863 {
864 sem.acquire();
865 }
866 };
867 class Runner : public QRunnable
868 {
869 public:
870 QAtomicPointer<QRunnable> &ptr;
871 Runner(QAtomicPointer<QRunnable> &ptr) : ptr(ptr) {}
872 void run()
873 {
874 ptr.testAndSetRelaxed(expectedValue: 0, newValue: this);
875 }
876 };
877
878 QFETCH(int, otherCount);
879 QSemaphore sem;
880 QAtomicPointer<QRunnable> firstStarted;
881 QRunnable *expected;
882 QThreadPool threadPool;
883 threadPool.setMaxThreadCount(1); // start only one thread at a time
884
885 // queue the holder first
886 // We need to be sure that all threads are active when we
887 // queue the two Runners
888 threadPool.start(runnable: new Holder(sem));
889 while (otherCount--)
890 threadPool.start(runnable: new Runner(firstStarted), priority: 0); // priority 0
891 threadPool.start(runnable: expected = new Runner(firstStarted), priority: 1); // priority 1
892
893 sem.release();
894 QVERIFY(threadPool.waitForDone());
895 QCOMPARE(firstStarted.loadRelaxed(), expected);
896}
897
898void tst_QThreadPool::waitForDone()
899{
900 QElapsedTimer total, pass;
901 total.start();
902
903 QThreadPool threadPool;
904 while (total.elapsed() < 10000) {
905 int runs;
906 count.storeRelaxed(newValue: runs = 0);
907 pass.restart();
908 while (pass.elapsed() < 100) {
909 threadPool.start(runnable: new CountingRunnable());
910 ++runs;
911 }
912 threadPool.waitForDone();
913 QCOMPARE(count.loadRelaxed(), runs);
914
915 count.storeRelaxed(newValue: runs = 0);
916 pass.restart();
917 while (pass.elapsed() < 100) {
918 threadPool.start(runnable: new CountingRunnable());
919 threadPool.start(runnable: new CountingRunnable());
920 runs += 2;
921 }
922 threadPool.waitForDone();
923 QCOMPARE(count.loadRelaxed(), runs);
924 }
925}
926
927void tst_QThreadPool::waitForDoneTimeout()
928{
929 QMutex mutex;
930 class BlockedTask : public QRunnable
931 {
932 public:
933 QMutex &mutex;
934 explicit BlockedTask(QMutex &m) : mutex(m) {}
935
936 void run()
937 {
938 mutex.lock();
939 mutex.unlock();
940 QTest::qSleep(ms: 50);
941 }
942 };
943
944 QThreadPool threadPool;
945
946 mutex.lock();
947 threadPool.start(runnable: new BlockedTask(mutex));
948 QVERIFY(!threadPool.waitForDone(100));
949 mutex.unlock();
950 QVERIFY(threadPool.waitForDone(400));
951}
952
953void tst_QThreadPool::clear()
954{
955 QSemaphore sem(0);
956 class BlockingRunnable : public QRunnable
957 {
958 public:
959 QSemaphore & sem;
960 BlockingRunnable(QSemaphore & sem) : sem(sem){}
961 void run()
962 {
963 sem.acquire();
964 count.ref();
965 }
966 };
967
968 QThreadPool threadPool;
969 threadPool.setMaxThreadCount(10);
970 int runs = 2 * threadPool.maxThreadCount();
971 count.storeRelaxed(newValue: 0);
972 for (int i = 0; i <= runs; i++) {
973 threadPool.start(runnable: new BlockingRunnable(sem));
974 }
975 threadPool.clear();
976 sem.release(n: threadPool.maxThreadCount());
977 threadPool.waitForDone();
978 QCOMPARE(count.loadRelaxed(), threadPool.maxThreadCount());
979}
980
981void tst_QThreadPool::clearWithAutoDelete()
982{
983 class MyRunnable : public QRunnable
984 {
985 public:
986 MyRunnable() {}
987 void run() override { QThread::usleep(30); }
988 };
989
990 QThreadPool threadPool;
991 threadPool.setMaxThreadCount(4);
992 const int loopCount = 20;
993 const int batchSize = 500;
994 // Should not crash see QTBUG-87092
995 for (int i = 0; i < loopCount; i++) {
996 threadPool.clear();
997 for (int j = 0; j < batchSize; j++) {
998 auto *runnable = new MyRunnable();
999 runnable->setAutoDelete(true);
1000 threadPool.start(runnable);
1001 }
1002 }
1003 QVERIFY(threadPool.waitForDone());
1004}
1005
1006#if QT_DEPRECATED_SINCE(5, 9)
1007void tst_QThreadPool::cancel()
1008{
1009 QSemaphore sem(0);
1010 QSemaphore startedThreads(0);
1011
1012 class BlockingRunnable : public QRunnable
1013 {
1014 public:
1015 QSemaphore & sem;
1016 QSemaphore &startedThreads;
1017 QAtomicInt &dtorCounter;
1018 QAtomicInt &runCounter;
1019 int dummy;
1020
1021 explicit BlockingRunnable(QSemaphore &s, QSemaphore &started, QAtomicInt &c, QAtomicInt &r)
1022 : sem(s), startedThreads(started), dtorCounter(c), runCounter(r){}
1023
1024 ~BlockingRunnable()
1025 {
1026 dtorCounter.fetchAndAddRelaxed(valueToAdd: 1);
1027 }
1028
1029 void run()
1030 {
1031 startedThreads.release();
1032 runCounter.fetchAndAddRelaxed(valueToAdd: 1);
1033 sem.acquire();
1034 count.ref();
1035 }
1036 };
1037
1038 enum {
1039 MaxThreadCount = 3,
1040 OverProvisioning = 2,
1041 runs = MaxThreadCount * OverProvisioning
1042 };
1043
1044 QThreadPool threadPool;
1045 threadPool.setMaxThreadCount(MaxThreadCount);
1046 BlockingRunnable *runnables[runs];
1047
1048 // ensure that the QThreadPool doesn't deadlock if any of the checks fail
1049 // and cause an early return:
1050 const QSemaphoreReleaser semReleaser(sem, runs);
1051
1052 count.storeRelaxed(newValue: 0);
1053 QAtomicInt dtorCounter = 0;
1054 QAtomicInt runCounter = 0;
1055 for (int i = 0; i < runs; i++) {
1056 runnables[i] = new BlockingRunnable(sem, startedThreads, dtorCounter, runCounter);
1057 runnables[i]->setAutoDelete(i != 0 && i != (runs-1)); //one which will run and one which will not
1058 threadPool.cancel(runnable: runnables[i]); //verify NOOP for jobs not in the queue
1059 threadPool.start(runnable: runnables[i]);
1060 }
1061 // wait for all worker threads to have started up:
1062 QVERIFY(startedThreads.tryAcquire(MaxThreadCount, 60*1000 /* 1min */));
1063
1064 for (int i = 0; i < runs; i++) {
1065 threadPool.cancel(runnable: runnables[i]);
1066 }
1067 runnables[0]->dummy = 0; //valgrind will catch this if cancel() is crazy enough to delete currently running jobs
1068 runnables[runs-1]->dummy = 0;
1069 QCOMPARE(dtorCounter.loadRelaxed(), runs - threadPool.maxThreadCount() - 1);
1070 sem.release(n: threadPool.maxThreadCount());
1071 threadPool.waitForDone();
1072 QCOMPARE(runCounter.loadRelaxed(), threadPool.maxThreadCount());
1073 QCOMPARE(count.loadRelaxed(), threadPool.maxThreadCount());
1074 QCOMPARE(dtorCounter.loadRelaxed(), runs - 2);
1075 delete runnables[0]; //if the pool deletes them then we'll get double-free crash
1076 delete runnables[runs-1];
1077}
1078#endif
1079
1080void tst_QThreadPool::tryTake()
1081{
1082 QSemaphore sem(0);
1083 QSemaphore startedThreads(0);
1084
1085 class BlockingRunnable : public QRunnable
1086 {
1087 public:
1088 QSemaphore &sem;
1089 QSemaphore &startedThreads;
1090 QAtomicInt &dtorCounter;
1091 QAtomicInt &runCounter;
1092 int dummy;
1093
1094 explicit BlockingRunnable(QSemaphore &s, QSemaphore &started, QAtomicInt &c, QAtomicInt &r)
1095 : sem(s), startedThreads(started), dtorCounter(c), runCounter(r) {}
1096
1097 ~BlockingRunnable()
1098 {
1099 dtorCounter.fetchAndAddRelaxed(valueToAdd: 1);
1100 }
1101
1102 void run() override
1103 {
1104 startedThreads.release();
1105 runCounter.fetchAndAddRelaxed(valueToAdd: 1);
1106 sem.acquire();
1107 count.ref();
1108 }
1109 };
1110
1111 enum {
1112 MaxThreadCount = 3,
1113 OverProvisioning = 2,
1114 Runs = MaxThreadCount * OverProvisioning
1115 };
1116
1117 QThreadPool threadPool;
1118 threadPool.setMaxThreadCount(MaxThreadCount);
1119 BlockingRunnable *runnables[Runs];
1120
1121 // ensure that the QThreadPool doesn't deadlock if any of the checks fail
1122 // and cause an early return:
1123 const QSemaphoreReleaser semReleaser(sem, Runs);
1124
1125 count.storeRelaxed(newValue: 0);
1126 QAtomicInt dtorCounter = 0;
1127 QAtomicInt runCounter = 0;
1128 for (int i = 0; i < Runs; i++) {
1129 runnables[i] = new BlockingRunnable(sem, startedThreads, dtorCounter, runCounter);
1130 runnables[i]->setAutoDelete(i != 0 && i != Runs - 1); // one which will run and one which will not
1131 QVERIFY(!threadPool.tryTake(runnables[i])); // verify NOOP for jobs not in the queue
1132 threadPool.start(runnable: runnables[i]);
1133 }
1134 // wait for all worker threads to have started up:
1135 QVERIFY(startedThreads.tryAcquire(MaxThreadCount, 60*1000 /* 1min */));
1136
1137 for (int i = 0; i < MaxThreadCount; ++i) {
1138 // check taking runnables doesn't work once they were started:
1139 QVERIFY(!threadPool.tryTake(runnables[i]));
1140 }
1141 for (int i = MaxThreadCount; i < Runs ; ++i) {
1142 QVERIFY(threadPool.tryTake(runnables[i]));
1143 delete runnables[i];
1144 }
1145
1146 runnables[0]->dummy = 0; // valgrind will catch this if tryTake() is crazy enough to delete currently running jobs
1147 QCOMPARE(dtorCounter.loadRelaxed(), int(Runs - MaxThreadCount));
1148 sem.release(n: MaxThreadCount);
1149 threadPool.waitForDone();
1150 QCOMPARE(runCounter.loadRelaxed(), int(MaxThreadCount));
1151 QCOMPARE(count.loadRelaxed(), int(MaxThreadCount));
1152 QCOMPARE(dtorCounter.loadRelaxed(), int(Runs - 1));
1153 delete runnables[0]; // if the pool deletes them then we'll get double-free crash
1154}
1155
1156void tst_QThreadPool::destroyingWaitsForTasksToFinish()
1157{
1158 QElapsedTimer total, pass;
1159 total.start();
1160
1161 while (total.elapsed() < 10000) {
1162 int runs;
1163 count.storeRelaxed(newValue: runs = 0);
1164 {
1165 QThreadPool threadPool;
1166 pass.restart();
1167 while (pass.elapsed() < 100) {
1168 threadPool.start(runnable: new CountingRunnable());
1169 ++runs;
1170 }
1171 }
1172 QCOMPARE(count.loadRelaxed(), runs);
1173
1174 count.storeRelaxed(newValue: runs = 0);
1175 {
1176 QThreadPool threadPool;
1177 pass.restart();
1178 while (pass.elapsed() < 100) {
1179 threadPool.start(runnable: new CountingRunnable());
1180 threadPool.start(runnable: new CountingRunnable());
1181 runs += 2;
1182 }
1183 }
1184 QCOMPARE(count.loadRelaxed(), runs);
1185 }
1186}
1187
1188// Verify that QThreadPool::stackSize is used when creating
1189// new threads. Note that this tests the Qt property only
1190// since QThread::stackSize() does not reflect the actual
1191// stack size used by the native thread.
1192void tst_QThreadPool::stackSize()
1193{
1194#if defined(Q_OS_UNIX) && !(defined(_POSIX_THREAD_ATTR_STACKSIZE) && (_POSIX_THREAD_ATTR_STACKSIZE-0 > 0))
1195 QSKIP("Setting stack size is unsupported on this platform.");
1196#endif
1197
1198 uint targetStackSize = 512 * 1024;
1199 uint threadStackSize = 1; // impossible value
1200
1201 class StackSizeChecker : public QRunnable
1202 {
1203 public:
1204 uint *stackSize;
1205
1206 StackSizeChecker(uint *stackSize)
1207 :stackSize(stackSize)
1208 {
1209
1210 }
1211
1212 void run()
1213 {
1214 *stackSize = QThread::currentThread()->stackSize();
1215 }
1216 };
1217
1218 QThreadPool threadPool;
1219 threadPool.setStackSize(targetStackSize);
1220 threadPool.start(runnable: new StackSizeChecker(&threadStackSize));
1221 QVERIFY(threadPool.waitForDone(30000)); // 30s timeout
1222 QCOMPARE(threadStackSize, targetStackSize);
1223}
1224
1225void tst_QThreadPool::stressTest()
1226{
1227 class Task : public QRunnable
1228 {
1229 QSemaphore semaphore;
1230 public:
1231 Task() { setAutoDelete(false); }
1232
1233 void start()
1234 {
1235 QThreadPool::globalInstance()->start(runnable: this);
1236 }
1237
1238 void wait()
1239 {
1240 semaphore.acquire();
1241 }
1242
1243 void run()
1244 {
1245 semaphore.release();
1246 }
1247 };
1248
1249 QElapsedTimer total;
1250 total.start();
1251 while (total.elapsed() < 30000) {
1252 Task t;
1253 t.start();
1254 t.wait();
1255 }
1256}
1257
1258void tst_QThreadPool::takeAllAndIncreaseMaxThreadCount() {
1259 class Task : public QRunnable
1260 {
1261 public:
1262 Task(QSemaphore *mainBarrier, QSemaphore *threadBarrier)
1263 : m_mainBarrier(mainBarrier)
1264 , m_threadBarrier(threadBarrier)
1265 {
1266 setAutoDelete(false);
1267 }
1268
1269 void run() {
1270 m_mainBarrier->release();
1271 m_threadBarrier->acquire();
1272 }
1273 private:
1274 QSemaphore *m_mainBarrier;
1275 QSemaphore *m_threadBarrier;
1276 };
1277
1278 QSemaphore mainBarrier;
1279 QSemaphore taskBarrier;
1280
1281 QThreadPool threadPool;
1282 threadPool.setMaxThreadCount(1);
1283
1284 Task *task1 = new Task(&mainBarrier, &taskBarrier);
1285 Task *task2 = new Task(&mainBarrier, &taskBarrier);
1286 Task *task3 = new Task(&mainBarrier, &taskBarrier);
1287
1288 threadPool.start(runnable: task1);
1289 threadPool.start(runnable: task2);
1290 threadPool.start(runnable: task3);
1291
1292 mainBarrier.acquire(n: 1);
1293
1294 QCOMPARE(threadPool.activeThreadCount(), 1);
1295
1296 QVERIFY(!threadPool.tryTake(task1));
1297 QVERIFY(threadPool.tryTake(task2));
1298 QVERIFY(threadPool.tryTake(task3));
1299
1300 // A bad queue implementation can segfault here because two consecutive items in the queue
1301 // have been taken
1302 threadPool.setMaxThreadCount(4);
1303
1304 // Even though we increase the max thread count, there should only be one job to run
1305 QCOMPARE(threadPool.activeThreadCount(), 1);
1306
1307 // Make sure jobs 2 and 3 never started
1308 QCOMPARE(mainBarrier.available(), 0);
1309
1310 taskBarrier.release(n: 1);
1311
1312 threadPool.waitForDone();
1313
1314 QCOMPARE(threadPool.activeThreadCount(), 0);
1315
1316 delete task1;
1317 delete task2;
1318 delete task3;
1319}
1320
1321void tst_QThreadPool::waitForDoneAfterTake()
1322{
1323 class Task : public QRunnable
1324 {
1325 public:
1326 Task(QSemaphore *mainBarrier, QSemaphore *threadBarrier)
1327 : m_mainBarrier(mainBarrier)
1328 , m_threadBarrier(threadBarrier)
1329 {}
1330
1331 void run()
1332 {
1333 m_mainBarrier->release();
1334 m_threadBarrier->acquire();
1335 }
1336
1337 private:
1338 QSemaphore *m_mainBarrier = nullptr;
1339 QSemaphore *m_threadBarrier = nullptr;
1340 };
1341
1342 int threadCount = 4;
1343
1344 // Blocks the main thread from releasing the threadBarrier before all run() functions have started
1345 QSemaphore mainBarrier;
1346 // Blocks the tasks from completing their run function
1347 QSemaphore threadBarrier;
1348
1349 QThreadPool manager;
1350 manager.setMaxThreadCount(threadCount);
1351
1352 // Fill all the threads with runnables that wait for the threadBarrier
1353 for (int i = 0; i < threadCount; i++) {
1354 auto *task = new Task(&mainBarrier, &threadBarrier);
1355 manager.start(runnable: task);
1356 }
1357
1358 QVERIFY(manager.activeThreadCount() == manager.maxThreadCount());
1359
1360 // Add runnables that are immediately removed from the pool queue.
1361 // This sets the queue elements to nullptr in QThreadPool and we want to test that
1362 // the threads keep going through the queue after encountering a nullptr.
1363 for (int i = 0; i < threadCount; i++) {
1364 QRunnable *runnable = createTask(pointer: emptyFunct);
1365 manager.start(runnable);
1366 QVERIFY(manager.tryTake(runnable));
1367 delete runnable;
1368 }
1369
1370 // Add another runnable that will not be removed
1371 manager.start(runnable: createTask(pointer: emptyFunct));
1372
1373 // Wait for the first runnables to start
1374 mainBarrier.acquire(n: threadCount);
1375
1376 QVERIFY(mainBarrier.available() == 0);
1377 QVERIFY(threadBarrier.available() == 0);
1378
1379 // Release runnables that are waiting and expect all runnables to complete
1380 threadBarrier.release(n: threadCount);
1381
1382 // Using qFatal instead of QVERIFY to force exit if threads are still running after timeout.
1383 // Otherwise, QCoreApplication will still wait for the stale threads and never exit the test.
1384 if (!manager.waitForDone(msecs: 5 * 60 * 1000))
1385 qFatal(msg: "waitForDone returned false. Aborting to stop background threads.");
1386
1387}
1388
1389/*
1390 Try trigger reuse of expired threads and check that all tasks execute.
1391
1392 This is a regression test for QTBUG-72872.
1393*/
1394void tst_QThreadPool::threadReuse()
1395{
1396 QThreadPool manager;
1397 manager.setExpiryTimeout(-1);
1398 manager.setMaxThreadCount(1);
1399
1400 constexpr int repeatCount = 10000;
1401 constexpr int timeoutMs = 1000;
1402 QSemaphore sem;
1403
1404 for (int i = 0; i < repeatCount; i++) {
1405 manager.start(functionToRun: [&sem]() { sem.release(); });
1406 manager.start(functionToRun: [&sem]() { sem.release(); });
1407 manager.releaseThread();
1408 QVERIFY(sem.tryAcquire(2, timeoutMs));
1409 manager.reserveThread();
1410 }
1411}
1412
1413QTEST_MAIN(tst_QThreadPool);
1414#include "tst_qthreadpool.moc"
1415

source code of qtbase/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp