| 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 |  | 
| 39 | typedef void (*FunctionPointer)(); | 
| 40 |  | 
| 41 | class FunctionPointerTask : public QRunnable | 
| 42 | { | 
| 43 | public: | 
| 44 |     FunctionPointerTask(FunctionPointer function) | 
| 45 |     :function(function) {} | 
| 46 |     void run() { function(); } | 
| 47 | private: | 
| 48 |     FunctionPointer function; | 
| 49 | }; | 
| 50 |  | 
| 51 | QRunnable *createTask(FunctionPointer pointer) | 
| 52 | { | 
| 53 |     return new FunctionPointerTask(pointer); | 
| 54 | } | 
| 55 |  | 
| 56 | class tst_QThreadPool : public QObject | 
| 57 | { | 
| 58 |     Q_OBJECT | 
| 59 | public: | 
| 60 |     tst_QThreadPool(); | 
| 61 |     ~tst_QThreadPool(); | 
| 62 |  | 
| 63 |     static QMutex *functionTestMutex; | 
| 64 |  | 
| 65 | private 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 |  | 
| 109 | private: | 
| 110 |     QMutex m_functionTestMutex; | 
| 111 | }; | 
| 112 |  | 
| 113 |  | 
| 114 | QMutex *tst_QThreadPool::functionTestMutex = 0; | 
| 115 |  | 
| 116 | tst_QThreadPool::tst_QThreadPool() | 
| 117 | { | 
| 118 |     tst_QThreadPool::functionTestMutex = &m_functionTestMutex; | 
| 119 | } | 
| 120 |  | 
| 121 | tst_QThreadPool::~tst_QThreadPool() | 
| 122 | { | 
| 123 |     tst_QThreadPool::functionTestMutex = 0; | 
| 124 | } | 
| 125 |  | 
| 126 | int testFunctionCount; | 
| 127 |  | 
| 128 | void sleepTestFunction() | 
| 129 | { | 
| 130 |     QTest::qSleep(ms: 1000); | 
| 131 |     ++testFunctionCount; | 
| 132 | } | 
| 133 |  | 
| 134 | void emptyFunct() | 
| 135 | { | 
| 136 |  | 
| 137 | } | 
| 138 |  | 
| 139 | void noSleepTestFunction() | 
| 140 | { | 
| 141 |     ++testFunctionCount; | 
| 142 | } | 
| 143 |  | 
| 144 | void 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 |  | 
| 153 | void noSleepTestFunctionMutex() | 
| 154 | { | 
| 155 |     Q_ASSERT(tst_QThreadPool::functionTestMutex); | 
| 156 |     tst_QThreadPool::functionTestMutex->lock(); | 
| 157 |     ++testFunctionCount; | 
| 158 |     tst_QThreadPool::functionTestMutex->unlock(); | 
| 159 | } | 
| 160 |  | 
| 161 | void tst_QThreadPool::runFunction() | 
| 162 | { | 
| 163 |     { | 
| 164 |         QThreadPool manager; | 
| 165 |         testFunctionCount = 0; | 
| 166 |         manager.start(functionToRun: noSleepTestFunction); | 
| 167 |     } | 
| 168 |     QCOMPARE(testFunctionCount, 1); | 
| 169 | } | 
| 170 |  | 
| 171 | void tst_QThreadPool::runFunction2() | 
| 172 | { | 
| 173 |     int localCount = 0; | 
| 174 |     { | 
| 175 |         QThreadPool manager; | 
| 176 |         manager.start(functionToRun: [&]() { ++localCount; }); | 
| 177 |     } | 
| 178 |     QCOMPARE(localCount, 1); | 
| 179 | } | 
| 180 |  | 
| 181 | void tst_QThreadPool::createThreadRunFunction() | 
| 182 | { | 
| 183 |     { | 
| 184 |         QThreadPool manager; | 
| 185 |         testFunctionCount = 0; | 
| 186 |         manager.start(functionToRun: noSleepTestFunction); | 
| 187 |     } | 
| 188 |  | 
| 189 |     QCOMPARE(testFunctionCount, 1); | 
| 190 | } | 
| 191 |  | 
| 192 | void 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 |  | 
| 221 | void 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 |  | 
| 232 | QAtomicInt ran; // bool | 
| 233 | class TestTask : public QRunnable | 
| 234 | { | 
| 235 | public: | 
| 236 |     void run() | 
| 237 |     { | 
| 238 |         ran.storeRelaxed(newValue: true); | 
| 239 |     } | 
| 240 | }; | 
| 241 |  | 
| 242 | void 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 | */ | 
| 253 | void tst_QThreadPool::singleton() | 
| 254 | { | 
| 255 |     ran.storeRelaxed(newValue: false); | 
| 256 |     QThreadPool::globalInstance()->start(runnable: new TestTask()); | 
| 257 |     QTRY_VERIFY(ran.loadRelaxed()); | 
| 258 | } | 
| 259 |  | 
| 260 | QAtomicInt *value = 0; | 
| 261 | class IntAccessor : public QRunnable | 
| 262 | { | 
| 263 | public: | 
| 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 | */ | 
| 277 | void 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 |  | 
| 288 | QSemaphore threadRecyclingSemaphore; | 
| 289 | QThread *recycledThread = 0; | 
| 290 |  | 
| 291 | class ThreadRecorderTask : public QRunnable | 
| 292 | { | 
| 293 | public: | 
| 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 | */ | 
| 304 | void 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 |  | 
| 327 | class ExpiryTimeoutTask : public QRunnable | 
| 328 | { | 
| 329 | public: | 
| 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 |  | 
| 348 | void 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 |  | 
| 384 | void 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 | 
| 405 | class ExceptionTask : public QRunnable | 
| 406 | { | 
| 407 | public: | 
| 408 |     void run() | 
| 409 |     { | 
| 410 |         throw new int; | 
| 411 |     } | 
| 412 | }; | 
| 413 |  | 
| 414 | void 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 |  | 
| 425 | void 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 |  | 
| 442 | void 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 |  | 
| 473 | void 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 |  | 
| 543 | void tst_QThreadPool::reserveThread_data() | 
| 544 | { | 
| 545 |     setMaxThreadCount_data(); | 
| 546 | } | 
| 547 |  | 
| 548 | void 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 |  | 
| 606 | void tst_QThreadPool::releaseThread_data() | 
| 607 | { | 
| 608 |     setMaxThreadCount_data(); | 
| 609 | } | 
| 610 |  | 
| 611 | void 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 |  | 
| 668 | void 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 |  | 
| 727 | QAtomicInt count; | 
| 728 | class CountingRunnable : public QRunnable | 
| 729 | { | 
| 730 |     public: void run() | 
| 731 |     { | 
| 732 |         count.ref(); | 
| 733 |     } | 
| 734 | }; | 
| 735 |  | 
| 736 | void 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 |  | 
| 749 | void 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 |  | 
| 778 | QMutex mutex; | 
| 779 | QAtomicInt activeThreads; | 
| 780 | QAtomicInt peakActiveThreads; | 
| 781 | void 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 |  | 
| 820 | void 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 |  | 
| 847 | void 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 |  | 
| 855 | void 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 |  | 
| 898 | void 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 |  | 
| 927 | void 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 |  | 
| 953 | void 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 |  | 
| 981 | void 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) | 
| 1007 | void 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 |  | 
| 1080 | void 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 |  | 
| 1156 | void 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. | 
| 1192 | void 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 |  | 
| 1225 | void 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 |  | 
| 1258 | void 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 |  | 
| 1321 | void 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 | */ | 
| 1394 | void 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 |  | 
| 1413 | QTEST_MAIN(tst_QThreadPool); | 
| 1414 | #include "tst_qthreadpool.moc" | 
| 1415 |  |