summaryrefslogtreecommitdiffstats
path: root/src/corelib/kernel
diff options
context:
space:
mode:
authorLars Knoll <lars.knoll@qt.io>2019-01-09 19:12:46 +0100
committerLars Knoll <lars.knoll@qt.io>2019-03-29 13:46:17 +0000
commit6e0b5dadc7e91be786411809f1f9667c239168e2 (patch)
tree4c8efa67ad7b5ebcd3536ee16af07eed697b0d57 /src/corelib/kernel
parenta2fda801cc2e3558d8bcf7a002df6a824f9509fa (diff)
Change cleanup mechanism for orphaned connections
Put all connections that get disconnected into a singly linked orphaned list. Whenever the refcount on the connectionData drops down to one, this list can safely be cleared, even with the planned removal of locking in activate(). Use an id integer in the connection to acoid activating newly added connections. Fixes: QTBUG-72649 Change-Id: Ide3d116ae7fc9ca497598c1c2b71d43b4339c92d Reviewed-by: Olivier Goffart (Woboq GmbH) <ogoffart@woboq.com>
Diffstat (limited to 'src/corelib/kernel')
-rw-r--r--src/corelib/kernel/qmetaobject_p.h2
-rw-r--r--src/corelib/kernel/qobject.cpp185
-rw-r--r--src/corelib/kernel/qobject_p.h109
3 files changed, 165 insertions, 131 deletions
diff --git a/src/corelib/kernel/qmetaobject_p.h b/src/corelib/kernel/qmetaobject_p.h
index 26d86fdf14..0cd9da2eac 100644
--- a/src/corelib/kernel/qmetaobject_p.h
+++ b/src/corelib/kernel/qmetaobject_p.h
@@ -232,7 +232,7 @@ struct QMetaObjectPrivate
const QMetaObject *smeta,
const QObject *receiver, int method_index, void **slot,
DisconnectType = DisconnectAll);
- static inline bool disconnectHelper(QObjectPrivate::Connection *c,
+ static inline bool disconnectHelper(QObjectPrivate::ConnectionData *connections, int signalIndex,
const QObject *receiver, int method_index, void **slot,
QBasicMutex *senderMutex, DisconnectType = DisconnectAll);
#endif
diff --git a/src/corelib/kernel/qobject.cpp b/src/corelib/kernel/qobject.cpp
index 268f3949fd..77e58abfa2 100644
--- a/src/corelib/kernel/qobject.cpp
+++ b/src/corelib/kernel/qobject.cpp
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2019 The Qt Company Ltd.
** Copyright (C) 2016 Intel Corporation.
** Copyright (C) 2013 Olivier Goffart <ogoffart@woboq.com>
** Contact: https://www.qt.io/licensing/
@@ -332,14 +332,15 @@ void QObjectPrivate::addConnection(int signal, Connection *c)
ConnectionList &connectionList = cd->connectionsForSignal(signal);
if (connectionList.last) {
+ Q_ASSERT(connectionList.last->receiver);
connectionList.last->nextConnectionList = c;
} else {
connectionList.first = c;
}
+ c->id = ++cd->currentConnectionId;
+ c->prevConnectionList = connectionList.last;
connectionList.last = c;
- cleanConnectionLists();
-
QObjectPrivate *rd = QObjectPrivate::get(c->receiver);
rd->ensureConnectionData();
@@ -350,39 +351,27 @@ void QObjectPrivate::addConnection(int signal, Connection *c)
c->next->prev = &c->next;
}
-void QObjectPrivate::cleanConnectionLists()
+void QObjectPrivate::ConnectionData::cleanOrphanedConnectionsImpl(QObject *sender)
{
- ConnectionData *cd = connections.load();
- if (cd->dirty && cd->ref == 1) {
- // remove broken connections
- for (int signal = -1; signal < cd->signalVector.count(); ++signal) {
- ConnectionList &connectionList = cd->connectionsForSignal(signal);
-
- // Set to the last entry in the connection list that was *not*
- // deleted. This is needed to update the list's last pointer
- // at the end of the cleanup.
- QObjectPrivate::Connection *last = 0;
-
- QObjectPrivate::Connection **prev = &connectionList.first;
- QObjectPrivate::Connection *c = *prev;
- while (c) {
- if (c->receiver) {
- last = c;
- prev = &c->nextConnectionList;
- c = *prev;
- } else {
- QObjectPrivate::Connection *next = c->nextConnectionList;
- *prev = next;
- c->deref();
- c = next;
- }
- }
+ Connection *c = nullptr;
+ {
+ QBasicMutexLocker l(signalSlotLock(sender));
+ if (ref > 1)
+ return;
- // Correct the connection list's last pointer.
- // As conectionList.last could equal last, this could be a noop
- connectionList.last = last;
- }
- cd->dirty = false;
+ // Since ref == 1, no activate() is in process since we locked the mutex. That implies,
+ // that nothing can reference the orphaned connection objects anymore and they can
+ // be safely deleted
+ c = orphaned.load();
+ orphaned.store(nullptr);
+ }
+ while (c) {
+ Q_ASSERT(!c->receiver);
+ Q_ASSERT(!c->prev);
+ QObjectPrivate::Connection *next = c->nextInOrphanList;
+ c->freeSlotObject();
+ c->deref();
+ c = next;
}
}
@@ -905,47 +894,22 @@ QObject::~QObject()
QObjectPrivate::ConnectionList &connectionList = cd->connectionsForSignal(signal);
while (QObjectPrivate::Connection *c = connectionList.first) {
- if (!c->receiver) {
- connectionList.first = c->nextConnectionList;
- c->deref();
- continue;
- }
+ Q_ASSERT(c->receiver);
QBasicMutex *m = signalSlotLock(c->receiver);
bool needToUnlock = QOrderedMutexLocker::relock(signalSlotMutex, m);
-
if (c->receiver) {
- *c->prev = c->next;
- if (c->next) c->next->prev = c->prev;
+ cd->removeConnection(c);
+ Q_ASSERT(connectionList.first != c);
}
- c->receiver = 0;
if (needToUnlock)
m->unlock();
-
- connectionList.first = c->nextConnectionList;
-
- // The destroy operation must happen outside the lock
- if (c->isSlotObject) {
- c->isSlotObject = false;
- locker.unlock();
- c->slotObj->destroyIfLastRef();
- locker.relock();
- }
- c->deref();
}
}
/* Disconnect all senders:
- * This loop basically just does
- * for (node = d->senders; node; node = node->next) { ... }
- *
- * We need to temporarily unlock the receiver mutex to destroy the functors or to lock the
- * sender's mutex. And when the mutex is released, node->next might be destroyed by another
- * thread. That's why we set node->prev to &node, that way, if node is destroyed, node will
- * be updated.
*/
- QObjectPrivate::Connection *node = cd->senders;
- while (node) {
+ while (QObjectPrivate::Connection *node = cd->senders) {
Q_ASSERT(node->receiver);
QObject *sender = node->sender;
// Send disconnectNotify before removing the connection from sender's connection list.
@@ -953,19 +917,17 @@ QObject::~QObject()
// and not finish until we release it.
sender->disconnectNotify(QMetaObjectPrivate::signal(sender->metaObject(), node->signal_index));
QBasicMutex *m = signalSlotLock(sender);
- node->prev = &node;
bool needToUnlock = QOrderedMutexLocker::relock(signalSlotMutex, m);
//the node has maybe been removed while the mutex was unlocked in relock?
- if (!node || node->sender != sender) {
+ if (node != cd->senders) {
// We hold the wrong mutex
Q_ASSERT(needToUnlock);
m->unlock();
continue;
}
- node->receiver = 0;
+
QObjectPrivate::ConnectionData *senderData = sender->d_func()->connections.load();
- if (senderData)
- senderData->dirty = true;
+ Q_ASSERT(senderData);
QtPrivate::QSlotObjectBase *slotObj = nullptr;
if (node->isSlotObject) {
@@ -973,20 +935,20 @@ QObject::~QObject()
node->isSlotObject = false;
}
- node = node->next;
+ senderData->removeConnection(node);
if (needToUnlock)
m->unlock();
if (slotObj) {
- if (node)
- node->prev = &node;
locker.unlock();
slotObj->destroyIfLastRef();
locker.relock();
}
}
- cd->objectDeleted = true;
+ // invalidate all connections on the object and make sure
+ // activate() will skip them
+ cd->currentConnectionId.store(0);
}
if (cd && !cd->ref.deref())
delete cd;
@@ -3333,16 +3295,19 @@ bool QMetaObject::disconnectOne(const QObject *sender, int signal_index,
\internal
Helper function to remove the connection from the senders list and setting the receivers to 0
*/
-bool QMetaObjectPrivate::disconnectHelper(QObjectPrivate::Connection *c,
+bool QMetaObjectPrivate::disconnectHelper(QObjectPrivate::ConnectionData *connections, int signalIndex,
const QObject *receiver, int method_index, void **slot,
QBasicMutex *senderMutex, DisconnectType disconnectType)
{
bool success = false;
+
+ auto &connectionList = connections->connectionsForSignal(signalIndex);
+ auto *c = connectionList.first;
while (c) {
if (c->receiver
- && (receiver == 0 || (c->receiver == receiver
+ && (receiver == nullptr || (c->receiver == receiver
&& (method_index < 0 || (!c->isSlotObject && c->method() == method_index))
- && (slot == 0 || (c->isSlotObject && c->slotObj->compare(slot)))))) {
+ && (slot == nullptr || (c->isSlotObject && c->slotObj->compare(slot)))))) {
bool needToUnlock = false;
QBasicMutex *receiverMutex = nullptr;
if (c->receiver) {
@@ -3350,24 +3315,12 @@ bool QMetaObjectPrivate::disconnectHelper(QObjectPrivate::Connection *c,
// need to relock this receiver and sender in the correct order
needToUnlock = QOrderedMutexLocker::relock(senderMutex, receiverMutex);
}
- if (c->receiver) {
- *c->prev = c->next;
- if (c->next)
- c->next->prev = c->prev;
- }
+ if (c->receiver)
+ connections->removeConnection(c);
if (needToUnlock)
receiverMutex->unlock();
- c->receiver = 0;
-
- if (c->isSlotObject) {
- c->isSlotObject = false;
- senderMutex->unlock();
- c->slotObj->destroyIfLastRef();
- senderMutex->lock();
- }
-
success = true;
if (disconnectType == DisconnectOne)
@@ -3407,23 +3360,19 @@ bool QMetaObjectPrivate::disconnect(const QObject *sender,
if (signal_index < 0) {
// remove from all connection lists
for (int sig_index = -1; sig_index < scd->signalVector.count(); ++sig_index) {
- QObjectPrivate::Connection *c = scd->connectionsForSignal(sig_index).first;
- if (disconnectHelper(c, receiver, method_index, slot, senderMutex, disconnectType)) {
+ if (disconnectHelper(connections.data(), sig_index, receiver, method_index, slot, senderMutex, disconnectType))
success = true;
- scd->dirty = true;
- }
}
} else if (signal_index < scd->signalVector.count()) {
- QObjectPrivate::Connection *c = scd->signalVector.at(signal_index).first;
- if (disconnectHelper(c, receiver, method_index, slot, senderMutex, disconnectType)) {
+ if (disconnectHelper(connections.data(), signal_index, receiver, method_index, slot, senderMutex, disconnectType))
success = true;
- scd->dirty = true;
- }
}
}
locker.unlock();
if (success) {
+ scd->cleanOrphanedConnections(s);
+
QMetaMethod smethod = QMetaObjectPrivate::signal(smeta, signal_index);
if (smethod.isValid())
s->disconnectNotify(smethod);
@@ -3630,6 +3579,7 @@ void doActivate(QObject *sender, int signal_index, void **argv)
signal_spy_set->signal_begin_callback(sender, signal_index, argv);
Q_TRACE(QMetaObject_activate_begin_signal, sender, signal_index);
+ bool senderDeleted = false;
{
QBasicMutexLocker locker(signalSlotLock(sender));
Q_ASSERT(sp->connections);
@@ -3643,12 +3593,13 @@ void doActivate(QObject *sender, int signal_index, void **argv)
Qt::HANDLE currentThreadId = QThread::currentThreadId();
+ // We need to check against the highest connection id to ensure that signals added
+ // during the signal emission are not emitted in this emission.
+ uint highestConnectionId = connections->currentConnectionId.load();
do {
QObjectPrivate::Connection *c = list->first;
- if (!c) continue;
- // We need to check against last here to ensure that signals added
- // during the signal emission are not emitted in this emission.
- QObjectPrivate::Connection *last = list->last;
+ if (!c)
+ continue;
do {
if (!c->receiver)
@@ -3732,18 +3683,17 @@ void doActivate(QObject *sender, int signal_index, void **argv)
locker.relock();
}
+ } while ((c = c->nextConnectionList) != 0 && c->id <= highestConnectionId);
- if (connections->objectDeleted)
- break;
- } while (c != last && (c = c->nextConnectionList) != 0);
-
- if (connections->objectDeleted)
- break;
} while (list != &connections->allsignals &&
//start over for all signals;
((list = &connections->allsignals), true));
+ if (connections->currentConnectionId.load() == 0)
+ senderDeleted = true;
}
+ if (!senderDeleted)
+ sp->connections.load()->cleanOrphanedConnections(sender);
if (callbacks_enabled && signal_spy_set->signal_end_callback != nullptr)
signal_spy_set->signal_end_callback(sender, signal_index);
@@ -4859,30 +4809,25 @@ bool QObject::disconnect(const QMetaObject::Connection &connection)
{
QObjectPrivate::Connection *c = static_cast<QObjectPrivate::Connection *>(connection.d_ptr);
- if (!c || !c->receiver)
+ if (!c)
return false;
QBasicMutex *senderMutex = signalSlotLock(c->sender);
QBasicMutex *receiverMutex = signalSlotLock(c->receiver);
+ QObjectPrivate::ConnectionData *connections;
{
QOrderedMutexLocker locker(senderMutex, receiverMutex);
- QObjectPrivate::ConnectionData *connections = QObjectPrivate::get(c->sender)->connections.load();
- Q_ASSERT(connections);
- connections->dirty = true;
+ if (!c->receiver)
+ return false;
- *c->prev = c->next;
- if (c->next)
- c->next->prev = c->prev;
- c->receiver = nullptr;
+ connections = QObjectPrivate::get(c->sender)->connections.load();
+ Q_ASSERT(connections);
+ connections->removeConnection(c);
}
- // destroy the QSlotObject, if possible
- if (c->isSlotObject) {
- c->slotObj->destroyIfLastRef();
- c->isSlotObject = false;
- }
+ connections->cleanOrphanedConnections(c->sender);
c->sender->disconnectNotify(QMetaObjectPrivate::signal(c->sender->metaObject(),
c->signal_index));
diff --git a/src/corelib/kernel/qobject_p.h b/src/corelib/kernel/qobject_p.h
index 863689fddd..2c1502c2a0 100644
--- a/src/corelib/kernel/qobject_p.h
+++ b/src/corelib/kernel/qobject_p.h
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2019 The Qt Company Ltd.
** Copyright (C) 2013 Olivier Goffart <ogoffart@woboq.com>
** Contact: https://www.qt.io/licensing/
**
@@ -126,22 +126,29 @@ public:
typedef void (*StaticMetaCallFunction)(QObject *, QMetaObject::Call, int, void **);
struct Connection
{
+ union {
+ // linked list of orphaned connections that need cleaning up
+ Connection *nextInOrphanList;
+ // linked list of connections connected to slots in this object
+ Connection *next;
+ };
+ Connection **prev;
+ // linked list of connections connected to signals in this object
+ Connection *nextConnectionList;
+ Connection *prevConnectionList;
+
QObject *sender;
QObject *receiver;
union {
StaticMetaCallFunction callFunction;
QtPrivate::QSlotObjectBase *slotObj;
};
- // The next pointer for the singly-linked ConnectionList
- Connection *nextConnectionList;
- //senders linked list
- Connection *next;
- Connection **prev;
QAtomicPointer<const int> argumentTypes;
QAtomicInt ref_;
+ uint id = 0;
ushort method_offset;
ushort method_relative;
- uint signal_index : 27; // In signal range (see QObjectPrivate::signalIndex())
+ int signal_index : 27; // In signal range (see QObjectPrivate::signalIndex())
ushort connectionType : 3; // 0 == auto, 1 == direct, 2 == queued, 4 == blocking
ushort isSlotObject : 1;
ushort ownArgumentTypes : 1;
@@ -151,9 +158,17 @@ public:
~Connection();
int method() const { Q_ASSERT(!isSlotObject); return method_offset + method_relative; }
void ref() { ref_.ref(); }
+ void freeSlotObject()
+ {
+ if (isSlotObject) {
+ slotObj->destroyIfLastRef();
+ isSlotObject = false;
+ }
+ }
void deref() {
if (!ref_.deref()) {
Q_ASSERT(!receiver);
+ Q_ASSERT(!isSlotObject);
delete this;
}
}
@@ -210,7 +225,9 @@ public:
linked list.
*/
struct ConnectionData {
- bool objectDeleted = false; //the QObject owner of this vector has been destroyed while the vector was inUse
+ // the id below is used to avoid activating new connections. When the object gets
+ // deleted it's set to 0, so that signal emission stops
+ QAtomicInteger<uint> currentConnectionId;
struct Ref {
int _ref = 0;
void ref() { ++_ref; }
@@ -219,11 +236,84 @@ public:
};
Ref ref;
- bool dirty = false; //some Connection have been disconnected (their receiver is 0) but not removed from the list yet
ConnectionList allsignals;
QVector<ConnectionList> signalVector;
Connection *senders = nullptr;
Sender *currentSender = nullptr; // object currently activating the object
+ QAtomicPointer<Connection> orphaned;
+
+ ~ConnectionData()
+ {
+ Connection *c = orphaned.load();
+ while (c) {
+ Q_ASSERT(!c->receiver);
+ QObjectPrivate::Connection *next = c->nextInOrphanList;
+ c->freeSlotObject();
+ c->deref();
+ c = next;
+ }
+ }
+
+ // must be called on the senders connection data
+ // assumes the senders and receivers lock are held
+ void removeConnection(Connection *c)
+ {
+ Q_ASSERT(c->receiver);
+ ConnectionList &connections = connectionsForSignal(c->signal_index);
+ c->receiver = nullptr;
+
+#ifndef QT_NO_DEBUG
+ bool found = false;
+ for (Connection *cc = connections.first; cc; cc = cc->nextConnectionList) {
+ if (cc == c) {
+ found = true;
+ break;
+ }
+ }
+ Q_ASSERT(found);
+#endif
+
+ // remove from the senders linked list
+ *c->prev = c->next;
+ if (c->next)
+ c->next->prev = c->prev;
+ c->prev = nullptr;
+
+ if (connections.first == c)
+ connections.first = c->nextConnectionList;
+ if (connections.last == c)
+ connections.last = c->prevConnectionList;
+
+ // keep c->nextConnectionList intact, as it might still get accessed by activate
+ if (c->nextConnectionList)
+ c->nextConnectionList->prevConnectionList = c->prevConnectionList;
+ if (c->prevConnectionList)
+ c->prevConnectionList->nextConnectionList = c->nextConnectionList;
+ c->prevConnectionList = nullptr;
+
+ Q_ASSERT(c != orphaned.load());
+ // add c to orphanedConnections
+ c->nextInOrphanList = orphaned.load();
+ orphaned.store(c);
+
+#ifndef QT_NO_DEBUG
+ found = false;
+ for (Connection *cc = connections.first; cc; cc = cc->nextConnectionList) {
+ if (cc == c) {
+ found = true;
+ break;
+ }
+ }
+ Q_ASSERT(!found);
+#endif
+
+ }
+ void cleanOrphanedConnections(QObject *sender)
+ {
+ if (orphaned.load() && ref == 1)
+ cleanOrphanedConnectionsImpl(sender);
+ }
+ void cleanOrphanedConnectionsImpl(QObject *sender);
ConnectionList &connectionsForSignal(int signal)
{
@@ -245,7 +335,6 @@ public:
QObjectList senderList() const;
void addConnection(int signal, Connection *c);
- void cleanConnectionLists();
static QObjectPrivate *get(QObject *o) {
return o->d_func();