summaryrefslogtreecommitdiffstats
path: root/src/corelib/kernel/qobject.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/kernel/qobject.cpp')
-rw-r--r--src/corelib/kernel/qobject.cpp71
1 files changed, 55 insertions, 16 deletions
diff --git a/src/corelib/kernel/qobject.cpp b/src/corelib/kernel/qobject.cpp
index 9385419015..d9c324f9f9 100644
--- a/src/corelib/kernel/qobject.cpp
+++ b/src/corelib/kernel/qobject.cpp
@@ -71,6 +71,7 @@
#include <qtcore_tracepoints_p.h>
#include <new>
+#include <mutex>
#include <ctype.h>
#include <limits.h>
@@ -390,8 +391,14 @@ void QObjectPrivate::ConnectionData::removeConnection(QObjectPrivate::Connection
Q_ASSERT(c != orphaned.loadRelaxed());
// add c to orphanedConnections
- c->nextInOrphanList = orphaned.loadRelaxed();
- orphaned.storeRelaxed(c);
+ Connection *o = nullptr;
+ /* No ABA issue here: When adding a node, we only care about the list head, it doesn't
+ * matter if the tail changes.
+ */
+ do {
+ o = orphaned.loadRelaxed();
+ c->nextInOrphanList = o;
+ } while (!orphaned.testAndSetRelease(o, c));
#ifndef QT_NO_DEBUG
found = false;
@@ -406,21 +413,32 @@ void QObjectPrivate::ConnectionData::removeConnection(QObjectPrivate::Connection
}
-void QObjectPrivate::ConnectionData::cleanOrphanedConnectionsImpl(QObject *sender)
+void QObjectPrivate::ConnectionData::cleanOrphanedConnectionsImpl(QObject *sender, LockPolicy lockPolicy)
{
+ QBasicMutex *senderMutex = signalSlotLock(sender);
ConnectionOrSignalVector *c = nullptr;
{
- QBasicMutexLocker l(signalSlotLock(sender));
+ std::unique_lock<QBasicMutex> lock(*senderMutex, std::defer_lock_t{});
+ if (lockPolicy == NeedToLock)
+ lock.lock();
if (ref.loadAcquire() > 1)
return;
// 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.loadRelaxed();
- orphaned.storeRelaxed(nullptr);
+ c = orphaned.fetchAndStoreRelaxed(nullptr);
+ }
+ if (c) {
+ // Deleting c might run arbitrary user code, so we must not hold the lock
+ if (lockPolicy == AlreadyLockedAndTemporarilyReleasingLock) {
+ senderMutex->unlock();
+ deleteOrphaned(c);
+ senderMutex->lock();
+ } else {
+ deleteOrphaned(c);
+ }
}
- deleteOrphaned(c);
}
void QObjectPrivate::ConnectionData::deleteOrphaned(QObjectPrivate::ConnectionOrSignalVector *o)
@@ -1022,7 +1040,7 @@ QObject::~QObject()
QBasicMutex *m = signalSlotLock(c->receiver.loadRelaxed());
bool needToUnlock = QOrderedMutexLocker::relock(signalSlotMutex, m);
- if (c->receiver.loadAcquire()) {
+ if (c == connectionList.first.loadAcquire() && c->receiver.loadAcquire()) {
cd->removeConnection(c);
Q_ASSERT(connectionList.first.loadRelaxed() != c);
}
@@ -1060,14 +1078,29 @@ QObject::~QObject()
}
senderData->removeConnection(node);
+ /*
+ When we unlock, another thread has the chance to delete/modify sender data.
+ Thus we need to call cleanOrphanedConnections before unlocking. We use the
+ variant of the function which assumes that the lock is already held to avoid
+ a deadlock.
+ We need to hold m, the sender lock. Considering that we might execute arbitrary user
+ code, we should already release the signalSlotMutex here – unless they are the same.
+ */
+ const bool locksAreTheSame = signalSlotMutex == m;
+ if (!locksAreTheSame)
+ locker.unlock();
+ senderData->cleanOrphanedConnections(
+ sender,
+ QObjectPrivate::ConnectionData::AlreadyLockedAndTemporarilyReleasingLock
+ );
if (needToUnlock)
m->unlock();
- if (slotObj) {
+ if (locksAreTheSame) // otherwise already unlocked
locker.unlock();
+ if (slotObj)
slotObj->destroyIfLastRef();
- locker.relock();
- }
+ locker.relock();
}
// invalidate all connections on the object and make sure
@@ -5106,12 +5139,18 @@ bool QObject::disconnect(const QMetaObject::Connection &connection)
connections = QObjectPrivate::get(c->sender)->connections.loadRelaxed();
Q_ASSERT(connections);
connections->removeConnection(c);
- }
-
- connections->cleanOrphanedConnections(c->sender);
- c->sender->disconnectNotify(QMetaObjectPrivate::signal(c->sender->metaObject(),
- c->signal_index));
+ c->sender->disconnectNotify(QMetaObjectPrivate::signal(c->sender->metaObject(), c->signal_index));
+ // We must not hold the receiver mutex, else we risk dead-locking; we also only need the sender mutex
+ // It is however vital to hold the senderMutex before calling cleanOrphanedConnections, as otherwise
+ // another thread might modify/delete the connection
+ if (receiverMutex != senderMutex) {
+ receiverMutex->unlock();
+ }
+ connections->cleanOrphanedConnections(c->sender, QObjectPrivate::ConnectionData::AlreadyLockedAndTemporarilyReleasingLock);
+ senderMutex->unlock(); // now both sender and receiver mutex have been manually unlocked
+ locker.dismiss(); // so we dismiss the QOrderedMutexLocker
+ }
const_cast<QMetaObject::Connection &>(connection).d_ptr = nullptr;
c->deref(); // has been removed from the QMetaObject::Connection object