diff --git a/src/dbus/qdbusconnection.cpp b/src/dbus/qdbusconnection.cpp index e9196173ad53b9435978bc6e941135cd0d59e865..0f2d799b9226f2fc7e4b0b7c864bb2884eb754e0 100644 --- a/src/dbus/qdbusconnection.cpp +++ b/src/dbus/qdbusconnection.cpp @@ -38,6 +38,7 @@ #include <qdebug.h> #include <qcoreapplication.h> #include <qstringlist.h> +#include <qtimer.h> #include <qthread.h> #include "qdbusconnectioninterface.h" @@ -59,6 +60,24 @@ QT_BEGIN_NAMESPACE Q_GLOBAL_STATIC(QDBusConnectionManager, _q_manager) +// can be replaced with a lambda in Qt 5.7 +class QDBusConnectionDispatchEnabler : public QObject +{ + Q_OBJECT + QDBusConnectionPrivate *con; +public: + QDBusConnectionDispatchEnabler(QDBusConnectionPrivate *con) : con(con) {} + +public slots: + void execute() + { + con->setDispatchEnabled(true); + if (!con->ref.deref()) + con->deleteLater(); + deleteLater(); + } +}; + struct QDBusConnectionManager::ConnectionRequestData { enum RequestType { @@ -74,6 +93,8 @@ struct QDBusConnectionManager::ConnectionRequestData const QString *name; QDBusConnectionPrivate *result; + + bool suspendedDelivery; }; QDBusConnectionPrivate *QDBusConnectionManager::busConnection(QDBusConnection::BusType type) @@ -84,6 +105,10 @@ QDBusConnectionPrivate *QDBusConnectionManager::busConnection(QDBusConnection::B if (!qdbus_loadLibDBus()) return 0; + // we'll start in suspended delivery mode if we're in the main thread + // (the event loop will resume delivery) + bool suspendedDelivery = qApp && qApp->thread() == QThread::currentThread(); + QMutexLocker lock(&defaultBusMutex); if (defaultBuses[type]) return defaultBuses[type]; @@ -91,7 +116,7 @@ QDBusConnectionPrivate *QDBusConnectionManager::busConnection(QDBusConnection::B QString name = QStringLiteral("qt_default_session_bus"); if (type == QDBusConnection::SystemBus) name = QStringLiteral("qt_default_system_bus"); - return defaultBuses[type] = connectToBus(type, name); + return defaultBuses[type] = connectToBus(type, name, suspendedDelivery); } QDBusConnectionPrivate *QDBusConnectionManager::connection(const QString &name) const @@ -169,14 +194,22 @@ void QDBusConnectionManager::run() moveToThread(Q_NULLPTR); } -QDBusConnectionPrivate *QDBusConnectionManager::connectToBus(QDBusConnection::BusType type, const QString &name) +QDBusConnectionPrivate *QDBusConnectionManager::connectToBus(QDBusConnection::BusType type, const QString &name, + bool suspendedDelivery) { ConnectionRequestData data; data.type = ConnectionRequestData::ConnectToStandardBus; data.busType = type; data.name = &name; + data.suspendedDelivery = suspendedDelivery; emit connectionRequested(&data); + if (suspendedDelivery) { + data.result->ref.ref(); + QDBusConnectionDispatchEnabler *o = new QDBusConnectionDispatchEnabler(data.result); + QTimer::singleShot(0, o, SLOT(execute())); + o->moveToThread(qApp->thread()); // qApp was checked in the caller + } return data.result; } @@ -186,6 +219,7 @@ QDBusConnectionPrivate *QDBusConnectionManager::connectToBus(const QString &addr data.type = ConnectionRequestData::ConnectToBusByAddress; data.busAddress = &address; data.name = &name; + data.suspendedDelivery = false; emit connectionRequested(&data); return data.result; @@ -197,6 +231,7 @@ QDBusConnectionPrivate *QDBusConnectionManager::connectToPeer(const QString &add data.type = ConnectionRequestData::ConnectToPeerByAddress; data.busAddress = &address; data.name = &name; + data.suspendedDelivery = false; emit connectionRequested(&data); return data.result; @@ -252,6 +287,8 @@ void QDBusConnectionManager::executeConnectionRequest(QDBusConnectionManager::Co // will lock in QDBusConnectionPrivate::connectRelay() d->setConnection(c, error); d->createBusService(); + if (data->suspendedDelivery) + d->setDispatchEnabled(false); } } @@ -456,7 +493,7 @@ QDBusConnection QDBusConnection::connectToBus(BusType type, const QString &name) QDBusConnectionPrivate *d = 0; return QDBusConnection(d); } - return QDBusConnection(_q_manager()->connectToBus(type, name)); + return QDBusConnection(_q_manager()->connectToBus(type, name, false)); } /*! @@ -1232,4 +1269,6 @@ QByteArray QDBusConnection::localMachineId() QT_END_NAMESPACE +#include "qdbusconnection.moc" + #endif // QT_NO_DBUS diff --git a/src/dbus/qdbusconnection_p.h b/src/dbus/qdbusconnection_p.h index 2df7a49966efd5efb6e075cb7396174022a13657..f030a3ff8c007f3a8d65928d54551967103b92a9 100644 --- a/src/dbus/qdbusconnection_p.h +++ b/src/dbus/qdbusconnection_p.h @@ -169,6 +169,7 @@ public: // typedefs typedef QMultiHash<int, Watcher> WatcherHash; typedef QHash<int, DBusTimeout *> TimeoutHash; + typedef QVector<QDBusMessage> PendingMessageList; typedef QMultiHash<QString, SignalHook> SignalHookHash; typedef QHash<QString, QDBusMetaObject* > MetaObjectHash; @@ -191,6 +192,7 @@ public: ~QDBusConnectionPrivate(); void createBusService(); + void setDispatchEnabled(bool enable); void setPeer(DBusConnection *connection, const QDBusErrorInternal &error); void setConnection(DBusConnection *connection, const QDBusErrorInternal &error); void setServer(QDBusServer *object, DBusServer *server, const QDBusErrorInternal &error); @@ -308,6 +310,7 @@ public: }; WatcherHash watchers; TimeoutHash timeouts; + PendingMessageList pendingMessages; // the master lock protects our own internal state QReadWriteLock lock; @@ -322,6 +325,7 @@ public: PendingCallList pendingCalls; bool anonymousAuthenticationAllowed; + bool dispatchEnabled; // protected by the dispatch lock, not the main lock public: // static methods diff --git a/src/dbus/qdbusconnectionmanager_p.h b/src/dbus/qdbusconnectionmanager_p.h index 3f815fdcd76a9891bdd3746ee74c5220a0f21a7a..c0ab48e4ee66bc52c8345c74bfae26e425dc896f 100644 --- a/src/dbus/qdbusconnectionmanager_p.h +++ b/src/dbus/qdbusconnectionmanager_p.h @@ -67,7 +67,7 @@ public: QDBusConnectionPrivate *connection(const QString &name) const; void removeConnection(const QString &name); void setConnection(const QString &name, QDBusConnectionPrivate *c); - QDBusConnectionPrivate *connectToBus(QDBusConnection::BusType type, const QString &name); + QDBusConnectionPrivate *connectToBus(QDBusConnection::BusType type, const QString &name, bool suspendedDelivery); QDBusConnectionPrivate *connectToBus(const QString &address, const QString &name); QDBusConnectionPrivate *connectToPeer(const QString &address, const QString &name); diff --git a/src/dbus/qdbusintegrator.cpp b/src/dbus/qdbusintegrator.cpp index c465706913f97bf2182c43081e8038972851d6c6..f6221d51b6f91c413379cd72de70991c454eefb4 100644 --- a/src/dbus/qdbusintegrator.cpp +++ b/src/dbus/qdbusintegrator.cpp @@ -496,6 +496,11 @@ bool QDBusConnectionPrivate::handleMessage(const QDBusMessage &amsg) if (!ref.load()) return false; + if (!dispatchEnabled && !QDBusMessagePrivate::isLocal(amsg)) { + // queue messages only, we'll handle them later + pendingMessages << amsg; + return amsg.type() == QDBusMessage::MethodCallMessage; + } switch (amsg.type()) { case QDBusMessage::SignalMessage: @@ -690,6 +695,20 @@ static int findSlot(const QMetaObject *mo, const QByteArray &name, int flags, return -1; } +/*! + \internal + Enables or disables the delivery of incoming method calls and signals. If + \a enable is true, this will also cause any queued, pending messages to be + delivered. + */ +void QDBusConnectionPrivate::setDispatchEnabled(bool enable) +{ + QDBusDispatchLocker locker(SetDispatchEnabledAction, this); + dispatchEnabled = enable; + if (enable) + emit dispatchStatusChanged(); +} + static QDBusCallDeliveryEvent * const DIRECT_DELIVERY = (QDBusCallDeliveryEvent *)1; QDBusCallDeliveryEvent* QDBusConnectionPrivate::prepareReply(QDBusConnectionPrivate *target, @@ -946,7 +965,8 @@ QDBusConnectionPrivate::QDBusConnectionPrivate(QObject *p) : QObject(p), ref(1), capabilities(0), mode(InvalidMode), busService(0), dispatchLock(QMutex::Recursive), connection(0), rootNode(QString(QLatin1Char('/'))), - anonymousAuthenticationAllowed(false) + anonymousAuthenticationAllowed(false), + dispatchEnabled(true) { static const bool threads = q_dbus_threads_init_default(); if (::isDebugging == -1) @@ -1066,8 +1086,17 @@ void QDBusConnectionPrivate::timerEvent(QTimerEvent *e) void QDBusConnectionPrivate::doDispatch() { QDBusDispatchLocker locker(DoDispatchAction, this); - if (mode == ClientMode || mode == PeerMode) + if (mode == ClientMode || mode == PeerMode) { while (q_dbus_connection_dispatch(connection) == DBUS_DISPATCH_DATA_REMAINS) ; + if (dispatchEnabled && !pendingMessages.isEmpty()) { + // dispatch previously queued messages + PendingMessageList::Iterator it = pendingMessages.begin(); + PendingMessageList::Iterator end = pendingMessages.end(); + for ( ; it != end; ++it) + handleMessage(qMove(*it)); + pendingMessages.clear(); + } + } } void QDBusConnectionPrivate::socketRead(int fd) diff --git a/src/dbus/qdbusthreaddebug_p.h b/src/dbus/qdbusthreaddebug_p.h index eace25478d6f91c5fe32faf96b3c59c058130571..420f06261516f1c7457b3d45c6442442e343e414 100644 --- a/src/dbus/qdbusthreaddebug_p.h +++ b/src/dbus/qdbusthreaddebug_p.h @@ -83,7 +83,7 @@ enum ThreadAction { HandleObjectCallPostEventAction = 22, HandleObjectCallSemaphoreAction = 23, DoDispatchAction = 24, - // unused: 25, + SetDispatchEnabledAction = 25, MessageResultReceivedAction = 26, ActivateSignalAction = 27, PendingCallBlockAction = 28, diff --git a/tests/auto/dbus/dbus.pro b/tests/auto/dbus/dbus.pro index bd1fef519375990d1f20cfc55d5ecd35e52ae25a..ea8939bb9d45cc90e2fafef03d3c332744c17c31 100644 --- a/tests/auto/dbus/dbus.pro +++ b/tests/auto/dbus/dbus.pro @@ -1,5 +1,10 @@ TEMPLATE=subdirs + +# Run this test first SUBDIRS=\ + qdbusconnection_delayed + +SUBDIRS+=\ qdbusabstractadaptor \ qdbusabstractinterface \ qdbusconnection \ diff --git a/tests/auto/dbus/qdbusconnection_delayed/qdbusconnection_delayed.pro b/tests/auto/dbus/qdbusconnection_delayed/qdbusconnection_delayed.pro new file mode 100644 index 0000000000000000000000000000000000000000..71a75c644d05e4a4c2d1587f3159f5a4d2687922 --- /dev/null +++ b/tests/auto/dbus/qdbusconnection_delayed/qdbusconnection_delayed.pro @@ -0,0 +1,5 @@ +CONFIG += testcase parallel_test +TARGET = tst_qdbusconnection_delayed +QT = core dbus testlib +SOURCES += tst_qdbusconnection_delayed.cpp +DEFINES += SRCDIR=\\\"$$PWD/\\\" diff --git a/tests/auto/dbus/qdbusconnection_delayed/tst_qdbusconnection_delayed.cpp b/tests/auto/dbus/qdbusconnection_delayed/tst_qdbusconnection_delayed.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a7492a485515cc3f0410536ba4b63608ec2783f2 --- /dev/null +++ b/tests/auto/dbus/qdbusconnection_delayed/tst_qdbusconnection_delayed.cpp @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Intel Corporation. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtDBus> +#include <QtTest> + +#ifdef Q_OS_WIN +# include <process.h> +# define getpid _getpid +#else +# include <sys/types.h> +# include <unistd.h> +#endif + +class tst_QDBusConnection_Delayed : public QObject +{ + Q_OBJECT +private slots: + void delayedMessages(); +}; + +class Foo : public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.qtproject.tst_qdbusconnection_delayed.Foo") +public slots: + int bar() { return 42; } +}; + +static bool executedOnce = false; + +void tst_QDBusConnection_Delayed::delayedMessages() +{ + if (executedOnce) + QSKIP("This test can only be executed once"); + executedOnce = true; + + int argc = 1; + char *argv[] = { const_cast<char *>("tst_qdbusconnection_delayed"), 0 }; + QCoreApplication app(argc, argv); + + QDBusConnection session = QDBusConnection::sessionBus(); + QVERIFY(session.isConnected()); + QVERIFY(!session.baseService().isEmpty()); + + QDBusConnection other = QDBusConnection::connectToBus(QDBusConnection::SessionBus, "other"); + QVERIFY(other.isConnected()); + QVERIFY(!other.baseService().isEmpty()); + + QString name = "org.qtproject.tst_qdbusconnection_delayed-" + + QString::number(getpid()); + + // acquire a name in the main session bus connection + QVERIFY(session.registerService(name)); + QVERIFY(other.interface()->isServiceRegistered(name)); + + // make an asynchronous call to a yet-unregistered object + QDBusPendingCallWatcher pending(other.asyncCall(QDBusMessage::createMethodCall(name, "/foo", QString(), "bar"))); + + // sleep the main thread without running the event loop; + // the call must not be delivered + QTest::qSleep(1000); + QVERIFY(!pending.isFinished()); + + // now register the object + Foo foo; + session.registerObject("/foo", &foo, QDBusConnection::ExportAllSlots); + + connect(&pending, &QDBusPendingCallWatcher::finished, + &QTestEventLoop::instance(), &QTestEventLoop::exitLoop); + QTestEventLoop::instance().enterLoop(2); + QVERIFY(!QTestEventLoop::instance().timeout()); + QVERIFY(pending.isFinished()); + QVERIFY2(!pending.isError(), pending.error().name().toLatin1()); + QVERIFY(!pending.reply().arguments().isEmpty()); + QCOMPARE(pending.reply().arguments().at(0), QVariant(42)); +} + +QTEST_APPLESS_MAIN(tst_QDBusConnection_Delayed) + +#include "tst_qdbusconnection_delayed.moc"