diff --git a/src/corelib/kernel/qobject.cpp b/src/corelib/kernel/qobject.cpp index 750f8b541ca..78394b86491 100644 --- a/src/corelib/kernel/qobject.cpp +++ b/src/corelib/kernel/qobject.cpp @@ -85,6 +85,221 @@ Q_LOGGING_CATEGORY(lcConnect, "qt.core.qobject.connect") Q_CORE_EXPORT QBasicAtomicPointer qt_signal_spy_callback_set = Q_BASIC_ATOMIC_INITIALIZER(nullptr); +// ConnectionList is a singly-linked list +struct QObjectPrivate::ConnectionList +{ + QAtomicPointer first; + QAtomicPointer last; +}; +static_assert(std::is_trivially_destructible_v); +Q_DECLARE_TYPEINFO(QObjectPrivate::ConnectionList, Q_RELOCATABLE_TYPE); + +struct QObjectPrivate::ConnectionOrSignalVector +{ + union { + // linked list of orphaned connections that need cleaning up + ConnectionOrSignalVector *nextInOrphanList; + // linked list of connections connected to slots in this object + Connection *next; + }; + + static SignalVector *asSignalVector(ConnectionOrSignalVector *c) + { + if (reinterpret_cast(c) & 1) + return reinterpret_cast(reinterpret_cast(c) & ~quintptr(1u)); + return nullptr; + } + static Connection *fromSignalVector(SignalVector *v) { + return reinterpret_cast(reinterpret_cast(v) | quintptr(1u)); + } +}; +static_assert(std::is_trivial_v); + +struct QObjectPrivate::Connection : public ConnectionOrSignalVector +{ + // linked list of connections connected to slots in this object, next is in base class + Connection **prev; + // linked list of connections connected to signals in this object + QAtomicPointer nextConnectionList; + Connection *prevConnectionList; + + QObject *sender; + QAtomicPointer receiver; + QAtomicPointer receiverThreadData; + union { + StaticMetaCallFunction callFunction; + QtPrivate::QSlotObjectBase *slotObj; + }; + QAtomicPointer argumentTypes; + QAtomicInt ref_; + uint id = 0; + ushort method_offset; + ushort method_relative; + signed int signal_index : 27; // In signal range (see QObjectPrivate::signalIndex()) + ushort connectionType : 2; // 0 == auto, 1 == direct, 2 == queued, 3 == blocking + ushort isSlotObject : 1; + ushort ownArgumentTypes : 1; + ushort isSingleShot : 1; + Connection() : ref_(2), ownArgumentTypes(true) { + //ref_ is 2 for the use in the internal lists, and for the use in QMetaObject::Connection + } + ~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.loadRelaxed()); + Q_ASSERT(!isSlotObject); + delete this; + } + } +}; +Q_DECLARE_TYPEINFO(QObjectPrivate::Connection, Q_RELOCATABLE_TYPE); + +struct QObjectPrivate::SignalVector : public ConnectionOrSignalVector +{ + quintptr allocated; + // ConnectionList signals[] + ConnectionList &at(int i) + { + return reinterpret_cast(this + 1)[i + 1]; + } + const ConnectionList &at(int i) const + { + return reinterpret_cast(this + 1)[i + 1]; + } + int count() const { return static_cast(allocated); } +}; +static_assert(std::is_trivial_v); // it doesn't need to be, but it helps + +struct QObjectPrivate::ConnectionData +{ + // 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 currentConnectionId; + QAtomicInt ref; + QAtomicPointer signalVector; + Connection *senders = nullptr; + Sender *currentSender = nullptr; // object currently activating the object + QAtomicPointer orphaned; + + ~ConnectionData() + { + Q_ASSERT(ref.loadRelaxed() == 0); + auto *c = orphaned.fetchAndStoreRelaxed(nullptr); + if (c) + deleteOrphaned(c); + SignalVector *v = signalVector.loadRelaxed(); + if (v) { + v->~SignalVector(); + free(v); + } + } + + // must be called on the senders connection data + // assumes the senders and receivers lock are held + void removeConnection(Connection *c); + enum LockPolicy { + NeedToLock, + // Beware that we need to temporarily release the lock + // and thus calling code must carefully consider whether + // invariants still hold. + AlreadyLockedAndTemporarilyReleasingLock + }; + void cleanOrphanedConnections(QObject *sender, LockPolicy lockPolicy = NeedToLock) + { + if (orphaned.loadRelaxed() && ref.loadAcquire() == 1) + cleanOrphanedConnectionsImpl(sender, lockPolicy); + } + void cleanOrphanedConnectionsImpl(QObject *sender, LockPolicy lockPolicy); + + ConnectionList &connectionsForSignal(int signal) + { + return signalVector.loadRelaxed()->at(signal); + } + + void resizeSignalVector(uint size) + { + SignalVector *vector = this->signalVector.loadRelaxed(); + if (vector && vector->allocated > size) + return; + size = (size + 7) & ~7; + void *ptr = malloc(sizeof(SignalVector) + (size + 1) * sizeof(ConnectionList)); + auto newVector = new (ptr) SignalVector; + + int start = -1; + if (vector) { + // not (yet) existing trait: + //static_assert(std::is_relocatable_v); + //static_assert(std::is_relocatable_v); + memcpy(newVector, vector, sizeof(SignalVector) + (vector->allocated + 1) * sizeof(ConnectionList)); + start = vector->count(); + } + for (int i = start; i < int(size); ++i) + new (&newVector->at(i)) ConnectionList(); + newVector->next = nullptr; + newVector->allocated = size; + + signalVector.storeRelaxed(newVector); + if (vector) { + 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(); + vector->nextInOrphanList = o; + } while (!orphaned.testAndSetRelease(o, ConnectionOrSignalVector::fromSignalVector(vector))); + + } + } + int signalVectorCount() const + { + return signalVector.loadAcquire() ? signalVector.loadRelaxed()->count() : -1; + } + + static void deleteOrphaned(ConnectionOrSignalVector *c); +}; + +struct QObjectPrivate::Sender +{ + Sender(QObject *receiver, QObject *sender, int signal) + : receiver(receiver), sender(sender), signal(signal) + { + if (receiver) { + ConnectionData *cd = receiver->d_func()->connections.loadRelaxed(); + previous = cd->currentSender; + cd->currentSender = this; + } + } + ~Sender() + { + if (receiver) + receiver->d_func()->connections.loadRelaxed()->currentSender = previous; + } + void receiverDeleted() + { + Sender *s = this; + while (s) { + s->receiver = nullptr; + s = s->previous; + } + } + Sender *previous; + QObject *receiver; + QObject *sender; + int signal; +}; +Q_DECLARE_TYPEINFO(QObjectPrivate::Sender, Q_RELOCATABLE_TYPE); + void qt_register_signal_spy_callbacks(QSignalSpyCallbackSet *callback_set) { qt_signal_spy_callback_set.storeRelease(callback_set); @@ -303,6 +518,15 @@ QObjectList QObjectPrivate::senderList() const return returnValue; } +void QObjectPrivate::ensureConnectionData() +{ + if (connections.loadRelaxed()) + return; + ConnectionData *cd = new ConnectionData; + cd->ref.ref(); + connections.storeRelaxed(cd); +} + /*! \internal Add the connection \a c to the list of connections of the sender's object diff --git a/src/corelib/kernel/qobject_p.h b/src/corelib/kernel/qobject_p.h index 4fcd2281e7d..ed32a904978 100644 --- a/src/corelib/kernel/qobject_p.h +++ b/src/corelib/kernel/qobject_p.h @@ -135,123 +135,11 @@ public: typedef void (*StaticMetaCallFunction)(QObject *, QMetaObject::Call, int, void **); struct Connection; + struct ConnectionData; + struct ConnectionList; + struct ConnectionOrSignalVector; struct SignalVector; - - struct ConnectionOrSignalVector { - union { - // linked list of orphaned connections that need cleaning up - ConnectionOrSignalVector *nextInOrphanList; - // linked list of connections connected to slots in this object - Connection *next; - }; - - static SignalVector *asSignalVector(ConnectionOrSignalVector *c) - { - if (reinterpret_cast(c) & 1) - return reinterpret_cast(reinterpret_cast(c) & ~quintptr(1u)); - return nullptr; - } - static Connection *fromSignalVector(SignalVector *v) { - return reinterpret_cast(reinterpret_cast(v) | quintptr(1u)); - } - }; - - struct Connection : public ConnectionOrSignalVector - { - // linked list of connections connected to slots in this object, next is in base class - Connection **prev; - // linked list of connections connected to signals in this object - QAtomicPointer nextConnectionList; - Connection *prevConnectionList; - - QObject *sender; - QAtomicPointer receiver; - QAtomicPointer receiverThreadData; - union { - StaticMetaCallFunction callFunction; - QtPrivate::QSlotObjectBase *slotObj; - }; - QAtomicPointer argumentTypes; - QAtomicInt ref_; - uint id = 0; - ushort method_offset; - ushort method_relative; - signed int signal_index : 27; // In signal range (see QObjectPrivate::signalIndex()) - ushort connectionType : 2; // 0 == auto, 1 == direct, 2 == queued, 3 == blocking - ushort isSlotObject : 1; - ushort ownArgumentTypes : 1; - ushort isSingleShot : 1; - Connection() : ref_(2), ownArgumentTypes(true) { - //ref_ is 2 for the use in the internal lists, and for the use in QMetaObject::Connection - } - ~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.loadRelaxed()); - Q_ASSERT(!isSlotObject); - delete this; - } - } - }; - // ConnectionList is a singly-linked list - struct ConnectionList { - QAtomicPointer first; - QAtomicPointer last; - }; - - struct Sender - { - Sender(QObject *receiver, QObject *sender, int signal) - : receiver(receiver), sender(sender), signal(signal) - { - if (receiver) { - ConnectionData *cd = receiver->d_func()->connections.loadRelaxed(); - previous = cd->currentSender; - cd->currentSender = this; - } - } - ~Sender() - { - if (receiver) - receiver->d_func()->connections.loadRelaxed()->currentSender = previous; - } - void receiverDeleted() - { - Sender *s = this; - while (s) { - s->receiver = nullptr; - s = s->previous; - } - } - Sender *previous; - QObject *receiver; - QObject *sender; - int signal; - }; - - struct SignalVector : public ConnectionOrSignalVector { - quintptr allocated; - // ConnectionList signals[] - ConnectionList &at(int i) - { - return reinterpret_cast(this + 1)[i + 1]; - } - const ConnectionList &at(int i) const - { - return reinterpret_cast(this + 1)[i + 1]; - } - int count() const { return static_cast(allocated); } - }; + struct Sender; /* This contains the all connections from and to an object. @@ -267,93 +155,6 @@ public: to a slot in this object. The mutex of the receiver must be locked when touching the pointers of this linked list. */ - struct ConnectionData { - // 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 currentConnectionId; - QAtomicInt ref; - QAtomicPointer signalVector; - Connection *senders = nullptr; - Sender *currentSender = nullptr; // object currently activating the object - QAtomicPointer orphaned; - - ~ConnectionData() - { - Q_ASSERT(ref.loadRelaxed() == 0); - auto *c = orphaned.fetchAndStoreRelaxed(nullptr); - if (c) - deleteOrphaned(c); - SignalVector *v = signalVector.loadRelaxed(); - if (v) { - v->~SignalVector(); - free(v); - } - } - - // must be called on the senders connection data - // assumes the senders and receivers lock are held - void removeConnection(Connection *c); - enum LockPolicy { - NeedToLock, - // Beware that we need to temporarily release the lock - // and thus calling code must carefully consider whether - // invariants still hold. - AlreadyLockedAndTemporarilyReleasingLock - }; - void cleanOrphanedConnections(QObject *sender, LockPolicy lockPolicy = NeedToLock) - { - if (orphaned.loadRelaxed() && ref.loadAcquire() == 1) - cleanOrphanedConnectionsImpl(sender, lockPolicy); - } - void cleanOrphanedConnectionsImpl(QObject *sender, LockPolicy lockPolicy); - - ConnectionList &connectionsForSignal(int signal) - { - return signalVector.loadRelaxed()->at(signal); - } - - void resizeSignalVector(uint size) - { - SignalVector *vector = this->signalVector.loadRelaxed(); - if (vector && vector->allocated > size) - return; - size = (size + 7) & ~7; - void *ptr = malloc(sizeof(SignalVector) + (size + 1) * sizeof(ConnectionList)); - auto newVector = new (ptr) SignalVector; - - int start = -1; - if (vector) { - // not (yet) existing trait: - //static_assert(std::is_relocatable_v); - //static_assert(std::is_relocatable_v); - memcpy(newVector, vector, sizeof(SignalVector) + (vector->allocated + 1) * sizeof(ConnectionList)); - start = vector->count(); - } - for (int i = start; i < int(size); ++i) - new (&newVector->at(i)) ConnectionList(); - newVector->next = nullptr; - newVector->allocated = size; - - signalVector.storeRelaxed(newVector); - if (vector) { - 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(); - vector->nextInOrphanList = o; - } while (!orphaned.testAndSetRelease(o, ConnectionOrSignalVector::fromSignalVector(vector))); - - } - } - int signalVectorCount() const - { - return signalVector.loadAcquire() ? signalVector.loadRelaxed()->count() : -1; - } - - static void deleteOrphaned(ConnectionOrSignalVector *c); - }; QObjectPrivate(int version = QObjectPrivateVersion); virtual ~QObjectPrivate(); @@ -412,14 +213,7 @@ public: void **slot); static bool disconnect(Connection *c); - void ensureConnectionData() - { - if (connections.loadRelaxed()) - return; - ConnectionData *cd = new ConnectionData; - cd->ref.ref(); - connections.storeRelaxed(cd); - } + void ensureConnectionData(); virtual std::string flagsForDumping() const; @@ -445,8 +239,6 @@ public: QAtomicPointer sharedRefcount; }; -Q_DECLARE_TYPEINFO(QObjectPrivate::ConnectionList, Q_RELOCATABLE_TYPE); - /* Catch mixing of incompatible library versions. @@ -587,9 +379,6 @@ bool QObjectPrivate::disconnect(const typename QtPrivate::FunctionPointer< Func1 &SignalType::Object::staticMetaObject); } -Q_DECLARE_TYPEINFO(QObjectPrivate::Connection, Q_RELOCATABLE_TYPE); -Q_DECLARE_TYPEINFO(QObjectPrivate::Sender, Q_RELOCATABLE_TYPE); - class QSemaphore; class Q_CORE_EXPORT QAbstractMetaCallEvent : public QEvent {