[PyQt] Automatically avoiding segfaults with sip.setdeleted()?

Russell Warren russ at perspexis.com
Wed Apr 19 21:30:20 BST 2017


I've been looking into frustratingly frequent segfaults with a PyQt5
application today.  The go-to reason is usually that some C-side Qt
instance has been deleted without PyQt being aware of it.  PyQt is decent
at handling this case gracefully sometimes (with a nice "RuntimeError:
wrapped C/C++ object of type <whatever> has been deleted" exception), but
it is definitely not perfect.

Experimentation seems to show that when python is aware of the
hierarchy/parentage via python-side constructors, it is handled well, as in
this short example:

>>> from PyQt5 import QtCore
>>> obj1 = QtCore.QObject()
>>> obj2 = QtCore.QObject(parent = obj1)
>>> del obj1  # <-- Qt will also delete the obj2 child
>>> obj2.objectName()
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    obj2.objectName()
RuntimeError: wrapped C/C++ object of type QObject has been deleted

This exception is great.  However, when the parentage is set internally to
the C++ code (without python knowing about it), segfaults can result.  For
example:

>>> from PyQt5 import QtWidgets
>>> app = QtWidgets.QApplication([])
>>> w = QtWidgets.QMainWindow()
>>> sb = w.statusBar() # <-- Qt internals set the child relationship
>>> del w              # <-- Qt deletes sb child w/o python knowing
>>> sb.objectName()Segmentation fault

That segfault is clearly undesirable. However... fixing it is conceptually
trivial, since you just need to hook sip.setdeleted() up to the 'destroyed'
signal:

>>> import sip
>>> from PyQt5 import QtWidgets
>>> def on_destroy(obj):
...     sip.setdeleted(obj)
...
>>> app = QtWidgets.QApplication([])
>>> w = QtWidgets.QMainWindow()
>>> sb = w.statusBar()
>>> sb.destroyed.connect(on_destroy)
>>> del w
>>> sb.objectName()
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    sb.objectName()
RuntimeError: wrapped C/C++ object of type QStatusBar has been deleted
>>> # rejoice! no segfault!

Getting that RuntimeError is clearly MUCH better than getting a segfault.
but having to manually connect/manage all of those 'destroyed' connections
in application code is obviously not ideal, and is prone to errors.

It seems like a good solution would be to ALWAYS connect the
QObject.destroyed signal of wrapped QObject instances to sip.setdeleted.

Is there a way to do this destroyed->sip.setdeleted hookup automatically
for all wrapped QObjects without needing to manage it for each
PyQt5.QObject instance at the application layer?

Thanks,
Russ
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20170419/a03ad4ed/attachment.html>


More information about the PyQt mailing list