QtDBus: finish all pending call with error if disconnected

libdbus will send a local signal if connection gets disconnected. When
this happens, end all pending calls with QDBusError::Disconnected.

Task-number: QTBUG-51649
Change-Id: I5c7d2a468bb5da746d0c0e53e458c1e376f186a9
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
bb10
Weng Xuetian 2016-03-03 21:56:53 -08:00 committed by Thiago Macieira
parent 8a7311532b
commit 9be4ee5202
6 changed files with 64 additions and 6 deletions

View File

@ -99,9 +99,11 @@ typedef dbus_uint32_t dbus_bool_t;
/* dbus-shared.h */
#define DBUS_SERVICE_DBUS "org.freedesktop.DBus"
#define DBUS_PATH_DBUS "/org/freedesktop/DBus"
#define DBUS_PATH_LOCAL "/org/freedesktop/DBus/Local"
#define DBUS_INTERFACE_DBUS "org.freedesktop.DBus"
#define DBUS_INTERFACE_INTROSPECTABLE "org.freedesktop.DBus.Introspectable"
#define DBUS_INTERFACE_PROPERTIES "org.freedesktop.DBus.Properties"
#define DBUS_INTERFACE_LOCAL "org.freedesktop.DBus.Local"
#define DBUS_NAME_FLAG_ALLOW_REPLACEMENT 0x1 /**< Allow another service to become the primary owner if requested */
#define DBUS_NAME_FLAG_REPLACE_EXISTING 0x2 /**< Request to replace the current primary owner */

View File

@ -260,6 +260,8 @@ private:
QString getNameOwnerNoCache(const QString &service);
void watchForDBusDisconnection();
void _q_newConnection(QDBusConnectionPrivate *newConnection);
protected:
@ -279,6 +281,7 @@ private slots:
void serviceOwnerChangedNoLock(const QString &name, const QString &oldOwner, const QString &newOwner);
void registerServiceNoLock(const QString &serviceName);
void unregisterServiceNoLock(const QString &serviceName);
void handleDBusDisconnection();
signals:
void dispatchStatusChanged();

View File

@ -1121,6 +1121,12 @@ void QDBusConnectionPrivate::closeConnection()
rootNode.children.clear(); // free resources
}
void QDBusConnectionPrivate::handleDBusDisconnection()
{
while (!pendingCalls.isEmpty())
processFinishedCall(pendingCalls.first());
}
void QDBusConnectionPrivate::checkThread()
{
Q_ASSERT(thread() == QDBusConnectionManager::instance());
@ -1646,6 +1652,19 @@ void QDBusConnectionPrivate::handleSignal(const QDBusMessage& msg)
handleSignal(key, msg); // third try
}
void QDBusConnectionPrivate::watchForDBusDisconnection()
{
SignalHook hook;
// Initialize the hook for Disconnected signal
hook.service.clear(); // org.freedesktop.DBus.Local.Disconnected uses empty service name
hook.path = QDBusUtil::dbusPathLocal();
hook.obj = this;
hook.params << QMetaType::Void;
hook.midx = staticMetaObject.indexOfSlot("handleDBusDisconnection()");
Q_ASSERT(hook.midx != -1);
signalHooks.insert(QLatin1String("Disconnected:" DBUS_INTERFACE_LOCAL), hook);
}
void QDBusConnectionPrivate::setServer(QDBusServer *object, DBusServer *s, const QDBusErrorInternal &error)
{
mode = ServerMode;
@ -1711,6 +1730,8 @@ void QDBusConnectionPrivate::setPeer(DBusConnection *c, const QDBusErrorInternal
qDBusSignalFilter,
this, 0);
watchForDBusDisconnection();
QMetaObject::invokeMethod(this, "doDispatch", Qt::QueuedConnection);
}
@ -1787,6 +1808,8 @@ void QDBusConnectionPrivate::setConnection(DBusConnection *dbc, const QDBusError
Q_ASSERT(hook.midx != -1);
signalHooks.insert(QLatin1String("NameOwnerChanged:" DBUS_INTERFACE_DBUS), hook);
watchForDBusDisconnection();
qDBusDebug() << this << ": connected successfully";
// schedule a dispatch:
@ -1813,10 +1836,16 @@ void QDBusConnectionPrivate::processFinishedCall(QDBusPendingCallPrivate *call)
QDBusMessage &msg = call->replyMessage;
if (call->pending) {
// decode the message
DBusMessage *reply = q_dbus_pending_call_steal_reply(call->pending);
msg = QDBusMessagePrivate::fromDBusMessage(reply, connection->capabilities);
q_dbus_message_unref(reply);
// when processFinishedCall is called and pending call is not completed,
// it means we received disconnected signal from libdbus
if (q_dbus_pending_call_get_completed(call->pending)) {
// decode the message
DBusMessage *reply = q_dbus_pending_call_steal_reply(call->pending);
msg = QDBusMessagePrivate::fromDBusMessage(reply, connection->capabilities);
q_dbus_message_unref(reply);
} else {
msg = QDBusMessage::createError(QDBusError::Disconnected, QDBusUtil::disconnectedErrorMessage());
}
}
qDBusDebug() << connection << "got message reply:" << msg;
@ -2116,8 +2145,8 @@ void QDBusConnectionPrivate::sendInternal(QDBusPendingCallPrivate *pcall, void *
pcall->pending = pending;
q_dbus_pending_call_set_notify(pending, qDBusResultReceived, pcall, 0);
// DBus won't notify us when a peer disconnects so we need to track these ourselves
if (mode == QDBusConnectionPrivate::PeerMode)
// DBus won't notify us when a peer disconnects or server terminates so we need to track these ourselves
if (mode == QDBusConnectionPrivate::PeerMode || mode == QDBusConnectionPrivate::ClientMode)
pendingCalls.append(pcall);
return;

View File

@ -155,6 +155,8 @@ namespace QDBusUtil
{ return QStringLiteral(DBUS_SERVICE_DBUS); }
inline QString dbusPath()
{ return QStringLiteral(DBUS_PATH_DBUS); }
inline QString dbusPathLocal()
{ return QStringLiteral(DBUS_PATH_LOCAL); }
inline QString dbusInterface()
{
// it's the same string, but just be sure

View File

@ -1218,6 +1218,27 @@ void tst_QDBusConnection::callVirtualObjectLocal()
QCOMPARE(obj.replyArguments, subPathReply.arguments());
}
void tst_QDBusConnection::pendingCallWhenDisconnected()
{
if (!QCoreApplication::instance())
QSKIP("Test requires a QCoreApplication");
QDBusServer *server = new QDBusServer;
QDBusConnection con = QDBusConnection::connectToPeer(server->address(), "disconnect");
QTestEventLoop::instance().enterLoop(2);
QVERIFY(con.isConnected());
QDBusMessage message = QDBusMessage::createMethodCall("", "/", QString(), "method");
QDBusPendingCall reply = con.asyncCall(message);
delete server;
QTestEventLoop::instance().enterLoop(2);
QVERIFY(!con.isConnected());
QVERIFY(reply.isFinished());
QVERIFY(reply.isError());
QVERIFY(reply.error().type() == QDBusError::Disconnected);
}
QString MyObject::path;
QString MyObjectWithoutInterface::path;
QString MyObjectWithoutInterface::interface;

View File

@ -121,6 +121,7 @@ private slots:
void registerVirtualObject();
void callVirtualObject();
void callVirtualObjectLocal();
void pendingCallWhenDisconnected();
public:
QString serviceName() const { return "org.qtproject.Qt.Autotests.QDBusConnection"; }