34fe9232db
Semi-automated, just needed ~20 manual fixes: $ find \( -iname \*.cpp -or -iname \*.h \) -exec perl -pe 's/(\.|->)load\(\)/$1loadRelaxed\(\)/g' -i \{\} + $ find \( -iname \*.cpp -or -iname \*.h \) -exec perl -pe 's/(\.|->)store\(/$1storeRelaxed\(/g' -i \{\} + It can be easily improved (e.g. for store check that there are no commas after the opening parens). The most common offender is QLibrary::load, and some code using std::atomic directly. Change-Id: I07c38a3c8ed32c924ef4999e85c7e45cf48f0f6c Reviewed-by: Marc Mutz <marc.mutz@kdab.com>
594 lines
17 KiB
C++
594 lines
17 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 <QtTest>
|
|
#include <QtDBus>
|
|
#include <QtCore/QVarLengthArray>
|
|
#include <QtCore/QThread>
|
|
#include <QtCore/QObject>
|
|
#include <QtCore/QSemaphore>
|
|
#include <QtCore/QMutex>
|
|
#include <QtCore/QWaitCondition>
|
|
#include <QtCore/QMap>
|
|
|
|
class Thread : public QThread
|
|
{
|
|
Q_OBJECT
|
|
static int counter;
|
|
public:
|
|
Thread(bool automatic = true);
|
|
void run();
|
|
|
|
using QThread::exec;
|
|
};
|
|
int Thread::counter;
|
|
|
|
class tst_QDBusThreading : public QObject
|
|
{
|
|
Q_OBJECT
|
|
static tst_QDBusThreading *_self;
|
|
QAtomicInt threadJoinCount;
|
|
QSemaphore threadJoin;
|
|
public:
|
|
QSemaphore sem1, sem2;
|
|
volatile bool success;
|
|
QEventLoop *loop;
|
|
enum FunctionSpy {
|
|
NoMethod = 0,
|
|
Adaptor_method,
|
|
Object_method
|
|
} functionSpy;
|
|
|
|
QThread *threadSpy;
|
|
int signalSpy;
|
|
|
|
tst_QDBusThreading();
|
|
static inline tst_QDBusThreading *self() { return _self; }
|
|
|
|
void joinThreads();
|
|
bool waitForSignal(QObject *obj, const char *signal, int delay = 1);
|
|
|
|
public Q_SLOTS:
|
|
void cleanup();
|
|
void signalSpySlot() { ++signalSpy; }
|
|
void threadStarted() { threadJoinCount.ref(); }
|
|
void threadFinished() { threadJoin.release(); }
|
|
|
|
void dyingThread_thread();
|
|
void lastInstanceInOtherThread_thread();
|
|
void concurrentCreation_thread();
|
|
void disconnectAnothersConnection_thread();
|
|
void accessMainsConnection_thread();
|
|
void accessOthersConnection_thread();
|
|
void registerObjectInOtherThread_thread();
|
|
void registerAdaptorInOtherThread_thread();
|
|
void callbackInMainThread_thread();
|
|
void callbackInAuxThread_thread();
|
|
void callbackInAnotherAuxThread_thread();
|
|
|
|
private Q_SLOTS:
|
|
void dyingThread();
|
|
void lastInstanceInOtherThread();
|
|
void concurrentCreation();
|
|
void disconnectAnothersConnection();
|
|
void accessMainsConnection();
|
|
void accessOthersConnection();
|
|
void registerObjectInOtherThread();
|
|
void registerAdaptorInOtherThread();
|
|
void callbackInMainThread();
|
|
void callbackInAuxThread();
|
|
void callbackInAnotherAuxThread();
|
|
};
|
|
tst_QDBusThreading *tst_QDBusThreading::_self;
|
|
|
|
class Adaptor : public QDBusAbstractAdaptor
|
|
{
|
|
Q_OBJECT
|
|
Q_CLASSINFO("D-Bus Interface", "local.Adaptor")
|
|
public:
|
|
Adaptor(QObject *parent)
|
|
: QDBusAbstractAdaptor(parent)
|
|
{
|
|
}
|
|
|
|
public Q_SLOTS:
|
|
void method()
|
|
{
|
|
tst_QDBusThreading::self()->functionSpy = tst_QDBusThreading::Adaptor_method;
|
|
tst_QDBusThreading::self()->threadSpy = QThread::currentThread();
|
|
emit signal();
|
|
}
|
|
|
|
Q_SIGNALS:
|
|
void signal();
|
|
};
|
|
|
|
class Object : public QObject
|
|
{
|
|
Q_OBJECT
|
|
Q_CLASSINFO("D-Bus Interface", "local.Object")
|
|
public:
|
|
Object(bool useAdaptor)
|
|
{
|
|
if (useAdaptor)
|
|
new Adaptor(this);
|
|
}
|
|
|
|
~Object()
|
|
{
|
|
QMetaObject::invokeMethod(QThread::currentThread(), "quit", Qt::QueuedConnection);
|
|
}
|
|
|
|
public Q_SLOTS:
|
|
void method()
|
|
{
|
|
tst_QDBusThreading::self()->functionSpy = tst_QDBusThreading::Object_method;
|
|
tst_QDBusThreading::self()->threadSpy = QThread::currentThread();
|
|
emit signal();
|
|
deleteLater();
|
|
}
|
|
|
|
Q_SIGNALS:
|
|
void signal();
|
|
};
|
|
|
|
#if 0
|
|
typedef void (*qdbusThreadDebugFunc)(int, int, QDBusConnectionPrivate *);
|
|
QDBUS_EXPORT void qdbusDefaultThreadDebug(int, int, QDBusConnectionPrivate *);
|
|
extern QDBUS_EXPORT qdbusThreadDebugFunc qdbusThreadDebug;
|
|
|
|
static void threadDebug(int action, int condition, QDBusConnectionPrivate *p)
|
|
{
|
|
qdbusDefaultThreadDebug(action, condition, p);
|
|
}
|
|
#endif
|
|
|
|
Thread::Thread(bool automatic)
|
|
{
|
|
setObjectName(QString::fromLatin1("Aux thread %1").arg(++counter));
|
|
connect(this, SIGNAL(started()), tst_QDBusThreading::self(), SLOT(threadStarted()));
|
|
connect(this, SIGNAL(finished()), tst_QDBusThreading::self(), SLOT(threadFinished()),
|
|
Qt::DirectConnection);
|
|
connect(this, SIGNAL(finished()), this, SLOT(deleteLater()), Qt::DirectConnection);
|
|
if (automatic)
|
|
start();
|
|
}
|
|
|
|
void Thread::run()
|
|
{
|
|
QVarLengthArray<char, 56> name;
|
|
name.append(QTest::currentTestFunction(), qstrlen(QTest::currentTestFunction()));
|
|
name.append("_thread", sizeof "_thread");
|
|
QMetaObject::invokeMethod(tst_QDBusThreading::self(), name.constData(), Qt::DirectConnection);
|
|
}
|
|
|
|
static const char myConnectionName[] = "connection";
|
|
|
|
tst_QDBusThreading::tst_QDBusThreading()
|
|
: loop(0), functionSpy(NoMethod), threadSpy(0)
|
|
{
|
|
_self = this;
|
|
QCoreApplication::instance()->thread()->setObjectName("Main thread");
|
|
}
|
|
|
|
void tst_QDBusThreading::joinThreads()
|
|
{
|
|
threadJoin.acquire(threadJoinCount.loadRelaxed());
|
|
threadJoinCount.storeRelaxed(0);
|
|
}
|
|
|
|
bool tst_QDBusThreading::waitForSignal(QObject *obj, const char *signal, int delay)
|
|
{
|
|
QObject::connect(obj, signal, &QTestEventLoop::instance(), SLOT(exitLoop()));
|
|
QPointer<QObject> safe = obj;
|
|
|
|
QTestEventLoop::instance().enterLoop(delay);
|
|
if (!safe.isNull())
|
|
QObject::disconnect(safe, signal, &QTestEventLoop::instance(), SLOT(exitLoop()));
|
|
return QTestEventLoop::instance().timeout();
|
|
}
|
|
|
|
void tst_QDBusThreading::cleanup()
|
|
{
|
|
joinThreads();
|
|
|
|
if (sem1.available())
|
|
sem1.acquire(sem1.available());
|
|
if (sem2.available())
|
|
sem2.acquire(sem2.available());
|
|
|
|
if (QDBusConnection(myConnectionName).isConnected())
|
|
QDBusConnection::disconnectFromBus(myConnectionName);
|
|
|
|
delete loop;
|
|
loop = 0;
|
|
|
|
QTest::qWait(500);
|
|
}
|
|
|
|
void tst_QDBusThreading::dyingThread_thread()
|
|
{
|
|
QDBusConnection::connectToBus(QDBusConnection::SessionBus, myConnectionName);
|
|
}
|
|
|
|
void tst_QDBusThreading::dyingThread()
|
|
{
|
|
Thread *th = new Thread(false);
|
|
QTestEventLoop::instance().connect(th, SIGNAL(destroyed(QObject*)), SLOT(exitLoop()));
|
|
th->start();
|
|
|
|
QTestEventLoop::instance().enterLoop(10);
|
|
QVERIFY(!QTestEventLoop::instance().timeout());
|
|
|
|
QDBusConnection con(myConnectionName);
|
|
QDBusConnection::disconnectFromBus(myConnectionName);
|
|
|
|
QVERIFY(con.isConnected());
|
|
QDBusReply<QStringList> reply = con.interface()->registeredServiceNames();
|
|
QVERIFY(reply.isValid());
|
|
QVERIFY(!reply.value().isEmpty());
|
|
QVERIFY(reply.value().contains(con.baseService()));
|
|
|
|
con.interface()->callWithCallback("ListNames", QVariantList(),
|
|
&QTestEventLoop::instance(), SLOT(exitLoop()));
|
|
|
|
QTestEventLoop::instance().enterLoop(1);
|
|
QVERIFY(!QTestEventLoop::instance().timeout());
|
|
}
|
|
|
|
void tst_QDBusThreading::lastInstanceInOtherThread_thread()
|
|
{
|
|
QDBusConnection con(myConnectionName);
|
|
QVERIFY(con.isConnected());
|
|
|
|
QDBusConnection::disconnectFromBus(myConnectionName);
|
|
|
|
// con is being destroyed in the wrong thread
|
|
}
|
|
|
|
void tst_QDBusThreading::lastInstanceInOtherThread()
|
|
{
|
|
Thread *th = new Thread(false);
|
|
// create the connection:
|
|
QDBusConnection::connectToBus(QDBusConnection::SessionBus, myConnectionName);
|
|
|
|
th->start();
|
|
th->wait();
|
|
}
|
|
|
|
void tst_QDBusThreading::concurrentCreation_thread()
|
|
{
|
|
sem1.acquire();
|
|
QDBusConnection con = QDBusConnection::connectToBus(QDBusConnection::SessionBus,
|
|
myConnectionName);
|
|
sem2.release();
|
|
}
|
|
|
|
void tst_QDBusThreading::concurrentCreation()
|
|
{
|
|
Thread *th = new Thread;
|
|
|
|
{
|
|
sem1.release();
|
|
QDBusConnection con = QDBusConnection::connectToBus(QDBusConnection::SessionBus,
|
|
myConnectionName);
|
|
QVERIFY(con.isConnected());
|
|
sem2.acquire();
|
|
}
|
|
waitForSignal(th, SIGNAL(finished()));
|
|
QDBusConnection::disconnectFromBus(myConnectionName);
|
|
|
|
QVERIFY(!QDBusConnection(myConnectionName).isConnected());
|
|
}
|
|
|
|
void tst_QDBusThreading::disconnectAnothersConnection_thread()
|
|
{
|
|
QDBusConnection con = QDBusConnection::connectToBus(QDBusConnection::SessionBus,
|
|
myConnectionName);
|
|
sem2.release();
|
|
}
|
|
|
|
void tst_QDBusThreading::disconnectAnothersConnection()
|
|
{
|
|
new Thread;
|
|
sem2.acquire();
|
|
|
|
QVERIFY(QDBusConnection(myConnectionName).isConnected());
|
|
QDBusConnection::disconnectFromBus(myConnectionName);
|
|
}
|
|
|
|
void tst_QDBusThreading::accessMainsConnection_thread()
|
|
{
|
|
sem1.acquire();
|
|
QDBusConnection con = QDBusConnection::sessionBus();
|
|
con.interface()->registeredServiceNames();
|
|
sem2.release();
|
|
}
|
|
|
|
void tst_QDBusThreading::accessMainsConnection()
|
|
{
|
|
QVERIFY(QDBusConnection::sessionBus().isConnected());
|
|
|
|
new Thread;
|
|
sem1.release();
|
|
sem2.acquire();
|
|
};
|
|
|
|
void tst_QDBusThreading::accessOthersConnection_thread()
|
|
{
|
|
QDBusConnection::connectToBus(QDBusConnection::SessionBus, myConnectionName);
|
|
sem2.release();
|
|
|
|
// wait for main thread to be done
|
|
sem1.acquire();
|
|
QDBusConnection::disconnectFromBus(myConnectionName);
|
|
sem2.release();
|
|
}
|
|
|
|
void tst_QDBusThreading::accessOthersConnection()
|
|
{
|
|
new Thread;
|
|
|
|
// wait for the connection to be created
|
|
sem2.acquire();
|
|
|
|
{
|
|
QDBusConnection con(myConnectionName);
|
|
QVERIFY(con.isConnected());
|
|
QVERIFY(con.baseService() != QDBusConnection::sessionBus().baseService());
|
|
|
|
QDBusReply<QStringList> reply = con.interface()->registeredServiceNames();
|
|
if (!reply.isValid())
|
|
qDebug() << reply.error().name() << reply.error().message();
|
|
QVERIFY(reply.isValid());
|
|
QVERIFY(!reply.value().isEmpty());
|
|
QVERIFY(reply.value().contains(con.baseService()));
|
|
QVERIFY(reply.value().contains(QDBusConnection::sessionBus().baseService()));
|
|
}
|
|
|
|
// tell it to destroy:
|
|
sem1.release();
|
|
sem2.acquire();
|
|
|
|
QDBusConnection con(myConnectionName);
|
|
QVERIFY(!con.isConnected());
|
|
}
|
|
|
|
void tst_QDBusThreading::registerObjectInOtherThread_thread()
|
|
{
|
|
{
|
|
Object *obj = new Object(false);
|
|
QDBusConnection::sessionBus().registerObject("/", obj, QDBusConnection::ExportAllSlots | QDBusConnection::ExportAllSignals);
|
|
|
|
sem2.release();
|
|
static_cast<Thread *>(QThread::currentThread())->exec();
|
|
}
|
|
|
|
sem2.release();
|
|
}
|
|
|
|
void tst_QDBusThreading::registerObjectInOtherThread()
|
|
{
|
|
QVERIFY(QDBusConnection::sessionBus().isConnected());
|
|
QThread *th = new Thread;
|
|
sem2.acquire();
|
|
|
|
signalSpy = 0;
|
|
|
|
QDBusInterface iface(QDBusConnection::sessionBus().baseService(), "/", "local.Object");
|
|
QVERIFY(iface.isValid());
|
|
|
|
connect(&iface, SIGNAL(signal()), SLOT(signalSpySlot()));
|
|
|
|
QTest::qWait(100);
|
|
QCOMPARE(signalSpy, 0);
|
|
|
|
functionSpy = NoMethod;
|
|
threadSpy = 0;
|
|
QDBusReply<void> reply = iface.call("method");
|
|
QVERIFY(reply.isValid());
|
|
QCOMPARE(functionSpy, Object_method);
|
|
QCOMPARE(threadSpy, th);
|
|
|
|
QTRY_COMPARE(signalSpy, 1);
|
|
|
|
sem2.acquire(); // the object is gone
|
|
functionSpy = NoMethod;
|
|
threadSpy = 0;
|
|
reply = iface.call("method");
|
|
QVERIFY(!reply.isValid());
|
|
QCOMPARE(functionSpy, NoMethod);
|
|
QCOMPARE(threadSpy, (QThread*)0);
|
|
}
|
|
|
|
void tst_QDBusThreading::registerAdaptorInOtherThread_thread()
|
|
{
|
|
{
|
|
Object *obj = new Object(true);
|
|
QDBusConnection::sessionBus().registerObject("/", obj, QDBusConnection::ExportAdaptors |
|
|
QDBusConnection::ExportAllSlots | QDBusConnection::ExportAllSignals);
|
|
|
|
sem2.release();
|
|
static_cast<Thread *>(QThread::currentThread())->exec();
|
|
}
|
|
|
|
sem2.release();
|
|
}
|
|
|
|
void tst_QDBusThreading::registerAdaptorInOtherThread()
|
|
{
|
|
QVERIFY(QDBusConnection::sessionBus().isConnected());
|
|
QThread *th = new Thread;
|
|
sem2.acquire();
|
|
|
|
QDBusInterface object(QDBusConnection::sessionBus().baseService(), "/", "local.Object");
|
|
QDBusInterface adaptor(QDBusConnection::sessionBus().baseService(), "/", "local.Adaptor");
|
|
QVERIFY(object.isValid());
|
|
QVERIFY(adaptor.isValid());
|
|
|
|
signalSpy = 0;
|
|
connect(&adaptor, SIGNAL(signal()), SLOT(signalSpySlot()));
|
|
QCOMPARE(signalSpy, 0);
|
|
|
|
functionSpy = NoMethod;
|
|
threadSpy = 0;
|
|
QDBusReply<void> reply = adaptor.call("method");
|
|
QVERIFY(reply.isValid());
|
|
QCOMPARE(functionSpy, Adaptor_method);
|
|
QCOMPARE(threadSpy, th);
|
|
|
|
QTRY_COMPARE(signalSpy, 1);
|
|
|
|
functionSpy = NoMethod;
|
|
threadSpy = 0;
|
|
reply = object.call("method");
|
|
QVERIFY(reply.isValid());
|
|
QCOMPARE(functionSpy, Object_method);
|
|
QCOMPARE(threadSpy, th);
|
|
|
|
QTest::qWait(100);
|
|
QCOMPARE(signalSpy, 1);
|
|
|
|
sem2.acquire(); // the object is gone
|
|
functionSpy = NoMethod;
|
|
threadSpy = 0;
|
|
reply = adaptor.call("method");
|
|
QVERIFY(!reply.isValid());
|
|
QCOMPARE(functionSpy, NoMethod);
|
|
QCOMPARE(threadSpy, (QThread*)0);
|
|
reply = object.call("method");
|
|
QVERIFY(!reply.isValid());
|
|
QCOMPARE(functionSpy, NoMethod);
|
|
QCOMPARE(threadSpy, (QThread*)0);
|
|
}
|
|
|
|
void tst_QDBusThreading::callbackInMainThread_thread()
|
|
{
|
|
QDBusConnection::connectToBus(QDBusConnection::SessionBus, myConnectionName);
|
|
sem2.release();
|
|
|
|
static_cast<Thread *>(QThread::currentThread())->exec();
|
|
QDBusConnection::disconnectFromBus(myConnectionName);
|
|
}
|
|
|
|
void tst_QDBusThreading::callbackInMainThread()
|
|
{
|
|
Thread *th = new Thread;
|
|
|
|
// wait for it to be connected
|
|
sem2.acquire();
|
|
|
|
QDBusConnection con(myConnectionName);
|
|
con.interface()->callWithCallback("ListNames", QVariantList(),
|
|
&QTestEventLoop::instance(), SLOT(exitLoop()));
|
|
QTestEventLoop::instance().enterLoop(10);
|
|
QVERIFY(!QTestEventLoop::instance().timeout());
|
|
|
|
QMetaObject::invokeMethod(th, "quit");
|
|
waitForSignal(th, SIGNAL(finished()));
|
|
}
|
|
|
|
void tst_QDBusThreading::callbackInAuxThread_thread()
|
|
{
|
|
QDBusConnection con(QDBusConnection::sessionBus());
|
|
QTestEventLoop ownLoop;
|
|
con.interface()->callWithCallback("ListNames", QVariantList(),
|
|
&ownLoop, SLOT(exitLoop()));
|
|
ownLoop.enterLoop(10);
|
|
loop->exit(ownLoop.timeout() ? 1 : 0);
|
|
}
|
|
|
|
void tst_QDBusThreading::callbackInAuxThread()
|
|
{
|
|
QVERIFY(QDBusConnection::sessionBus().isConnected());
|
|
|
|
loop = new QEventLoop;
|
|
|
|
new Thread;
|
|
QCOMPARE(loop->exec(), 0);
|
|
}
|
|
|
|
void tst_QDBusThreading::callbackInAnotherAuxThread_thread()
|
|
{
|
|
sem1.acquire();
|
|
if (!loop) {
|
|
// first thread
|
|
// create the connection and just wait
|
|
QDBusConnection con = QDBusConnection::connectToBus(QDBusConnection::SessionBus, myConnectionName);
|
|
loop = new QEventLoop;
|
|
|
|
// tell the main thread we have created the loop and connection
|
|
sem2.release();
|
|
|
|
// wait for the main thread to connect its signal
|
|
sem1.acquire();
|
|
success = loop->exec() == 0;
|
|
sem2.release();
|
|
|
|
// clean up
|
|
QDBusConnection::disconnectFromBus(myConnectionName);
|
|
} else {
|
|
// second thread
|
|
// try waiting for a message
|
|
QDBusConnection con(myConnectionName);
|
|
QTestEventLoop ownLoop;
|
|
con.interface()->callWithCallback("ListNames", QVariantList(),
|
|
&ownLoop, SLOT(exitLoop()));
|
|
ownLoop.enterLoop(1);
|
|
loop->exit(ownLoop.timeout() ? 1 : 0);
|
|
}
|
|
}
|
|
|
|
void tst_QDBusThreading::callbackInAnotherAuxThread()
|
|
{
|
|
// create first thread
|
|
success = false;
|
|
new Thread;
|
|
|
|
// wait for the event loop
|
|
sem1.release();
|
|
sem2.acquire();
|
|
QVERIFY(loop);
|
|
|
|
// create the second thread
|
|
new Thread;
|
|
sem1.release(2);
|
|
|
|
// wait for loop thread to finish executing:
|
|
sem2.acquire();
|
|
|
|
QVERIFY(success);
|
|
}
|
|
|
|
// Next tests:
|
|
// - unexport an object at the moment the call is being delivered
|
|
// - delete an object at the moment the call is being delivered
|
|
// - keep a global-static QDBusConnection for a thread-created connection
|
|
|
|
QTEST_MAIN(tst_QDBusThreading)
|
|
#include "tst_qdbusthreading.moc"
|