Hard crash when updating QTreeWidget items while handling changed event
-
I'm writing an application in python that used PySide6/Qt for a gui. Sometimes the application hard-crashes as result of user actions. This is hard to debug as the error messages do not contain any info as on where the error occured.
See for example the following:
The following example causes a
exit code -1073740791 (0xC0000409)
when one of the boxes is checked the second time:from PySide6.QtCore import Qt from PySide6.QtWidgets import QApplication, QWidget, QVBoxLayout, QTreeWidget, \ QTreeWidgetItem app = QApplication([]) widget = QWidget() layout = QVBoxLayout(widget) treewidget = QTreeWidget() layout.addWidget(treewidget) def add_items(): for i in range(5): item1 = QTreeWidgetItem() item1.setText(0, f'Check to crash{i}') item1.setCheckState(0, Qt.CheckState.Unchecked) treewidget.invisibleRootItem().addChild(item1) treewidget.itemChanged.connect(on_item_changed) def on_item_changed(item): print('clearing') treewidget.clear() add_items() add_items() widget.show() app.exec()
Uinsg windows-11 with PySide6-Essentials 6.6.1 (pip)
also crashes with 6.6.2How can I debug errors like this?
-
@VirtualFloat
You could try running under Python debugger to see if that tells you anything. You could try running the actualpython
process under gdb (or whatever your system/C++ debugger) and examining the stack trace when the crash occurs: it is true that will only give information about the python process itself, but sometimes there are clues there.You are calling
treewidget.itemChanged.connect(on_item_changed)
multiple times, for eachQTreeWidgetItem
you add. This is not right. You have only onetreewidget
, you should only connect the signal once. Try changing that. Your slot clears the tree widget out of its items and adds new ones, but you call this in the middle of an existing item being changed. That may not be good, especially since you are calling it multiple times, so by the time it fires a second time the original item being changed has already been cleared (and new ones added) from the tree. -
@JonB , thanks for the reply.
the script is buggy on purpose, but you are right about connecting the signal multiple times, that was not needed so I moved that out of the loop.The add_items part is now as follows:
for i in range(5): item1 = QTreeWidgetItem() item1.setText(0, f'Check to crash{i}') item1.setCheckState(0, Qt.CheckState.Unchecked) treewidget.invisibleRootItem().addChild(item1) treewidget.itemChanged.connect(on_item_changed)
which gives me the following stack trace:
# Child-SP RetAddr Call Site 00 (Inline Function) --------`-------- VCRUNTIME140!GetImageBaseFromCompleteObjectLocator+0x3 [D:\a\_work\1\s\src\vctools\crt\vcruntime\src\eh\rtti.cpp @ 44] 01 00000039`b45e9560 00007ffb`bf6f6230 VCRUNTIME140!__RTtypeid+0x15 [D:\a\_work\1\s\src\vctools\crt\vcruntime\src\eh\rtti.cpp @ 152] 02 00000039`b45e95b0 00007ffc`2c983202 QtWidgets+0x46230 03 00000039`b45e95f0 00007ffc`2c97c9f9 pyside6_abi3!PySide::PySideName::parameters+0xe2 04 00000039`b45e9650 00007ffc`2c985135 pyside6_abi3!PySide::SignalManager::callPythonMetaMethod+0x59 05 00000039`b45e96d0 00007ffb`db248fc1 pyside6_abi3!PySide::PySideName::qtEmit+0x995 06 00000039`b45e97d0 00007ffb`db27bed2 Qt6Core!QMetaObject::metacall+0x41 07 00000039`b45e9810 00007ffb`db27eae4 Qt6Core!QObject::qt_static_metacall+0x1832 08 00000039`b45e9950 00007ffb`b714e093 Qt6Core!QMetaObject::activate+0x84 09 00000039`b45e9980 00007ffb`db27beb6 Qt6Widgets!QTreeWidget::qt_static_metacall+0x123 0a 00000039`b45e9a20 00007ffb`db27eae4 Qt6Core!QObject::qt_static_metacall+0x1816 0b 00000039`b45e9b60 00007ffb`db457e6f Qt6Core!QMetaObject::activate+0x84 0c 00000039`b45e9b90 00007ffb`b7154548 Qt6Core!QAbstractItemModel::dataChanged+0x3f 0d 00000039`b45e9bf0 00007ffb`b715bc14 Qt6Widgets!QTreeWidget::editItem+0x1d8 0e 00000039`b45e9ca0 00007ffb`bf72cd55 Qt6Widgets!QTreeWidgetItem::setData+0x744 0f 00000039`b45e9e50 00007ffb`b715b4bb QtWidgets+0x7cd55 10 00000039`b45e9eb0 00007ffb`b70f60db Qt6Widgets!QTreeWidget::setCurrentItem+0xeb 11 00000039`b45e9ef0 00007ffb`b70d4fcc Qt6Widgets!QStyledItemDelegate::editorEvent+0x2cb 12 00000039`b45ea0c0 00007ffb`b70cb557 Qt6Widgets!QAbstractItemView::selectionCommand+0x51c 13 00000039`b45ea250 00007ffb`bf70708d Qt6Widgets!QAbstractItemView::edit+0x107 14 00000039`b45ea300 00007ffb`b70d1208 QtWidgets+0x5708d 15 00000039`b45ea380 00007ffb`b7145822 Qt6Widgets!QAbstractItemView::mouseReleaseEvent+0x2b8 16 00000039`b45ea560 00007ffb`bf7202a9 Qt6Widgets!QTreeView::mouseReleaseEvent+0x92 17 00000039`b45ea5d0 00007ffb`b6e66bc4 QtWidgets+0x702a9 18 00000039`b45ea620 00007ffb`b6f02540 Qt6Widgets!QWidget::event+0x164 19 00000039`b45ea700 00007ffb`b70d8efd Qt6Widgets!QFrame::event+0x30 1a 00000039`b45ea730 00007ffb`bf735f6a Qt6Widgets!QAbstractItemView::viewportEvent+0x45d 1b 00000039`b45ea8c0 00007ffb`db23eb87 QtWidgets+0x85f6a 1c 00000039`b45ea930 00007ffb`b6e21f23 Qt6Core!QCoreApplicationPrivate::sendThroughObjectEventFilters+0xd7 1d 00000039`b45ea990 00007ffb`b6e1fef0 Qt6Widgets!QApplicationPrivate::notify_helper+0xf3 1e 00000039`b45ea9c0 00007ffb`bfac880c Qt6Widgets!QApplication::notify+0x750 1f 00000039`b45eae70 00007ffb`db23b6ff QtWidgets!PyInit_QtWidgets+0x390624 20 00000039`b45eaef0 00007ffb`b6e25eaf Qt6Core!QCoreApplication::notifyInternal2+0x11f 21 00000039`b45eaf60 00007ffb`b6e89529 Qt6Widgets!QApplicationPrivate::sendMouseEvent+0x3ef 22 00000039`b45eb080 00007ffb`b6e86c02 Qt6Widgets!QWidgetRepaintManager::updateStaticContentsSize+0x34f9 23 00000039`b45eb590 00007ffb`b6e21f3e Qt6Widgets!QWidgetRepaintManager::updateStaticContentsSize+0xbd2 24 00000039`b45eb6b0 00007ffb`b6e21061 Qt6Widgets!QApplicationPrivate::notify_helper+0x10e 25 00000039`b45eb6e0 00007ffb`bfac880c Qt6Widgets!QApplication::notify+0x18c1 26 00000039`b45ebb90 00007ffb`db23b6ff QtWidgets!PyInit_QtWidgets+0x390624 27 00000039`b45ebc10 00007ffb`a9ba3f67 Qt6Core!QCoreApplication::notifyInternal2+0x11f 28 00000039`b45ebc80 00007ffb`a9c00dea Qt6Gui!QGuiApplicationPrivate::processMouseEvent+0x777 29 00000039`b45ec1b0 00007ffb`db3c2d70 Qt6Gui!QWindowSystemInterface::sendWindowSystemEvents+0xea 2a 00000039`b45ec1e0 00007ffb`a9e91989 Qt6Core!QEventDispatcherWin32::processEvents+0x90 2b 00000039`b45ef370 00007ffb`db242f8f Qt6Gui!QWindowsGuiEventDispatcher::processEvents+0x19 2c 00000039`b45ef3a0 00007ffb`db238c8d Qt6Core!QEventLoop::exec+0x19f 2d 00000039`b45ef440 00007ffb`bfa71bf0 Qt6Core!QCoreApplication::exec+0x15d 2e 00000039`b45ef4a0 00007ffb`dec59126 QtWidgets!PyInit_QtWidgets+0x339a08 2f 00000039`b45ef4d0 00007ffb`dec5a544 python311!PyObject_Vectorcall+0x606 30 00000039`b45ef5e0 00007ffb`dec6fa77 python311!PyEval_EvalFrameDefault+0x784 31 00000039`b45ef7f0 00007ffb`dec6f137 python311!PyMapping_Check+0x1eb 32 00000039`b45ef830 00007ffb`dec6d80a python311!PyEval_EvalCode+0x97 33 00000039`b45ef8b0 00007ffb`dec6d786 python311!PyMapping_Items+0x11e 34 00000039`b45ef8e0 00007ffb`dedba17e python311!PyMapping_Items+0x9a 35 00000039`b45ef920 00007ffb`dec233a5 python311!PyThread_tss_is_created+0x53ce 36 00000039`b45ef990 00007ffb`ded1a620 python311!PyRun_SimpleFileObject+0x11d 37 00000039`b45efa00 00007ffb`ded1aaef python311!PyRun_AnyFileObject+0x54 38 00000039`b45efa30 00007ffb`ded1ab5f python311!Py_MakePendingCalls+0x38f 39 00000039`b45efb00 00007ffb`ded1b964 python311!Py_MakePendingCalls+0x3ff 3a 00000039`b45efb30 00007ffb`ded1b7f5 python311!Py_RunMain+0x184 3b 00000039`b45efba0 00007ffb`dec660d9 python311!Py_RunMain+0x15 3c 00000039`b45efbd0 00007ff6`08131230 python311!Py_Main+0x25 3d 00000039`b45efc20 00007ffc`6532257d python+0x1230 3e 00000039`b45efc60 00007ffc`6628aa48 KERNEL32!BaseThreadInitThunk+0x1d 3f 00000039`b45efc90 00000000`00000000 ntdll!RtlUserThreadStart+0x28
which indeed gives me some clues.
I think I was hoping for some magic debugging switch in PySide that I could trigger from python and would output but this is going to help me as well. Thanks!
-
@VirtualFloat
It's not ideal, but maybe it gives us a clue.03 00000039`b45e95f0 00007ffc`2c97c9f9 pyside6_abi3!PySide::PySideName::parameters+0xe2 04 00000039`b45e9650 00007ffc`2c985135 pyside6_abi3!PySide::SignalManager::callPythonMetaMethod+0x59 05 00000039`b45e96d0 00007ffb`db248fc1 pyside6_abi3!PySide::PySideName::qtEmit+0x995
We are inside some kind of (Python)
emit
. It is calling some Python "meta method", doubtless for the signal-to-slot connection, and perhaps falling over setting up or passing parameters.[I have reproduced your "crash" with your code in my PySide2. Examining now and will post whatever correction shortly....]
-
@VirtualFloat
Findings:-
It does not crash every time. Sometimes I can click several times before it dumps. I suspect it might be to do with the speed at which I click, not sure.
-
It behaves the same (dumps immediately or after a few clicks) under PyQt5 as under PySide2.
-
Replacing the signal connection to
treewidget.itemChanged.connect(lambda: print("Received!"))
never crashes. -
Commenting out both the
treewidget.clear()
&add_items()
in the slot, so it only doesprint('clearing')
, works.
You have connected
add_items()
slot to be called on signalitemChanged()
. That means it is inside a call for a particular item being changed. In your slot you do 3 "worrying" things:treewidget.clear()
clears all the existing items. That includes the item in the midst of being changed.- You then add a number of new items in place of the old ones.
- And because you put
treewidget.itemChanged.connect()
insideadd_items()
you create a connection again, which you do not want to do, and you are doing that while still inside the signal handling. In your code theconnect()
belongs in the main line, e.g. immediately beforeadd_items()
call there, so it can only execute once.
I can only say that you cannot afford to have
treewidget.clear()
and/oradd_items()
in the slot being executed for when an item has been changed. -
-
@JonB , I really appreciate you help. Just want to stress again that I'm not looking for a solution to this particular situation. A solution is to create a single shot timer to handle the refresh of the items after the updates have been done.
-
@VirtualFloat
Yes, if for whatever reason that we do not know it is the fact that you clear/refill the items inside the slot for theitemChanged
signal which causes the "crash" then moving it to a 0-delayQTimer::singleShot()
would be recommended.