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>
This commit is contained in:
parent
a2fda801cc
commit
6e0b5dadc7
@ -232,7 +232,7 @@ struct QMetaObjectPrivate
|
|||||||
const QMetaObject *smeta,
|
const QMetaObject *smeta,
|
||||||
const QObject *receiver, int method_index, void **slot,
|
const QObject *receiver, int method_index, void **slot,
|
||||||
DisconnectType = DisconnectAll);
|
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,
|
const QObject *receiver, int method_index, void **slot,
|
||||||
QBasicMutex *senderMutex, DisconnectType = DisconnectAll);
|
QBasicMutex *senderMutex, DisconnectType = DisconnectAll);
|
||||||
#endif
|
#endif
|
||||||
|
@ -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) 2016 Intel Corporation.
|
||||||
** Copyright (C) 2013 Olivier Goffart <ogoffart@woboq.com>
|
** Copyright (C) 2013 Olivier Goffart <ogoffart@woboq.com>
|
||||||
** Contact: https://www.qt.io/licensing/
|
** Contact: https://www.qt.io/licensing/
|
||||||
@ -332,14 +332,15 @@ void QObjectPrivate::addConnection(int signal, Connection *c)
|
|||||||
|
|
||||||
ConnectionList &connectionList = cd->connectionsForSignal(signal);
|
ConnectionList &connectionList = cd->connectionsForSignal(signal);
|
||||||
if (connectionList.last) {
|
if (connectionList.last) {
|
||||||
|
Q_ASSERT(connectionList.last->receiver);
|
||||||
connectionList.last->nextConnectionList = c;
|
connectionList.last->nextConnectionList = c;
|
||||||
} else {
|
} else {
|
||||||
connectionList.first = c;
|
connectionList.first = c;
|
||||||
}
|
}
|
||||||
|
c->id = ++cd->currentConnectionId;
|
||||||
|
c->prevConnectionList = connectionList.last;
|
||||||
connectionList.last = c;
|
connectionList.last = c;
|
||||||
|
|
||||||
cleanConnectionLists();
|
|
||||||
|
|
||||||
QObjectPrivate *rd = QObjectPrivate::get(c->receiver);
|
QObjectPrivate *rd = QObjectPrivate::get(c->receiver);
|
||||||
rd->ensureConnectionData();
|
rd->ensureConnectionData();
|
||||||
|
|
||||||
@ -350,39 +351,27 @@ void QObjectPrivate::addConnection(int signal, Connection *c)
|
|||||||
c->next->prev = &c->next;
|
c->next->prev = &c->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
void QObjectPrivate::cleanConnectionLists()
|
void QObjectPrivate::ConnectionData::cleanOrphanedConnectionsImpl(QObject *sender)
|
||||||
{
|
{
|
||||||
ConnectionData *cd = connections.load();
|
Connection *c = nullptr;
|
||||||
if (cd->dirty && cd->ref == 1) {
|
{
|
||||||
// remove broken connections
|
QBasicMutexLocker l(signalSlotLock(sender));
|
||||||
for (int signal = -1; signal < cd->signalVector.count(); ++signal) {
|
if (ref > 1)
|
||||||
ConnectionList &connectionList = cd->connectionsForSignal(signal);
|
return;
|
||||||
|
|
||||||
// Set to the last entry in the connection list that was *not*
|
// Since ref == 1, no activate() is in process since we locked the mutex. That implies,
|
||||||
// deleted. This is needed to update the list's last pointer
|
// that nothing can reference the orphaned connection objects anymore and they can
|
||||||
// at the end of the cleanup.
|
// be safely deleted
|
||||||
QObjectPrivate::Connection *last = 0;
|
c = orphaned.load();
|
||||||
|
orphaned.store(nullptr);
|
||||||
QObjectPrivate::Connection **prev = &connectionList.first;
|
}
|
||||||
QObjectPrivate::Connection *c = *prev;
|
while (c) {
|
||||||
while (c) {
|
Q_ASSERT(!c->receiver);
|
||||||
if (c->receiver) {
|
Q_ASSERT(!c->prev);
|
||||||
last = c;
|
QObjectPrivate::Connection *next = c->nextInOrphanList;
|
||||||
prev = &c->nextConnectionList;
|
c->freeSlotObject();
|
||||||
c = *prev;
|
c->deref();
|
||||||
} else {
|
c = next;
|
||||||
QObjectPrivate::Connection *next = c->nextConnectionList;
|
|
||||||
*prev = next;
|
|
||||||
c->deref();
|
|
||||||
c = next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Correct the connection list's last pointer.
|
|
||||||
// As conectionList.last could equal last, this could be a noop
|
|
||||||
connectionList.last = last;
|
|
||||||
}
|
|
||||||
cd->dirty = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -905,47 +894,22 @@ QObject::~QObject()
|
|||||||
QObjectPrivate::ConnectionList &connectionList = cd->connectionsForSignal(signal);
|
QObjectPrivate::ConnectionList &connectionList = cd->connectionsForSignal(signal);
|
||||||
|
|
||||||
while (QObjectPrivate::Connection *c = connectionList.first) {
|
while (QObjectPrivate::Connection *c = connectionList.first) {
|
||||||
if (!c->receiver) {
|
Q_ASSERT(c->receiver);
|
||||||
connectionList.first = c->nextConnectionList;
|
|
||||||
c->deref();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
QBasicMutex *m = signalSlotLock(c->receiver);
|
QBasicMutex *m = signalSlotLock(c->receiver);
|
||||||
bool needToUnlock = QOrderedMutexLocker::relock(signalSlotMutex, m);
|
bool needToUnlock = QOrderedMutexLocker::relock(signalSlotMutex, m);
|
||||||
|
|
||||||
if (c->receiver) {
|
if (c->receiver) {
|
||||||
*c->prev = c->next;
|
cd->removeConnection(c);
|
||||||
if (c->next) c->next->prev = c->prev;
|
Q_ASSERT(connectionList.first != c);
|
||||||
}
|
}
|
||||||
c->receiver = 0;
|
|
||||||
if (needToUnlock)
|
if (needToUnlock)
|
||||||
m->unlock();
|
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:
|
/* 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 (QObjectPrivate::Connection *node = cd->senders) {
|
||||||
while (node) {
|
|
||||||
Q_ASSERT(node->receiver);
|
Q_ASSERT(node->receiver);
|
||||||
QObject *sender = node->sender;
|
QObject *sender = node->sender;
|
||||||
// Send disconnectNotify before removing the connection from sender's connection list.
|
// Send disconnectNotify before removing the connection from sender's connection list.
|
||||||
@ -953,19 +917,17 @@ QObject::~QObject()
|
|||||||
// and not finish until we release it.
|
// and not finish until we release it.
|
||||||
sender->disconnectNotify(QMetaObjectPrivate::signal(sender->metaObject(), node->signal_index));
|
sender->disconnectNotify(QMetaObjectPrivate::signal(sender->metaObject(), node->signal_index));
|
||||||
QBasicMutex *m = signalSlotLock(sender);
|
QBasicMutex *m = signalSlotLock(sender);
|
||||||
node->prev = &node;
|
|
||||||
bool needToUnlock = QOrderedMutexLocker::relock(signalSlotMutex, m);
|
bool needToUnlock = QOrderedMutexLocker::relock(signalSlotMutex, m);
|
||||||
//the node has maybe been removed while the mutex was unlocked in relock?
|
//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
|
// We hold the wrong mutex
|
||||||
Q_ASSERT(needToUnlock);
|
Q_ASSERT(needToUnlock);
|
||||||
m->unlock();
|
m->unlock();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
node->receiver = 0;
|
|
||||||
QObjectPrivate::ConnectionData *senderData = sender->d_func()->connections.load();
|
QObjectPrivate::ConnectionData *senderData = sender->d_func()->connections.load();
|
||||||
if (senderData)
|
Q_ASSERT(senderData);
|
||||||
senderData->dirty = true;
|
|
||||||
|
|
||||||
QtPrivate::QSlotObjectBase *slotObj = nullptr;
|
QtPrivate::QSlotObjectBase *slotObj = nullptr;
|
||||||
if (node->isSlotObject) {
|
if (node->isSlotObject) {
|
||||||
@ -973,20 +935,20 @@ QObject::~QObject()
|
|||||||
node->isSlotObject = false;
|
node->isSlotObject = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
node = node->next;
|
senderData->removeConnection(node);
|
||||||
if (needToUnlock)
|
if (needToUnlock)
|
||||||
m->unlock();
|
m->unlock();
|
||||||
|
|
||||||
if (slotObj) {
|
if (slotObj) {
|
||||||
if (node)
|
|
||||||
node->prev = &node;
|
|
||||||
locker.unlock();
|
locker.unlock();
|
||||||
slotObj->destroyIfLastRef();
|
slotObj->destroyIfLastRef();
|
||||||
locker.relock();
|
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())
|
if (cd && !cd->ref.deref())
|
||||||
delete cd;
|
delete cd;
|
||||||
@ -3333,16 +3295,19 @@ bool QMetaObject::disconnectOne(const QObject *sender, int signal_index,
|
|||||||
\internal
|
\internal
|
||||||
Helper function to remove the connection from the senders list and setting the receivers to 0
|
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,
|
const QObject *receiver, int method_index, void **slot,
|
||||||
QBasicMutex *senderMutex, DisconnectType disconnectType)
|
QBasicMutex *senderMutex, DisconnectType disconnectType)
|
||||||
{
|
{
|
||||||
bool success = false;
|
bool success = false;
|
||||||
|
|
||||||
|
auto &connectionList = connections->connectionsForSignal(signalIndex);
|
||||||
|
auto *c = connectionList.first;
|
||||||
while (c) {
|
while (c) {
|
||||||
if (c->receiver
|
if (c->receiver
|
||||||
&& (receiver == 0 || (c->receiver == receiver
|
&& (receiver == nullptr || (c->receiver == receiver
|
||||||
&& (method_index < 0 || (!c->isSlotObject && c->method() == method_index))
|
&& (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;
|
bool needToUnlock = false;
|
||||||
QBasicMutex *receiverMutex = nullptr;
|
QBasicMutex *receiverMutex = nullptr;
|
||||||
if (c->receiver) {
|
if (c->receiver) {
|
||||||
@ -3350,24 +3315,12 @@ bool QMetaObjectPrivate::disconnectHelper(QObjectPrivate::Connection *c,
|
|||||||
// need to relock this receiver and sender in the correct order
|
// need to relock this receiver and sender in the correct order
|
||||||
needToUnlock = QOrderedMutexLocker::relock(senderMutex, receiverMutex);
|
needToUnlock = QOrderedMutexLocker::relock(senderMutex, receiverMutex);
|
||||||
}
|
}
|
||||||
if (c->receiver) {
|
if (c->receiver)
|
||||||
*c->prev = c->next;
|
connections->removeConnection(c);
|
||||||
if (c->next)
|
|
||||||
c->next->prev = c->prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (needToUnlock)
|
if (needToUnlock)
|
||||||
receiverMutex->unlock();
|
receiverMutex->unlock();
|
||||||
|
|
||||||
c->receiver = 0;
|
|
||||||
|
|
||||||
if (c->isSlotObject) {
|
|
||||||
c->isSlotObject = false;
|
|
||||||
senderMutex->unlock();
|
|
||||||
c->slotObj->destroyIfLastRef();
|
|
||||||
senderMutex->lock();
|
|
||||||
}
|
|
||||||
|
|
||||||
success = true;
|
success = true;
|
||||||
|
|
||||||
if (disconnectType == DisconnectOne)
|
if (disconnectType == DisconnectOne)
|
||||||
@ -3407,23 +3360,19 @@ bool QMetaObjectPrivate::disconnect(const QObject *sender,
|
|||||||
if (signal_index < 0) {
|
if (signal_index < 0) {
|
||||||
// remove from all connection lists
|
// remove from all connection lists
|
||||||
for (int sig_index = -1; sig_index < scd->signalVector.count(); ++sig_index) {
|
for (int sig_index = -1; sig_index < scd->signalVector.count(); ++sig_index) {
|
||||||
QObjectPrivate::Connection *c = scd->connectionsForSignal(sig_index).first;
|
if (disconnectHelper(connections.data(), sig_index, receiver, method_index, slot, senderMutex, disconnectType))
|
||||||
if (disconnectHelper(c, receiver, method_index, slot, senderMutex, disconnectType)) {
|
|
||||||
success = true;
|
success = true;
|
||||||
scd->dirty = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (signal_index < scd->signalVector.count()) {
|
} else if (signal_index < scd->signalVector.count()) {
|
||||||
QObjectPrivate::Connection *c = scd->signalVector.at(signal_index).first;
|
if (disconnectHelper(connections.data(), signal_index, receiver, method_index, slot, senderMutex, disconnectType))
|
||||||
if (disconnectHelper(c, receiver, method_index, slot, senderMutex, disconnectType)) {
|
|
||||||
success = true;
|
success = true;
|
||||||
scd->dirty = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
locker.unlock();
|
locker.unlock();
|
||||||
if (success) {
|
if (success) {
|
||||||
|
scd->cleanOrphanedConnections(s);
|
||||||
|
|
||||||
QMetaMethod smethod = QMetaObjectPrivate::signal(smeta, signal_index);
|
QMetaMethod smethod = QMetaObjectPrivate::signal(smeta, signal_index);
|
||||||
if (smethod.isValid())
|
if (smethod.isValid())
|
||||||
s->disconnectNotify(smethod);
|
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);
|
signal_spy_set->signal_begin_callback(sender, signal_index, argv);
|
||||||
Q_TRACE(QMetaObject_activate_begin_signal, sender, signal_index);
|
Q_TRACE(QMetaObject_activate_begin_signal, sender, signal_index);
|
||||||
|
|
||||||
|
bool senderDeleted = false;
|
||||||
{
|
{
|
||||||
QBasicMutexLocker locker(signalSlotLock(sender));
|
QBasicMutexLocker locker(signalSlotLock(sender));
|
||||||
Q_ASSERT(sp->connections);
|
Q_ASSERT(sp->connections);
|
||||||
@ -3643,12 +3593,13 @@ void doActivate(QObject *sender, int signal_index, void **argv)
|
|||||||
|
|
||||||
Qt::HANDLE currentThreadId = QThread::currentThreadId();
|
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 {
|
do {
|
||||||
QObjectPrivate::Connection *c = list->first;
|
QObjectPrivate::Connection *c = list->first;
|
||||||
if (!c) continue;
|
if (!c)
|
||||||
// We need to check against last here to ensure that signals added
|
continue;
|
||||||
// during the signal emission are not emitted in this emission.
|
|
||||||
QObjectPrivate::Connection *last = list->last;
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if (!c->receiver)
|
if (!c->receiver)
|
||||||
@ -3732,18 +3683,17 @@ void doActivate(QObject *sender, int signal_index, void **argv)
|
|||||||
|
|
||||||
locker.relock();
|
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 &&
|
} while (list != &connections->allsignals &&
|
||||||
//start over for all signals;
|
//start over for all signals;
|
||||||
((list = &connections->allsignals), true));
|
((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)
|
if (callbacks_enabled && signal_spy_set->signal_end_callback != nullptr)
|
||||||
signal_spy_set->signal_end_callback(sender, signal_index);
|
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);
|
QObjectPrivate::Connection *c = static_cast<QObjectPrivate::Connection *>(connection.d_ptr);
|
||||||
|
|
||||||
if (!c || !c->receiver)
|
if (!c)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
QBasicMutex *senderMutex = signalSlotLock(c->sender);
|
QBasicMutex *senderMutex = signalSlotLock(c->sender);
|
||||||
QBasicMutex *receiverMutex = signalSlotLock(c->receiver);
|
QBasicMutex *receiverMutex = signalSlotLock(c->receiver);
|
||||||
|
|
||||||
|
QObjectPrivate::ConnectionData *connections;
|
||||||
{
|
{
|
||||||
QOrderedMutexLocker locker(senderMutex, receiverMutex);
|
QOrderedMutexLocker locker(senderMutex, receiverMutex);
|
||||||
|
|
||||||
QObjectPrivate::ConnectionData *connections = QObjectPrivate::get(c->sender)->connections.load();
|
if (!c->receiver)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
connections = QObjectPrivate::get(c->sender)->connections.load();
|
||||||
Q_ASSERT(connections);
|
Q_ASSERT(connections);
|
||||||
connections->dirty = true;
|
connections->removeConnection(c);
|
||||||
|
|
||||||
*c->prev = c->next;
|
|
||||||
if (c->next)
|
|
||||||
c->next->prev = c->prev;
|
|
||||||
c->receiver = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// destroy the QSlotObject, if possible
|
connections->cleanOrphanedConnections(c->sender);
|
||||||
if (c->isSlotObject) {
|
|
||||||
c->slotObj->destroyIfLastRef();
|
|
||||||
c->isSlotObject = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
c->sender->disconnectNotify(QMetaObjectPrivate::signal(c->sender->metaObject(),
|
c->sender->disconnectNotify(QMetaObjectPrivate::signal(c->sender->metaObject(),
|
||||||
c->signal_index));
|
c->signal_index));
|
||||||
|
@ -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>
|
** Copyright (C) 2013 Olivier Goffart <ogoffart@woboq.com>
|
||||||
** Contact: https://www.qt.io/licensing/
|
** Contact: https://www.qt.io/licensing/
|
||||||
**
|
**
|
||||||
@ -126,22 +126,29 @@ public:
|
|||||||
typedef void (*StaticMetaCallFunction)(QObject *, QMetaObject::Call, int, void **);
|
typedef void (*StaticMetaCallFunction)(QObject *, QMetaObject::Call, int, void **);
|
||||||
struct Connection
|
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 *sender;
|
||||||
QObject *receiver;
|
QObject *receiver;
|
||||||
union {
|
union {
|
||||||
StaticMetaCallFunction callFunction;
|
StaticMetaCallFunction callFunction;
|
||||||
QtPrivate::QSlotObjectBase *slotObj;
|
QtPrivate::QSlotObjectBase *slotObj;
|
||||||
};
|
};
|
||||||
// The next pointer for the singly-linked ConnectionList
|
|
||||||
Connection *nextConnectionList;
|
|
||||||
//senders linked list
|
|
||||||
Connection *next;
|
|
||||||
Connection **prev;
|
|
||||||
QAtomicPointer<const int> argumentTypes;
|
QAtomicPointer<const int> argumentTypes;
|
||||||
QAtomicInt ref_;
|
QAtomicInt ref_;
|
||||||
|
uint id = 0;
|
||||||
ushort method_offset;
|
ushort method_offset;
|
||||||
ushort method_relative;
|
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 connectionType : 3; // 0 == auto, 1 == direct, 2 == queued, 4 == blocking
|
||||||
ushort isSlotObject : 1;
|
ushort isSlotObject : 1;
|
||||||
ushort ownArgumentTypes : 1;
|
ushort ownArgumentTypes : 1;
|
||||||
@ -151,9 +158,17 @@ public:
|
|||||||
~Connection();
|
~Connection();
|
||||||
int method() const { Q_ASSERT(!isSlotObject); return method_offset + method_relative; }
|
int method() const { Q_ASSERT(!isSlotObject); return method_offset + method_relative; }
|
||||||
void ref() { ref_.ref(); }
|
void ref() { ref_.ref(); }
|
||||||
|
void freeSlotObject()
|
||||||
|
{
|
||||||
|
if (isSlotObject) {
|
||||||
|
slotObj->destroyIfLastRef();
|
||||||
|
isSlotObject = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
void deref() {
|
void deref() {
|
||||||
if (!ref_.deref()) {
|
if (!ref_.deref()) {
|
||||||
Q_ASSERT(!receiver);
|
Q_ASSERT(!receiver);
|
||||||
|
Q_ASSERT(!isSlotObject);
|
||||||
delete this;
|
delete this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -210,7 +225,9 @@ public:
|
|||||||
linked list.
|
linked list.
|
||||||
*/
|
*/
|
||||||
struct ConnectionData {
|
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 {
|
struct Ref {
|
||||||
int _ref = 0;
|
int _ref = 0;
|
||||||
void ref() { ++_ref; }
|
void ref() { ++_ref; }
|
||||||
@ -219,11 +236,84 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
Ref ref;
|
Ref ref;
|
||||||
bool dirty = false; //some Connection have been disconnected (their receiver is 0) but not removed from the list yet
|
|
||||||
ConnectionList allsignals;
|
ConnectionList allsignals;
|
||||||
QVector<ConnectionList> signalVector;
|
QVector<ConnectionList> signalVector;
|
||||||
Connection *senders = nullptr;
|
Connection *senders = nullptr;
|
||||||
Sender *currentSender = nullptr; // object currently activating the object
|
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)
|
ConnectionList &connectionsForSignal(int signal)
|
||||||
{
|
{
|
||||||
@ -245,7 +335,6 @@ public:
|
|||||||
QObjectList senderList() const;
|
QObjectList senderList() const;
|
||||||
|
|
||||||
void addConnection(int signal, Connection *c);
|
void addConnection(int signal, Connection *c);
|
||||||
void cleanConnectionLists();
|
|
||||||
|
|
||||||
static QObjectPrivate *get(QObject *o) {
|
static QObjectPrivate *get(QObject *o) {
|
||||||
return o->d_func();
|
return o->d_func();
|
||||||
|
@ -3411,12 +3411,11 @@ void tst_QObject::disconnectSelfInSlotAndDeleteAfterEmit()
|
|||||||
void tst_QObject::dumpObjectInfo()
|
void tst_QObject::dumpObjectInfo()
|
||||||
{
|
{
|
||||||
QObject a, b;
|
QObject a, b;
|
||||||
QObject::connect(&a, SIGNAL(destroyed(QObject*)), &b, SLOT(deleteLater()));
|
QObject::connect(&a, &QObject::destroyed, &b, &QObject::deleteLater);
|
||||||
a.disconnect(&b);
|
|
||||||
QTest::ignoreMessage(QtDebugMsg, "OBJECT QObject::unnamed");
|
QTest::ignoreMessage(QtDebugMsg, "OBJECT QObject::unnamed");
|
||||||
QTest::ignoreMessage(QtDebugMsg, " SIGNALS OUT");
|
QTest::ignoreMessage(QtDebugMsg, " SIGNALS OUT");
|
||||||
QTest::ignoreMessage(QtDebugMsg, " signal: destroyed(QObject*)");
|
QTest::ignoreMessage(QtDebugMsg, " signal: destroyed(QObject*)");
|
||||||
QTest::ignoreMessage(QtDebugMsg, " <Disconnected receiver>");
|
QTest::ignoreMessage(QtDebugMsg, " <functor or function pointer>");
|
||||||
QTest::ignoreMessage(QtDebugMsg, " SIGNALS IN");
|
QTest::ignoreMessage(QtDebugMsg, " SIGNALS IN");
|
||||||
QTest::ignoreMessage(QtDebugMsg, " <None>");
|
QTest::ignoreMessage(QtDebugMsg, " <None>");
|
||||||
a.dumpObjectInfo(); // should not crash
|
a.dumpObjectInfo(); // should not crash
|
||||||
@ -7575,8 +7574,6 @@ void tst_QObject::functorReferencesConnection()
|
|||||||
// top-level + the one in the 3 others lambdas
|
// top-level + the one in the 3 others lambdas
|
||||||
QCOMPARE(countedStructObjectsCount, 4);
|
QCOMPARE(countedStructObjectsCount, 4);
|
||||||
QObject::disconnect(*c2);
|
QObject::disconnect(*c2);
|
||||||
// the one in the c2's lambda is gone
|
|
||||||
QCOMPARE(countedStructObjectsCount, 3);
|
|
||||||
slot1Called++;
|
slot1Called++;
|
||||||
});
|
});
|
||||||
connect(&obj, &GetSenderObject::aSignal, [] {}); // just a dummy signal to fill the connection list
|
connect(&obj, &GetSenderObject::aSignal, [] {}); // just a dummy signal to fill the connection list
|
||||||
|
Loading…
x
Reference in New Issue
Block a user