aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside6/libpyside/dynamicslot.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'sources/pyside6/libpyside/dynamicslot.cpp')
-rw-r--r--sources/pyside6/libpyside/dynamicslot.cpp241
1 files changed, 212 insertions, 29 deletions
diff --git a/sources/pyside6/libpyside/dynamicslot.cpp b/sources/pyside6/libpyside/dynamicslot.cpp
index 182c2949f..b25928ea9 100644
--- a/sources/pyside6/libpyside/dynamicslot.cpp
+++ b/sources/pyside6/libpyside/dynamicslot.cpp
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "dynamicslot_p.h"
-#include "globalreceiverv2.h" // for GlobalReceiverMethodSlot
#include "pysidestaticstrings.h"
#include "pysideutils.h"
#include "pysideweakref.h"
@@ -13,10 +12,16 @@
#include <pep384ext.h>
#include <QtCore/QDebug>
+#include <QtCore/QtCompare>
+#include <QtCore/QCoreApplication>
+#include <QtCore/QHash>
+#include <QtCore/QPointer>
namespace PySide
{
+static void disconnectReceiver(PyObject *pythonSelf);
+
DynamicSlot::SlotType DynamicSlot::slotType(PyObject *callback)
{
if (PyMethod_Check(callback) != 0)
@@ -78,6 +83,8 @@ public:
explicit MethodDynamicSlot(PyObject *function, PyObject *pythonSelf);
~MethodDynamicSlot() override;
+ PyObject *pythonSelf() const { return m_pythonSelf; }
+
void call(const QByteArrayList &parameterTypes, const char *returnType,
void **cppArgs) override;
void formatDebug(QDebug &debug) const override;
@@ -139,45 +146,43 @@ TrackingMethodDynamicSlot::TrackingMethodDynamicSlot(PyObject *function, PyObjec
TrackingMethodDynamicSlot::~TrackingMethodDynamicSlot()
{
- Shiboken::GilState gil;
- Py_XDECREF(m_weakRef);
- m_weakRef = nullptr;
+ if (m_weakRef != nullptr) {
+ Shiboken::GilState gil;
+ // weakrefs must not be de-refed after the object has been deleted,
+ // else they get negative refcounts.
+ if (PyWeakref_GetObject(m_weakRef) != Py_None)
+ Py_DECREF(m_weakRef);
+ }
}
-// Delete the GlobalReceiver on pythonSelf deletion
-class GlobalReceiverMethodSlot : public TrackingMethodDynamicSlot
+// Delete the connection on receiver deletion by weakref
+class PysideReceiverMethodSlot : public TrackingMethodDynamicSlot
{
- Q_DISABLE_COPY_MOVE(GlobalReceiverMethodSlot)
+ Q_DISABLE_COPY_MOVE(PysideReceiverMethodSlot)
public:
- explicit GlobalReceiverMethodSlot(PyObject *function, PyObject *pythonSelf,
- GlobalReceiverV2 *parent);
- ~GlobalReceiverMethodSlot() override = default;
-
- GlobalReceiverV2 *parent() const { return m_parent; }
+ explicit PysideReceiverMethodSlot(PyObject *function, PyObject *pythonSelf);
-private:
- GlobalReceiverV2 *m_parent;
+ ~PysideReceiverMethodSlot() override = default;
};
-static void onGlobalReceiverSlotDestroyed(void *data)
+static void onPysideReceiverSlotDestroyed(void *data)
{
- auto *self = reinterpret_cast<GlobalReceiverMethodSlot *>(data);
+ auto *self = reinterpret_cast<PysideReceiverMethodSlot *>(data);
+ // Ensure the weakref is gone in case the connection stored in
+ // Qt's internals outlives Python.
self->releaseWeakRef();
Py_BEGIN_ALLOW_THREADS
- SignalManager::deleteGlobalReceiver(self->parent());
+ disconnectReceiver(self->pythonSelf());
Py_END_ALLOW_THREADS
}
-// monitor class from method lifetime
-GlobalReceiverMethodSlot::GlobalReceiverMethodSlot(PyObject *function, PyObject *pythonSelf,
- GlobalReceiverV2 *parent) :
+PysideReceiverMethodSlot::PysideReceiverMethodSlot(PyObject *function, PyObject *pythonSelf) :
TrackingMethodDynamicSlot(function, pythonSelf,
- WeakRef::create(pythonSelf, onGlobalReceiverSlotDestroyed, this)),
- m_parent(parent)
+ WeakRef::create(pythonSelf, onPysideReceiverSlotDestroyed, this))
{
}
-DynamicSlot* DynamicSlot::create(PyObject *callback, GlobalReceiverV2 *parent)
+DynamicSlot* DynamicSlot::create(PyObject *callback)
{
Shiboken::GilState gil;
switch (slotType(callback)) {
@@ -185,9 +190,7 @@ DynamicSlot* DynamicSlot::create(PyObject *callback, GlobalReceiverV2 *parent)
PyObject *function = PyMethod_GET_FUNCTION(callback);
Py_INCREF(function);
PyObject *pythonSelf = PyMethod_GET_SELF(callback);
- if (parent != nullptr)
- return new GlobalReceiverMethodSlot(function, pythonSelf, parent);
- return new MethodDynamicSlot(function, pythonSelf);
+ return new PysideReceiverMethodSlot(function, pythonSelf);
}
case SlotType::CompiledMethod: {
// PYSIDE-1523: PyMethod_Check is not accepting compiled form, we just go by attributes.
@@ -195,9 +198,7 @@ DynamicSlot* DynamicSlot::create(PyObject *callback, GlobalReceiverV2 *parent)
Py_DECREF(function);
PyObject *pythonSelf = PyObject_GetAttr(callback, PySide::PySideName::im_self());
Py_DECREF(pythonSelf);
- if (parent != nullptr)
- return new GlobalReceiverMethodSlot(function, pythonSelf, parent);
- return new MethodDynamicSlot(function, pythonSelf);
+ return new PysideReceiverMethodSlot(function, pythonSelf);
}
case SlotType::Callable:
break;
@@ -217,4 +218,186 @@ QDebug operator<<(QDebug debug, const DynamicSlot *ds)
return debug;
}
+// Connection code for signal connections that use a Python callable not
+// targeting the QMetaMethod of a QObject (no index). They require
+// invocation in a class inheriting QtPrivate::QSlotObjectBase (PySideQSlotObject
+// aggregating DynamicSlot), which is passed to:
+// QObjectPrivate::connect(const QObject *, int signal, QtPrivate::QSlotObjectBase *, ...).
+// For each of those connections (identified by ConnectionKey), we maintain a
+// hash of ConnectionKey->QMetaObject::Connection for:
+//
+// - Disconnecting: Retrieve QMetaObject::Connection for the connection parameters
+//
+// - Tracking sender (QObject) life time and clean hash on destroyed()
+//
+// - Tracking receiver (PyObject*) life time via weakref in case of a
+// connection to a method and proactively disconnect on weakref
+// notification as not to cause leaks of the instance.
+
+struct ConnectionKey
+{
+ const QObject *sender;
+ int senderIndex;
+ const PyObject *object;
+ const PyObject *method;
+
+ friend constexpr size_t qHash(const ConnectionKey &k, size_t seed = 0) noexcept
+ {
+ return qHashMulti(seed, k.sender, k.senderIndex, k.object, k.method);
+ }
+
+ friend constexpr bool comparesEqual(const ConnectionKey &lhs,
+ const ConnectionKey &rhs) noexcept
+ {
+ return lhs.sender == rhs.sender && lhs.senderIndex == rhs.senderIndex
+ && lhs.object == rhs.object && lhs.method == rhs.method;
+ }
+
+ Q_DECLARE_EQUALITY_COMPARABLE(ConnectionKey);
+};
+
+QDebug operator<<(QDebug debug, const ConnectionKey &k)
+{
+ QDebugStateSaver saver(debug);
+ debug.noquote();
+ debug.nospace();
+ debug << "ConnectionKey(" << static_cast<const void *>(k.sender)
+ << '/' << k.sender->metaObject()->className();
+ auto on = k.sender->objectName();
+ if (!on.isEmpty())
+ debug << "/\"" << on << '"';
+ debug << ", index=" << k.senderIndex << ", target="
+ << PySide::debugPyObject(const_cast<PyObject *>(k.object));
+ if (k.method != nullptr)
+ debug << ", method=" << PySide::debugPyObject(const_cast<PyObject *>(k.method));
+ debug << ')';
+ return debug;
+}
+
+QDebug operator<<(QDebug debug, const QMetaObject::Connection &c)
+{
+ QDebugStateSaver saver(debug);
+ debug.noquote();
+ debug.nospace();
+ debug << "Connection(";
+ if (c)
+ debug << static_cast<const void *>(&c); // d-ptr;
+ else
+ debug << '0';
+ debug << ')';
+ return debug;
+}
+
+using ConnectionHash = QHash<ConnectionKey, QMetaObject::Connection>;
+
+static ConnectionHash connectionHash;
+
+static ConnectionKey connectionKey(const QObject *sender, int senderIndex,
+ PyObject *callback)
+{
+ PyObject *object{};
+ PyObject *method{};
+
+ switch (DynamicSlot::slotType(callback)) {
+ case DynamicSlot::SlotType::Method:
+ // PYSIDE-1422: Avoid hash on self which might be unhashable.
+ object = PyMethod_GET_SELF(callback);
+ method = PyMethod_GET_FUNCTION(callback);
+ break;
+ case DynamicSlot::SlotType::CompiledMethod: {
+ // PYSIDE-1589: Fix for slots in compiled functions
+ Shiboken::AutoDecRef self(PyObject_GetAttr(callback, PySide::PySideName::im_self()));
+ Shiboken::AutoDecRef func(PyObject_GetAttr(callback, PySide::PySideName::im_func()));
+ object = self.object();
+ method = func.object();
+ break;
+ }
+ case DynamicSlot::SlotType::Callable:
+ method = callback;
+ break;
+ }
+
+ return {sender, senderIndex, object, method};
+}
+
+// Listens to QObject::destroyed of senders and removes them from the hash.
+class SenderSignalDeletionTracker : public QObject
+{
+ Q_OBJECT
+public:
+ using QObject::QObject;
+
+public Q_SLOTS:
+ void senderDestroyed(QObject *o);
+};
+
+void SenderSignalDeletionTracker::senderDestroyed(QObject *o)
+{
+ for (auto it = connectionHash.begin(); it != connectionHash.end(); ) {
+ if (it.key().sender == o)
+ it = connectionHash.erase(it);
+ else
+ ++it;
+ }
+}
+
+static QPointer<SenderSignalDeletionTracker> senderSignalDeletionTracker;
+
+static void disconnectReceiver(PyObject *pythonSelf)
+{
+ // A check for reentrancy was added for PYSIDE-88, but has not been
+ // observed yet.
+ for (bool keepGoing = true; keepGoing; ) {
+ keepGoing = false;
+ for (auto it = connectionHash.begin(); it != connectionHash.end(); ) {
+ if (it.key().object == pythonSelf) {
+ const auto oldSize = connectionHash.size();
+ QObject::disconnect(it.value());
+ it = connectionHash.erase(it);
+ // Check for a disconnection causing deletion of further objects
+ // by a re-entrant call.
+ if (connectionHash.size() < oldSize - 1) {
+ keepGoing = true;
+ break; // Iterators were invalidated, retry
+ }
+ } else {
+ ++it;
+ }
+ }
+ }
+}
+
+static void clearConnectionHash()
+{
+ connectionHash.clear();
+}
+
+void registerSlotConnection(QObject *source, int signalIndex, PyObject *callback,
+ const QMetaObject::Connection &connection)
+{
+ connectionHash.insert(connectionKey(source, signalIndex, callback), connection);
+ if (senderSignalDeletionTracker.isNull()) {
+ auto *app = QCoreApplication::instance();
+ senderSignalDeletionTracker = new SenderSignalDeletionTracker(app);
+ Py_AtExit(clearConnectionHash);
+ }
+
+ QObject::connect(source, &QObject::destroyed,
+ senderSignalDeletionTracker, &SenderSignalDeletionTracker::senderDestroyed,
+ Qt::UniqueConnection);
+}
+
+bool disconnectSlot(QObject *source, int signalIndex, PyObject *callback)
+{
+ auto it = connectionHash.find(connectionKey(source, signalIndex, callback));
+ const bool ok = it != connectionHash.end();
+ if (ok) {
+ QObject::disconnect(it.value());
+ connectionHash.erase(it);
+ }
+ return ok;
+}
+
} // namespace PySide
+
+#include "dynamicslot.moc"