From e080017c7286f6d3a4fe49ab0acf4a751924d3ed Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Wed, 23 Oct 2024 09:31:27 -0700 Subject: [PATCH] tst_QThread: add a test for multiple threads wait()ing for one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There's nothing in our documentation saying this is permitted, but we have specific code for it on Windows and it works on Unix because we just wait on a QWaitCondition. Change-Id: Id6331fa9aad473cb4f35fffdf8bb04d9a34cd108 Reviewed-by: MÃ¥rten Nordheim Reviewed-by: Ivan Solovev Reviewed-by: Edward Welbourne (cherry picked from commit 5b5297fe87859f59a7aaf5e86a8915c00714fefa) Reviewed-by: Qt Cherry-pick Bot --- .../corelib/thread/qthread/tst_qthread.cpp | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/tests/auto/corelib/thread/qthread/tst_qthread.cpp b/tests/auto/corelib/thread/qthread/tst_qthread.cpp index 65cf97cdf34..ca1f52d0756 100644 --- a/tests/auto/corelib/thread/qthread/tst_qthread.cpp +++ b/tests/auto/corelib/thread/qthread/tst_qthread.cpp @@ -81,6 +81,8 @@ private slots: void connectThreadFinishedSignalToObjectDeleteLaterSlot(); void wait2(); void wait3_slowDestructor(); + void multiThreadWait_data(); + void multiThreadWait(); void destroyFinishRace(); void startFinishRace(); void startAndQuitCustomEventLoop(); @@ -1197,6 +1199,113 @@ void tst_QThread::wait3_slowDestructor() QVERIFY(thread.wait(one_minute)); } +void tst_QThread::multiThreadWait_data() +{ + QTest::addColumn>("deadlines"); + auto addRow = [](auto &&... list) { + static_assert(sizeof...(list) <= 5, + "Limited by std::array in tst_QThread::multiThreadWait()"); + QList deadlines = { std::move(list)... }; + QByteArrayList name; + for (int value : deadlines) { + if (value < 0) + name.append("Forever"); + else + name.append(QByteArray::number(value)); + } + QTest::newRow(name.join('-').constData()) << deadlines; + }; + + addRow(0, 0); + addRow(0, 0, 0, 0, 0); + addRow(-1, -1); + addRow(-1, -1, -1, -1, -1); + + // this is probably too fast and the Forever gets in too quickly + addRow(0, -1); + + addRow(100, -1); + addRow(100, 200, -1); + addRow(200, 100, -1); + addRow(-1, 100, 100, 100); +} + +void tst_QThread::multiThreadWait() +{ + class TargetThread : public QThread { + public: + QSemaphore sync; + void run() override + { + sync.acquire(); + msleep(Waiting_Thread::WaitTime); + } + }; + + class WaiterThread : public QThread { + public: + QSemaphore *startSema, *endSema; + QThread *target; + QDeadlineTimer deadline; + QElapsedTimer::Duration waitedDuration = {}; + int result = -1; + void run() override + { + QElapsedTimer elapsed; + elapsed.start(); + startSema->acquire(); + result = target->wait(deadline); + waitedDuration = elapsed.durationElapsed(); + endSema->release(); + } + }; + + QFETCH(QList, deadlines); + TargetThread target; + target.start(); + + QSemaphore startSema, endSema; + std::array, 5> threads; // 5 threads is enough + for (int i = 0; i < deadlines.size(); ++i) { + threads[i] = std::make_unique(); + threads[i]->startSema = &startSema; + threads[i]->endSema = &endSema; + threads[i]->target = ⌖ + threads[i]->deadline = QDeadlineTimer(deadlines.at(i)); + threads[i]->start(); + } + + // release the waiting threads first, then the target thread they're waiting on + startSema.release(deadlines.size()); + target.sync.release(); + + // wait for our waiting threads on a semaphore instead of QThread::wait() + // to make debugging easier + QVERIFY(endSema.tryAcquire(deadlines.size(), QDeadlineTimer::Forever)); + + // wait for all the threads to end, before QVERIFY/QCOMPAREs + for (int i = 0; i < deadlines.size(); ++i) + threads[i]->wait(); + target.wait(); + + std::chrono::milliseconds expectedDuration{Waiting_Thread::WaitTime}; + for (int i = 0; i < deadlines.size(); ++i) { + auto printI = qScopeGuard([i] { qWarning("i = %i", i); }); + if (unsigned(deadlines.at(i)) < Waiting_Thread::WaitTime / 2) { + QCOMPARE(threads[i]->result, false); + QCOMPARE_LT(threads[i]->waitedDuration, expectedDuration); + } else if (unsigned(deadlines.at(i)) > Waiting_Thread::WaitTime * 3 / 2) { + QCOMPARE(threads[i]->result, true); + QCOMPARE_GE(threads[i]->waitedDuration, expectedDuration); + } else { + qWarning("Wait time %i (index %i) is too close to the target time; test would be flaky", + deadlines.at(i), i); + } + printI.dismiss(); + threads[i].reset(); + } +} + void tst_QThread::destroyFinishRace() { class Thread : public QThread { void run() override {} };