Why is QGraphicsObject getting deleted in QCoreApplication event loop?
-
I have a QGraphicsObject that is getting deleted when I let the QApplication's event loop run for a little bit. I can reproduce this in a unit test. This is a PyQt5 app. The QGraphicsObject is not being removed from the QGraphicsScene, but a delete event is obviously getting
posted
for deferred deletion. There is about a ~500ms delay between calling QMessageBox::information (to let the event loop run a little) and the actual deletion. Here is C++ backtrace on macOS:I am emitting a signal from my
PathItemBase
(myQGraphicsObject
subclass) so that I can set a python breakpoint on the deletion. TheQObject::destroyed
has not been emitted yet.C++ dtor:
PathItemBase::~PathItemBase() { print_stack_trace(); qDebug() << "~PathItemBase() objectName: " << this->objectName() << " className: " << this->metaObject()->className() << " parentItem: " << this->parentItem() << " parent: " << this->parent(); emit deleting(this); }
Python
deleting
slot:def _deleting(o): nonlocal self _log.info( f">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ABOUT TO BE DESTROYED " f"objectName: {o.objectName()}, className: {o.metaObject().className()}" ) x = 1 self.deleting.connect(_deleting)
Which prints:
C++ Stack trace: 0 _pkdiagram.cpython-310-darwin.so 0x00000001090b6516 _Z17print_stack_tracev + 54 1 _pkdiagram.cpython-310-darwin.so 0x00000001090bb0ef _ZN12PathItemBaseD2Ev + 47 2 _pkdiagram.cpython-310-darwin.so 0x00000001090af23d _ZN15sipPathItemBaseD0Ev + 61 3 QtCore 0x000000028a93088e _ZN7QObject5eventEP6QEvent + 158 4 _pkdiagram.cpython-310-darwin.so 0x00000001090af7d1 _ZN15sipPathItemBase5eventEP6QEvent + 113 5 QtWidgets 0x000000029671e9ea _ZN19QApplicationPrivate13notify_helperEP7QObjectP6QEvent + 266 6 QtWidgets 0x000000029671fe11 _ZN12QApplication6notifyEP7QObjectP6QEvent + 497 7 QtWidgets.abi3.so 0x0000000296e89e06 _ZN15sipQApplication6notifyEP7QObjectP6QEvent + 230 8 QtCore 0x000000028a905a34 _ZN16QCoreApplication15notifyInternal2EP7QObjectP6QEvent + 212 9 QtCore 0x000000028a906d79 _ZN23QCoreApplicationPrivate16sendPostedEventsEP7QObjectiP11QThreadData + 809 10 QtCore 0x000000028a969719 _ZN20QEventDispatcherUNIX13processEventsE6QFlagsIN10QEventLoop17ProcessEventsFlagEE + 73 11 libqoffscreen.dylib 0x000000029983f74e _ZN23QUnixEventDispatcherQPA13processEventsE6QFlagsIN10QEventLoop17ProcessEventsFlagEE + 14 12 QtCore 0x000000028a901acf _ZN10QEventLoop4execE6QFlagsINS_17ProcessEventsFlagEE + 431 13 QtWidgets 0x000000029691ee9e _ZN7QDialog4execEv + 526 14 QtWidgets 0x000000029695ca72 _ZL17showNewMessageBoxP7QWidgetN11QMessageBox4IconERK7QStringS5_6QFlagsINS1_14StandardButtonEES7_ + 354 15 QtWidgets.abi3.so 0x0000000296ef12f7 _ZL28meth_QMessageBox_informationP7_objectS0_S0_ + 231 16 python3.10 0x0000000106ca6045 cfunction_call + 69 17 python3.10 0x0000000106c5f218 _PyObject_MakeTpCall + 376 18 python3.10 0x0000000106d5228e call_function + 974 19 python3.10 0x0000000106d4e978 _PyEval_EvalFrameDefault + 28760 20 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 21 python3.10 0x0000000106c5f5eb PyVectorcall_Call + 155 22 python3.10 0x0000000106d4ee65 _PyEval_EvalFrameDefault + 30021 23 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 24 python3.10 0x0000000106d4ee65 _PyEval_EvalFrameDefault + 30021 25 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 26 python3.10 0x0000000106d5218a call_function + 714 27 python3.10 0x0000000106d4e978 _PyEval_EvalFrameDefault + 28760 28 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 29 python3.10 0x0000000106c61aed method_vectorcall + 205 30 python3.10 0x0000000106d5218a call_function + 714 31 python3.10 0x0000000106d4e978 _PyEval_EvalFrameDefault + 28760 32 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 33 python3.10 0x0000000106c5efee _PyObject_FastCallDictTstate + 174 34 python3.10 0x0000000106c5fa9b _PyObject_Call_Prepend + 139 35 python3.10 0x0000000106cc5e5c slot_tp_call + 204 36 python3.10 0x0000000106c5f218 _PyObject_MakeTpCall + 376 37 python3.10 0x0000000106d5228e call_function + 974 38 python3.10 0x0000000106d4ea29 _PyEval_EvalFrameDefault + 28937 39 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 40 python3.10 0x0000000106d5218a call_function + 714 41 python3.10 0x0000000106d4e94c _PyEval_EvalFrameDefault + 28716 42 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 43 python3.10 0x0000000106d4ee65 _PyEval_EvalFrameDefault + 30021 44 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 45 python3.10 0x0000000106d5218a call_function + 714 46 python3.10 0x0000000106d4e978 _PyEval_EvalFrameDefault + 28760 47 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 48 python3.10 0x0000000106c61aed method_vectorcall + 205 49 python3.10 0x0000000106d5218a call_function + 714 50 python3.10 0x0000000106d4e978 _PyEval_EvalFrameDefault + 28760 51 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 52 python3.10 0x0000000106c5efee _PyObject_FastCallDictTstate + 174 53 python3.10 0x0000000106c5fa9b _PyObject_Call_Prepend + 139 54 python3.10 0x0000000106cc5e5c slot_tp_call + 204 55 python3.10 0x0000000106c5f73d _PyObject_Call + 141 56 python3.10 0x0000000106d4ee65 _PyEval_EvalFrameDefault + 30021 57 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 58 python3.10 0x0000000106d5218a call_function + 714 59 python3.10 0x0000000106d47f5b _PyEval_EvalFrameDefault + 1595 60 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 61 python3.10 0x0000000106c61aed method_vectorcall + 205 62 python3.10 0x0000000106d5218a call_function + 714 63 python3.10 0x0000000106d4ea29 _PyEval_EvalFrameDefault + 28937 64 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 65 python3.10 0x0000000106d4ee65 _PyEval_EvalFrameDefault + 30021 66 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 67 python3.10 0x0000000106d5218a call_function + 714 68 python3.10 0x0000000106d47f5b _PyEval_EvalFrameDefault + 1595 69 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 70 python3.10 0x0000000106d5218a call_function + 714 71 python3.10 0x0000000106d4ea29 _PyEval_EvalFrameDefault + 28937 72 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 73 python3.10 0x0000000106d4ee65 _PyEval_EvalFrameDefault + 30021 74 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 75 python3.10 0x0000000106d5218a call_function + 714 76 python3.10 0x0000000106d4e978 _PyEval_EvalFrameDefault + 28760 77 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 78 python3.10 0x0000000106c61aed method_vectorcall + 205 79 python3.10 0x0000000106d5218a call_function + 714 80 python3.10 0x0000000106d4e978 _PyEval_EvalFrameDefault + 28760 81 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 82 python3.10 0x0000000106c5efee _PyObject_FastCallDictTstate + 174 83 python3.10 0x0000000106c5fa9b _PyObject_Call_Prepend + 139 84 python3.10 0x0000000106cc5e5c slot_tp_call + 204 85 python3.10 0x0000000106c5f218 _PyObject_MakeTpCall + 376 86 python3.10 0x0000000106d5228e call_function + 974 87 python3.10 0x0000000106d4ea29 _PyEval_EvalFrameDefault + 28937 88 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 89 python3.10 0x0000000106d4ee65 _PyEval_EvalFrameDefault + 30021 90 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 91 python3.10 0x0000000106d5218a call_function + 714 92 python3.10 0x0000000106d4e978 _PyEval_EvalFrameDefault + 28760 93 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 94 python3.10 0x0000000106c61aed method_vectorcall + 205 95 python3.10 0x0000000106d5218a call_function + 714 96 python3.10 0x0000000106d4e978 _PyEval_EvalFrameDefault + 28760 97 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 98 python3.10 0x0000000106c5efee _PyObject_FastCallDictTstate + 174 99 python3.10 0x0000000106c5fa9b _PyObject_Call_Prepend + 139 2024-08-11 11:11:52,145 INFO :0 ~PathItemBase() objectName: "Marriage" className: PathItemBase parentItem: QGraphicsItem(0) parent: QObject(0x0) 2024-08-11 11:11:52,146 INFO pathitem.py:104 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ABOUT TO BE DESTROYED objectName: Marriage, className: PathItemBase
Both
QObject::parent()
andQGraphicsItem::parentItem()
return0x0
, so it shouldn't be aQObject
parent-child hierarchy deletion. What else could post this deletion event? -
I have a QGraphicsObject that is getting deleted when I let the QApplication's event loop run for a little bit. I can reproduce this in a unit test. This is a PyQt5 app. The QGraphicsObject is not being removed from the QGraphicsScene, but a delete event is obviously getting
posted
for deferred deletion. There is about a ~500ms delay between calling QMessageBox::information (to let the event loop run a little) and the actual deletion. Here is C++ backtrace on macOS:I am emitting a signal from my
PathItemBase
(myQGraphicsObject
subclass) so that I can set a python breakpoint on the deletion. TheQObject::destroyed
has not been emitted yet.C++ dtor:
PathItemBase::~PathItemBase() { print_stack_trace(); qDebug() << "~PathItemBase() objectName: " << this->objectName() << " className: " << this->metaObject()->className() << " parentItem: " << this->parentItem() << " parent: " << this->parent(); emit deleting(this); }
Python
deleting
slot:def _deleting(o): nonlocal self _log.info( f">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ABOUT TO BE DESTROYED " f"objectName: {o.objectName()}, className: {o.metaObject().className()}" ) x = 1 self.deleting.connect(_deleting)
Which prints:
C++ Stack trace: 0 _pkdiagram.cpython-310-darwin.so 0x00000001090b6516 _Z17print_stack_tracev + 54 1 _pkdiagram.cpython-310-darwin.so 0x00000001090bb0ef _ZN12PathItemBaseD2Ev + 47 2 _pkdiagram.cpython-310-darwin.so 0x00000001090af23d _ZN15sipPathItemBaseD0Ev + 61 3 QtCore 0x000000028a93088e _ZN7QObject5eventEP6QEvent + 158 4 _pkdiagram.cpython-310-darwin.so 0x00000001090af7d1 _ZN15sipPathItemBase5eventEP6QEvent + 113 5 QtWidgets 0x000000029671e9ea _ZN19QApplicationPrivate13notify_helperEP7QObjectP6QEvent + 266 6 QtWidgets 0x000000029671fe11 _ZN12QApplication6notifyEP7QObjectP6QEvent + 497 7 QtWidgets.abi3.so 0x0000000296e89e06 _ZN15sipQApplication6notifyEP7QObjectP6QEvent + 230 8 QtCore 0x000000028a905a34 _ZN16QCoreApplication15notifyInternal2EP7QObjectP6QEvent + 212 9 QtCore 0x000000028a906d79 _ZN23QCoreApplicationPrivate16sendPostedEventsEP7QObjectiP11QThreadData + 809 10 QtCore 0x000000028a969719 _ZN20QEventDispatcherUNIX13processEventsE6QFlagsIN10QEventLoop17ProcessEventsFlagEE + 73 11 libqoffscreen.dylib 0x000000029983f74e _ZN23QUnixEventDispatcherQPA13processEventsE6QFlagsIN10QEventLoop17ProcessEventsFlagEE + 14 12 QtCore 0x000000028a901acf _ZN10QEventLoop4execE6QFlagsINS_17ProcessEventsFlagEE + 431 13 QtWidgets 0x000000029691ee9e _ZN7QDialog4execEv + 526 14 QtWidgets 0x000000029695ca72 _ZL17showNewMessageBoxP7QWidgetN11QMessageBox4IconERK7QStringS5_6QFlagsINS1_14StandardButtonEES7_ + 354 15 QtWidgets.abi3.so 0x0000000296ef12f7 _ZL28meth_QMessageBox_informationP7_objectS0_S0_ + 231 16 python3.10 0x0000000106ca6045 cfunction_call + 69 17 python3.10 0x0000000106c5f218 _PyObject_MakeTpCall + 376 18 python3.10 0x0000000106d5228e call_function + 974 19 python3.10 0x0000000106d4e978 _PyEval_EvalFrameDefault + 28760 20 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 21 python3.10 0x0000000106c5f5eb PyVectorcall_Call + 155 22 python3.10 0x0000000106d4ee65 _PyEval_EvalFrameDefault + 30021 23 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 24 python3.10 0x0000000106d4ee65 _PyEval_EvalFrameDefault + 30021 25 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 26 python3.10 0x0000000106d5218a call_function + 714 27 python3.10 0x0000000106d4e978 _PyEval_EvalFrameDefault + 28760 28 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 29 python3.10 0x0000000106c61aed method_vectorcall + 205 30 python3.10 0x0000000106d5218a call_function + 714 31 python3.10 0x0000000106d4e978 _PyEval_EvalFrameDefault + 28760 32 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 33 python3.10 0x0000000106c5efee _PyObject_FastCallDictTstate + 174 34 python3.10 0x0000000106c5fa9b _PyObject_Call_Prepend + 139 35 python3.10 0x0000000106cc5e5c slot_tp_call + 204 36 python3.10 0x0000000106c5f218 _PyObject_MakeTpCall + 376 37 python3.10 0x0000000106d5228e call_function + 974 38 python3.10 0x0000000106d4ea29 _PyEval_EvalFrameDefault + 28937 39 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 40 python3.10 0x0000000106d5218a call_function + 714 41 python3.10 0x0000000106d4e94c _PyEval_EvalFrameDefault + 28716 42 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 43 python3.10 0x0000000106d4ee65 _PyEval_EvalFrameDefault + 30021 44 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 45 python3.10 0x0000000106d5218a call_function + 714 46 python3.10 0x0000000106d4e978 _PyEval_EvalFrameDefault + 28760 47 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 48 python3.10 0x0000000106c61aed method_vectorcall + 205 49 python3.10 0x0000000106d5218a call_function + 714 50 python3.10 0x0000000106d4e978 _PyEval_EvalFrameDefault + 28760 51 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 52 python3.10 0x0000000106c5efee _PyObject_FastCallDictTstate + 174 53 python3.10 0x0000000106c5fa9b _PyObject_Call_Prepend + 139 54 python3.10 0x0000000106cc5e5c slot_tp_call + 204 55 python3.10 0x0000000106c5f73d _PyObject_Call + 141 56 python3.10 0x0000000106d4ee65 _PyEval_EvalFrameDefault + 30021 57 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 58 python3.10 0x0000000106d5218a call_function + 714 59 python3.10 0x0000000106d47f5b _PyEval_EvalFrameDefault + 1595 60 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 61 python3.10 0x0000000106c61aed method_vectorcall + 205 62 python3.10 0x0000000106d5218a call_function + 714 63 python3.10 0x0000000106d4ea29 _PyEval_EvalFrameDefault + 28937 64 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 65 python3.10 0x0000000106d4ee65 _PyEval_EvalFrameDefault + 30021 66 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 67 python3.10 0x0000000106d5218a call_function + 714 68 python3.10 0x0000000106d47f5b _PyEval_EvalFrameDefault + 1595 69 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 70 python3.10 0x0000000106d5218a call_function + 714 71 python3.10 0x0000000106d4ea29 _PyEval_EvalFrameDefault + 28937 72 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 73 python3.10 0x0000000106d4ee65 _PyEval_EvalFrameDefault + 30021 74 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 75 python3.10 0x0000000106d5218a call_function + 714 76 python3.10 0x0000000106d4e978 _PyEval_EvalFrameDefault + 28760 77 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 78 python3.10 0x0000000106c61aed method_vectorcall + 205 79 python3.10 0x0000000106d5218a call_function + 714 80 python3.10 0x0000000106d4e978 _PyEval_EvalFrameDefault + 28760 81 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 82 python3.10 0x0000000106c5efee _PyObject_FastCallDictTstate + 174 83 python3.10 0x0000000106c5fa9b _PyObject_Call_Prepend + 139 84 python3.10 0x0000000106cc5e5c slot_tp_call + 204 85 python3.10 0x0000000106c5f218 _PyObject_MakeTpCall + 376 86 python3.10 0x0000000106d5228e call_function + 974 87 python3.10 0x0000000106d4ea29 _PyEval_EvalFrameDefault + 28937 88 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 89 python3.10 0x0000000106d4ee65 _PyEval_EvalFrameDefault + 30021 90 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 91 python3.10 0x0000000106d5218a call_function + 714 92 python3.10 0x0000000106d4e978 _PyEval_EvalFrameDefault + 28760 93 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 94 python3.10 0x0000000106c61aed method_vectorcall + 205 95 python3.10 0x0000000106d5218a call_function + 714 96 python3.10 0x0000000106d4e978 _PyEval_EvalFrameDefault + 28760 97 python3.10 0x0000000106d477f2 _PyEval_Vector + 146 98 python3.10 0x0000000106c5efee _PyObject_FastCallDictTstate + 174 99 python3.10 0x0000000106c5fa9b _PyObject_Call_Prepend + 139 2024-08-11 11:11:52,145 INFO :0 ~PathItemBase() objectName: "Marriage" className: PathItemBase parentItem: QGraphicsItem(0) parent: QObject(0x0) 2024-08-11 11:11:52,146 INFO pathitem.py:104 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ABOUT TO BE DESTROYED objectName: Marriage, className: PathItemBase
Both
QObject::parent()
andQGraphicsItem::parentItem()
return0x0
, so it shouldn't be aQObject
parent-child hierarchy deletion. What else could post this deletion event?@patrickkidd said in Why is QGraphicsObject getting deleted in QCoreApplication event loop?:
I can reproduce this in a unit test.
Then you should post the unit test so we can see exactly what you are doing.
-
OK, but it's a fairly mature and complex app and there is a lot going on here. I bet this problem is best taken hypothetically, like when could a QGraphicsItem possibly get a deferred delete call if it isn't getting explicitly removed from the scene or anything.
But here is the unit/integration test:
-
OK, but it's a fairly mature and complex app and there is a lot going on here. I bet this problem is best taken hypothetically, like when could a QGraphicsItem possibly get a deferred delete call if it isn't getting explicitly removed from the scene or anything.
But here is the unit/integration test:
You are right, the test is not much use. It is a not a small, self-contained demonstration of the behaviour.
The QGraphicsObject will be destroyed when the QGraphicsScene containing it is destroyed. This is using the QGraphicsItem hierarchy, not the QObject hierarchy.
In a Python environment, using QGraphicsScene::removeItem() will probably result in removed items being garbage collected at a later point in time if you are not holding other references. QGraphicsScene::clear() will delete all items in the scene.
-
Yeah, I still have references in python, and in fact I first discovered this problem because PyQt was complaining about the underlying C++ object being deleted from a still valid python reference.
The QGraphicsScene isn't being destroyed so far as I can tell. The
QGraphicsScene.destroyed
signal never gets emitted andremoveItem
is never called. I've been using Qt for 25 years and this is such a strange bug. How in the world could the QGraphicsItem be getting deleted, and not only that but in a deferred way??I can debug the test with lldb and then set a breakpoint in
QObject::deleteLater()
(I am using the pip-installed PyQt5 bins on macOS and they accurately reflect the C++ object attrs) but I can't figure out how to set a lldb breakpoint condition for when the classname is"PathItemBase"
, or at least notQQml*
. -
I wonder, could this possibly have something to do with the QGraphicsObject getting deleted being garbage collected from a reference in the Qml JS engine?
-
I need more time testing, but it looks like setting object ownership to C++ when a Object is returned from a pyqt slot like this:
@pyqtSlot(int, result=QObject) def item(self, id): ret = self.scene.findById(id) QQmlEngine.setObjectOwnership(ret, QQmlEngine.CppOwnership) return ret
fixes it. I can't figure out where to find this in the docs, but a fresh ChatGPT 4 thread says that default ownership by objects returned to calls from QMl/javascript is to javascript and not python/C++.
Man, that was a tough and strange one. I would really love to find a place in the Qt JS engine where the deleteLater is called.