diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/auto/other/qobjectrace/tst_qobjectrace.cpp | 87 |
1 files changed, 87 insertions, 0 deletions
diff --git a/tests/auto/other/qobjectrace/tst_qobjectrace.cpp b/tests/auto/other/qobjectrace/tst_qobjectrace.cpp index e09d304ff3..dba1fddb76 100644 --- a/tests/auto/other/qobjectrace/tst_qobjectrace.cpp +++ b/tests/auto/other/qobjectrace/tst_qobjectrace.cpp @@ -47,6 +47,7 @@ class tst_QObjectRace: public QObject private slots: void moveToThreadRace(); void destroyRace(); + void blockingQueuedDestroyRace(); void disconnectRace(); }; @@ -298,6 +299,92 @@ void tst_QObjectRace::destroyRace() delete threads[i]; } +class BlockingQueuedDestroyRaceObject : public QObject +{ + Q_OBJECT + +public: + enum class Behavior { Normal, Crash }; + explicit BlockingQueuedDestroyRaceObject(Behavior b = Behavior::Normal) + : m_behavior(b) {} + +signals: + bool aSignal(); + +public slots: + bool aSlot() + { + switch (m_behavior) { + case Behavior::Normal: + return true; + case Behavior::Crash: + qFatal("Race detected in a blocking queued connection"); + break; + } + + Q_UNREACHABLE(); + return false; + } + +private: + Behavior m_behavior; +}; + +void tst_QObjectRace::blockingQueuedDestroyRace() +{ + enum { MinIterations = 100, MinTime = 3000, WaitTime = 25 }; + + BlockingQueuedDestroyRaceObject sender; + + QDeadlineTimer timer(MinTime); + int iteration = 0; + + while (iteration++ < MinIterations || !timer.hasExpired()) { + // Manually allocate some storage, and create a receiver in there + std::aligned_storage< + sizeof(BlockingQueuedDestroyRaceObject), + alignof(BlockingQueuedDestroyRaceObject) + >::type storage; + + auto *receiver = reinterpret_cast<BlockingQueuedDestroyRaceObject *>(&storage); + new (receiver) BlockingQueuedDestroyRaceObject(BlockingQueuedDestroyRaceObject::Behavior::Normal); + + // Connect it to the sender via BlockingQueuedConnection + QVERIFY(connect(&sender, &BlockingQueuedDestroyRaceObject::aSignal, + receiver, &BlockingQueuedDestroyRaceObject::aSlot, + Qt::BlockingQueuedConnection)); + + const auto emitUntilDestroyed = [&sender] { + // Hack: as long as the receiver is alive and the connection + // established, the signal will return true (from the slot). + // When the receiver gets destroyed, the signal is disconnected + // and therefore the emission returns false. + while (emit sender.aSignal()) + ; + }; + + std::unique_ptr<QThread> thread(QThread::create(emitUntilDestroyed)); + thread->start(); + + QTest::qWait(WaitTime); + + // Destroy the receiver, and immediately allocate a new one at + // the same address. In case of a race, this might cause: + // - the metacall event to be posted to a destroyed object; + // - the metacall event to be posted to the wrong object. + // In both cases we hope to catch the race by crashing. + receiver->~BlockingQueuedDestroyRaceObject(); + new (receiver) BlockingQueuedDestroyRaceObject(BlockingQueuedDestroyRaceObject::Behavior::Crash); + + // Flush events + QTest::qWait(0); + + thread->wait(); + + receiver->~BlockingQueuedDestroyRaceObject(); + } +} + static QAtomicInteger<unsigned> countedStructObjectsCount; struct CountedFunctor { |