qt5base-lts/tests/auto/other/qobjectrace/tst_qobjectrace.cpp
Tony Sarajärvi c9420aeeff Conditionally disable tests that depend on cxx11_future flag
Task-number: QTBUG-88392
Change-Id: Ic8451ca4052ec4a94f0d78307e1b32965eb18c4e
Reviewed-by: Andrei Golubev <andrei.golubev@qt.io>
Reviewed-by: Sona Kurazyan <sona.kurazyan@qt.io>
2020-11-17 05:21:06 +00:00

567 lines
16 KiB
C++

/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtCore>
#include <QtTest/QtTest>
#include "emulationdetector.h"
enum { OneMinute = 60 * 1000,
TwoMinutes = OneMinute * 2 };
struct Functor
{
void operator()() const {};
};
class tst_QObjectRace: public QObject
{
Q_OBJECT
private slots:
void moveToThreadRace();
void destroyRace();
void blockingQueuedDestroyRace();
void disconnectRace();
};
class RaceObject : public QObject
{
Q_OBJECT
QList<QThread *> threads;
int count;
public:
RaceObject()
: count(0)
{ }
void addThread(QThread *thread)
{ threads.append(thread); }
public slots:
void theSlot()
{
enum { step = 35 };
if ((++count % step) == 0) {
QThread *nextThread = threads.at((count / step) % threads.size());
moveToThread(nextThread);
}
}
void destroSlot() {
emit theSignal();
}
signals:
void theSignal();
};
class RaceThread : public QThread
{
Q_OBJECT
RaceObject *object;
QElapsedTimer stopWatch;
public:
RaceThread()
: object(0)
{ }
void setObject(RaceObject *o)
{
object = o;
object->addThread(this);
}
void start() {
stopWatch.start();
QThread::start();
}
void run() override
{
QTimer zeroTimer;
connect(&zeroTimer, SIGNAL(timeout()), object, SLOT(theSlot()));
connect(&zeroTimer, SIGNAL(timeout()), this, SLOT(checkStopWatch()), Qt::DirectConnection);
zeroTimer.start(0);
(void) exec();
}
signals:
void theSignal();
private slots:
void checkStopWatch()
{
if (stopWatch.elapsed() >= 5000)
quit();
QObject o;
connect(&o, SIGNAL(destroyed()) , object, SLOT(destroSlot()));
connect(object, SIGNAL(destroyed()) , &o, SLOT(deleteLater()));
}
};
void tst_QObjectRace::moveToThreadRace()
{
RaceObject *object = new RaceObject;
enum { ThreadCount = 6 };
RaceThread *threads[ThreadCount];
for (int i = 0; i < ThreadCount; ++i) {
threads[i] = new RaceThread;
threads[i]->setObject(object);
}
object->moveToThread(threads[0]);
for (int i = 0; i < ThreadCount; ++i)
threads[i]->start();
while(!threads[0]->isFinished()) {
QPointer<RaceObject> foo (object);
QObject o;
connect(&o, SIGNAL(destroyed()) , object, SLOT(destroSlot()));
connect(object, SIGNAL(destroyed()) , &o, SLOT(deleteLater()));
QTest::qWait(10);
}
// the other threads should finish pretty quickly now
for (int i = 1; i < ThreadCount; ++i)
QVERIFY(threads[i]->wait(300));
for (int i = 0; i < ThreadCount; ++i)
delete threads[i];
delete object;
}
class MyObject : public QObject
{ Q_OBJECT
bool ok;
public:
MyObject() : ok(true) {}
~MyObject() { Q_ASSERT(ok); ok = false; }
public slots:
void slot1() { Q_ASSERT(ok); }
void slot2() { Q_ASSERT(ok); }
void slot3() { Q_ASSERT(ok); }
void slot4() { Q_ASSERT(ok); }
void slot5() { Q_ASSERT(ok); }
void slot6() { Q_ASSERT(ok); }
void slot7() { Q_ASSERT(ok); }
signals:
void signal1();
void signal2();
void signal3();
void signal4();
void signal5();
void signal6();
void signal7();
};
namespace {
const char *_slots[] = { SLOT(slot1()) , SLOT(slot2()) , SLOT(slot3()),
SLOT(slot4()) , SLOT(slot5()) , SLOT(slot6()),
SLOT(slot7()) };
const char *_signals[] = { SIGNAL(signal1()), SIGNAL(signal2()), SIGNAL(signal3()),
SIGNAL(signal4()), SIGNAL(signal5()), SIGNAL(signal6()),
SIGNAL(signal7()) };
typedef void (MyObject::*PMFType)();
const PMFType _slotsPMF[] = { &MyObject::slot1, &MyObject::slot2, &MyObject::slot3,
&MyObject::slot4, &MyObject::slot5, &MyObject::slot6,
&MyObject::slot7 };
const PMFType _signalsPMF[] = { &MyObject::signal1, &MyObject::signal2, &MyObject::signal3,
&MyObject::signal4, &MyObject::signal5, &MyObject::signal6,
&MyObject::signal7 };
}
class DestroyThread : public QThread
{
Q_OBJECT
MyObject **objects;
int number;
public:
void setObjects(MyObject **o, int n)
{
objects = o;
number = n;
for(int i = 0; i < number; i++)
objects[i]->moveToThread(this);
}
void run() override
{
for (int i = number-1; i >= 0; --i) {
/* Do some more connection and disconnection between object in this thread that have not been destroyed yet */
const int nAlive = i+1;
connect (objects[((i+1)*31) % nAlive], _signals[(12*i)%7], objects[((i+2)*37) % nAlive], _slots[(15*i+2)%7] );
disconnect(objects[((i+1)*31) % nAlive], _signals[(12*i)%7], objects[((i+2)*37) % nAlive], _slots[(15*i+2)%7] );
connect (objects[((i+4)*41) % nAlive], _signalsPMF[(18*i)%7], objects[((i+5)*43) % nAlive], _slotsPMF[(19*i+2)%7] );
disconnect(objects[((i+4)*41) % nAlive], _signalsPMF[(18*i)%7], objects[((i+5)*43) % nAlive], _slotsPMF[(19*i+2)%7] );
QMetaObject::Connection c = connect(objects[((i+5)*43) % nAlive], _signalsPMF[(9*i+1)%7], Functor());
for (int f = 0; f < 7; ++f)
emit (objects[i]->*_signalsPMF[f])();
disconnect(c);
disconnect(objects[i], _signalsPMF[(10*i+5)%7], 0, 0);
disconnect(objects[i], _signals[(11*i+6)%7], 0, 0);
disconnect(objects[i], 0, objects[(i*17+6) % nAlive], 0);
if (i%4 == 1) {
disconnect(objects[i], 0, 0, 0);
}
delete objects[i];
}
//run the possible queued slots
qApp->processEvents();
}
};
#define EXTRA_THREAD_WAIT 3000
#define MAIN_THREAD_WAIT TwoMinutes
void tst_QObjectRace::destroyRace()
{
if (EmulationDetector::isRunningArmOnX86())
QSKIP("Test is too slow to run on emulator");
enum { ThreadCount = 10, ObjectCountPerThread = 2777,
ObjectCount = ThreadCount * ObjectCountPerThread };
MyObject *objects[ObjectCount];
for (int i = 0; i < ObjectCount; ++i)
objects[i] = new MyObject;
for (int i = 0; i < ObjectCount * 17; ++i) {
connect(objects[(i*13) % ObjectCount], _signals[(2*i)%7],
objects[((i+2)*17) % ObjectCount], _slots[(3*i+2)%7] );
connect(objects[((i+6)*23) % ObjectCount], _signals[(5*i+4)%7],
objects[((i+8)*41) % ObjectCount], _slots[(i+6)%7] );
connect(objects[(i*67) % ObjectCount], _signalsPMF[(2*i)%7],
objects[((i+1)*71) % ObjectCount], _slotsPMF[(3*i+2)%7] );
connect(objects[((i+3)*73) % ObjectCount], _signalsPMF[(5*i+4)%7],
objects[((i+5)*79) % ObjectCount], Functor() );
}
DestroyThread *threads[ThreadCount];
for (int i = 0; i < ThreadCount; ++i) {
threads[i] = new DestroyThread;
threads[i]->setObjects(objects + i*ObjectCountPerThread, ObjectCountPerThread);
}
for (int i = 0; i < ThreadCount; ++i)
threads[i]->start();
QVERIFY(threads[0]->wait(MAIN_THREAD_WAIT));
// the other threads should finish pretty quickly now
for (int i = 1; i < ThreadCount; ++i)
QVERIFY(threads[i]->wait(EXTRA_THREAD_WAIT));
for (int i = 0; i < ThreadCount; ++i)
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()
{
#if !QT_CONFIG(cxx11_future)
QSKIP("This test requires QThread::create");
#else
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();
}
#endif
}
static QAtomicInteger<unsigned> countedStructObjectsCount;
struct CountedFunctor
{
CountedFunctor() : destroyed(false) { countedStructObjectsCount.fetchAndAddRelaxed(1); }
CountedFunctor(const CountedFunctor &) : destroyed(false) { countedStructObjectsCount.fetchAndAddRelaxed(1); }
CountedFunctor &operator=(const CountedFunctor &) { return *this; }
~CountedFunctor() { destroyed = true; countedStructObjectsCount.fetchAndAddRelaxed(-1);}
void operator()() const {QCOMPARE(destroyed, false);}
private:
bool destroyed;
};
class DisconnectRaceSenderObject : public QObject
{
Q_OBJECT
signals:
void theSignal();
};
class DisconnectRaceThread : public QThread
{
Q_OBJECT
DisconnectRaceSenderObject *sender;
bool emitSignal;
public:
DisconnectRaceThread(DisconnectRaceSenderObject *s, bool emitIt)
: QThread(), sender(s), emitSignal(emitIt)
{
}
void run() override
{
while (!isInterruptionRequested()) {
QMetaObject::Connection conn = connect(sender, &DisconnectRaceSenderObject::theSignal,
sender, CountedFunctor(), Qt::BlockingQueuedConnection);
if (emitSignal)
emit sender->theSignal();
disconnect(conn);
yieldCurrentThread();
}
}
};
class DeleteReceiverRaceSenderThread : public QThread
{
Q_OBJECT
DisconnectRaceSenderObject *sender;
public:
DeleteReceiverRaceSenderThread(DisconnectRaceSenderObject *s)
: QThread(), sender(s)
{
}
void run() override
{
while (!isInterruptionRequested()) {
emit sender->theSignal();
yieldCurrentThread();
}
}
};
class DeleteReceiverRaceReceiver : public QObject
{
Q_OBJECT
DisconnectRaceSenderObject *sender;
QObject *receiver;
QTimer *timer;
public:
DeleteReceiverRaceReceiver(DisconnectRaceSenderObject *s)
: QObject(), sender(s), receiver(0)
{
timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &DeleteReceiverRaceReceiver::onTimeout);
timer->start(1);
}
~DeleteReceiverRaceReceiver()
{
delete receiver;
}
void onTimeout()
{
if (receiver)
delete receiver;
receiver = new QObject;
connect(sender, &DisconnectRaceSenderObject::theSignal, receiver, CountedFunctor(), Qt::BlockingQueuedConnection);
}
};
class DeleteReceiverRaceReceiverThread : public QThread
{
Q_OBJECT
DisconnectRaceSenderObject *sender;
public:
DeleteReceiverRaceReceiverThread(DisconnectRaceSenderObject *s)
: QThread(), sender(s)
{
}
void run() override
{
QScopedPointer<DeleteReceiverRaceReceiver> receiver(new DeleteReceiverRaceReceiver(sender));
exec();
}
};
void tst_QObjectRace::disconnectRace()
{
enum { ThreadCount = 20, TimeLimit = 3000 };
QCOMPARE(countedStructObjectsCount.loadRelaxed(), 0u);
{
QScopedPointer<DisconnectRaceSenderObject> sender(new DisconnectRaceSenderObject());
QScopedPointer<QThread> senderThread(new QThread());
senderThread->start();
sender->moveToThread(senderThread.data());
DisconnectRaceThread *threads[ThreadCount];
for (int i = 0; i < ThreadCount; ++i) {
threads[i] = new DisconnectRaceThread(sender.data(), !(i % 10));
threads[i]->start();
}
QTest::qWait(TimeLimit);
for (int i = 0; i < ThreadCount; ++i) {
threads[i]->requestInterruption();
QVERIFY(threads[i]->wait());
delete threads[i];
}
senderThread->quit();
QVERIFY(senderThread->wait());
}
QCOMPARE(countedStructObjectsCount.loadRelaxed(), 0u);
{
QScopedPointer<DisconnectRaceSenderObject> sender(new DisconnectRaceSenderObject());
QScopedPointer<DeleteReceiverRaceSenderThread> senderThread(new DeleteReceiverRaceSenderThread(sender.data()));
senderThread->start();
sender->moveToThread(senderThread.data());
DeleteReceiverRaceReceiverThread *threads[ThreadCount];
for (int i = 0; i < ThreadCount; ++i) {
threads[i] = new DeleteReceiverRaceReceiverThread(sender.data());
threads[i]->start();
}
QTest::qWait(TimeLimit);
senderThread->requestInterruption();
QVERIFY(senderThread->wait());
for (int i = 0; i < ThreadCount; ++i) {
threads[i]->quit();
QVERIFY(threads[i]->wait());
delete threads[i];
}
}
QCOMPARE(countedStructObjectsCount.loadRelaxed(), 0u);
}
QTEST_MAIN(tst_QObjectRace)
#include "tst_qobjectrace.moc"