From 4870282117b43242d9c2cd6fbde8175b2a907b08 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Thu, 9 Nov 2017 15:49:26 -0800 Subject: QSemaphore: fix regression when the timeout < 0 The issue was introduced by eaee1209f0ead5be786e81db8aee604ccfea85b0, so it affected only 5.9.2. [ChangeLog][QtCore][QSemaphore] Fixed a regression that would make tryAcquire() not to wait forever if the timeout was a negative value. Note: new code is advised to only use -1 to indicate "forever", as some other functions taking timeout periods do not accept other values. Task-number: QTBUG-64413 Change-Id: I57a1bd6e0c194530b732fffd14f58fce60d5dfc9 Reviewed-by: Lars Knoll --- .../corelib/thread/qsemaphore/tst_qsemaphore.cpp | 63 ++++++++++++++++++++++ 1 file changed, 63 insertions(+) (limited to 'tests/auto/corelib/thread') diff --git a/tests/auto/corelib/thread/qsemaphore/tst_qsemaphore.cpp b/tests/auto/corelib/thread/qsemaphore/tst_qsemaphore.cpp index 2970b2e118..ce093f769c 100644 --- a/tests/auto/corelib/thread/qsemaphore/tst_qsemaphore.cpp +++ b/tests/auto/corelib/thread/qsemaphore/tst_qsemaphore.cpp @@ -41,6 +41,8 @@ private slots: void tryAcquireWithTimeout_data(); void tryAcquireWithTimeout(); void tryAcquireWithTimeoutStarvation(); + void tryAcquireWithTimeoutForever_data(); + void tryAcquireWithTimeoutForever(); void producerConsumer(); }; @@ -155,21 +157,25 @@ void tst_QSemaphore::tryAcquire() semaphore.release(); QCOMPARE(semaphore.available(), 1); QVERIFY(!semaphore.tryAcquire(2)); + QVERIFY(!semaphore.tryAcquire(2, 0)); QCOMPARE(semaphore.available(), 1); semaphore.release(); QCOMPARE(semaphore.available(), 2); QVERIFY(!semaphore.tryAcquire(3)); + QVERIFY(!semaphore.tryAcquire(3, 0)); QCOMPARE(semaphore.available(), 2); semaphore.release(10); QCOMPARE(semaphore.available(), 12); QVERIFY(!semaphore.tryAcquire(100)); + QVERIFY(!semaphore.tryAcquire(100, 0)); QCOMPARE(semaphore.available(), 12); semaphore.release(10); QCOMPARE(semaphore.available(), 22); QVERIFY(!semaphore.tryAcquire(100)); + QVERIFY(!semaphore.tryAcquire(100, 0)); QCOMPARE(semaphore.available(), 22); QVERIFY(semaphore.tryAcquire()); @@ -178,23 +184,38 @@ void tst_QSemaphore::tryAcquire() QVERIFY(semaphore.tryAcquire()); QCOMPARE(semaphore.available(), 20); + semaphore.release(2); + QVERIFY(semaphore.tryAcquire(1, 0)); + QCOMPARE(semaphore.available(), 21); + + QVERIFY(semaphore.tryAcquire(1, 0)); + QCOMPARE(semaphore.available(), 20); + QVERIFY(semaphore.tryAcquire(10)); QCOMPARE(semaphore.available(), 10); + semaphore.release(10); + QVERIFY(semaphore.tryAcquire(10, 0)); + QCOMPARE(semaphore.available(), 10); + QVERIFY(semaphore.tryAcquire(10)); QCOMPARE(semaphore.available(), 0); // should not be able to acquire more QVERIFY(!semaphore.tryAcquire()); + QVERIFY(!semaphore.tryAcquire(1, 0)); QCOMPARE(semaphore.available(), 0); QVERIFY(!semaphore.tryAcquire()); + QVERIFY(!semaphore.tryAcquire(1, 0)); QCOMPARE(semaphore.available(), 0); QVERIFY(!semaphore.tryAcquire(10)); + QVERIFY(!semaphore.tryAcquire(10, 0)); QCOMPARE(semaphore.available(), 0); QVERIFY(!semaphore.tryAcquire(10)); + QVERIFY(!semaphore.tryAcquire(10, 0)); QCOMPARE(semaphore.available(), 0); } @@ -344,6 +365,48 @@ void tst_QSemaphore::tryAcquireWithTimeoutStarvation() QVERIFY(consumer.wait()); } +void tst_QSemaphore::tryAcquireWithTimeoutForever_data() +{ + QTest::addColumn("timeout"); + QTest::newRow("-1") << -1; + + // tryAcquire is documented to take any negative value as "forever" + QTest::newRow("INT_MIN") << INT_MIN; +} + +void tst_QSemaphore::tryAcquireWithTimeoutForever() +{ + enum { WaitTime = 1000 }; + struct Thread : public QThread { + QSemaphore sem; + + void run() override + { + QTest::qWait(WaitTime); + sem.release(2); + } + }; + + QFETCH(int, timeout); + Thread t; + + // sanity check it works if we can immediately acquire + t.sem.release(11); + QVERIFY(t.sem.tryAcquire(1, timeout)); + QVERIFY(t.sem.tryAcquire(10, timeout)); + + // verify that we do wait for at least WaitTime if we can't acquire immediately + QElapsedTimer timer; + timer.start(); + t.start(); + QVERIFY(t.sem.tryAcquire(1, timeout)); + QVERIFY(timer.elapsed() >= WaitTime); + + QVERIFY(t.wait()); + + QCOMPARE(t.sem.available(), 1); +} + const char alphabet[] = "ACGTH"; const int AlphabetSize = sizeof(alphabet) - 1; -- cgit v1.2.3 From ce08318a46164172eaa72f4436cddf7f69ce9e1c Mon Sep 17 00:00:00 2001 From: Svenn-Arne Dragly Date: Wed, 20 Sep 2017 09:28:27 +0200 Subject: Add QThreadPool autotest to detect stale threads after tryTake This test makes sure that we do not introduce a regression where the threads exited the inner loop over the queue before the queue was empty. This was triggered by calling tryTake at least maxThreadCount times, which left the same number of null pointers in the queue and caused the inner loop to exit too soon for all the threads. Change-Id: I3a9d800149b88d09510ddc424667670b60f06a33 Reviewed-by: Lars Knoll --- .../corelib/thread/qthreadpool/tst_qthreadpool.cpp | 68 ++++++++++++++++++++++ 1 file changed, 68 insertions(+) (limited to 'tests/auto/corelib/thread') diff --git a/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp b/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp index 66853a88d8..0eaf8a4b77 100644 --- a/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp +++ b/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp @@ -94,6 +94,7 @@ private slots: void destroyingWaitsForTasksToFinish(); void stressTest(); void takeAllAndIncreaseMaxThreadCount(); + void waitForDoneAfterTake(); private: QMutex m_functionTestMutex; @@ -1263,5 +1264,72 @@ void tst_QThreadPool::takeAllAndIncreaseMaxThreadCount() { delete task3; } +void tst_QThreadPool::waitForDoneAfterTake() +{ + class Task : public QRunnable + { + public: + Task(QSemaphore *mainBarrier, QSemaphore *threadBarrier) + : m_mainBarrier(mainBarrier) + , m_threadBarrier(threadBarrier) + {} + + void run() + { + m_mainBarrier->release(); + m_threadBarrier->acquire(); + } + + private: + QSemaphore *m_mainBarrier = nullptr; + QSemaphore *m_threadBarrier = nullptr; + }; + + int threadCount = 4; + + // Blocks the main thread from releasing the threadBarrier before all run() functions have started + QSemaphore mainBarrier; + // Blocks the tasks from completing their run function + QSemaphore threadBarrier; + + QThreadPool manager; + manager.setMaxThreadCount(threadCount); + + // Fill all the threads with runnables that wait for the threadBarrier + for (int i = 0; i < threadCount; i++) { + auto *task = new Task(&mainBarrier, &threadBarrier); + manager.start(task); + } + + QVERIFY(manager.activeThreadCount() == manager.maxThreadCount()); + + // Add runnables that are immediately removed from the pool queue. + // This sets the queue elements to nullptr in QThreadPool and we want to test that + // the threads keep going through the queue after encountering a nullptr. + for (int i = 0; i < threadCount; i++) { + QRunnable *runnable = createTask(emptyFunct); + manager.start(runnable); + QVERIFY(manager.tryTake(runnable)); + } + + // Add another runnable that will not be removed + manager.start(createTask(emptyFunct)); + + // Wait for the first runnables to start + mainBarrier.acquire(threadCount); + + QVERIFY(mainBarrier.available() == 0); + QVERIFY(threadBarrier.available() == 0); + + // Release runnables that are waiting and expect all runnables to complete + threadBarrier.release(threadCount); + + // Using qFatal instead of QVERIFY to force exit if threads are still running after timeout. + // Otherwise, QCoreApplication will still wait for the stale threads and never exit the test. + if (!manager.waitForDone(5 * 60 * 1000)) + qFatal("waitForDone returned false. Aborting to stop background threads."); + +} + QTEST_MAIN(tst_QThreadPool); #include "tst_qthreadpool.moc" -- cgit v1.2.3