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

Phil Thompson phil at riverbankcomputing.com
Wed Apr 19 22:41:12 BST 2017


On 19 Apr 2017, at 9:30 pm, Russell Warren <russ at perspexis.com> wrote:
> 
> 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

Parentage isn't the issue. It's the fact that the C++ instances are being created internally and not by your application. The workaround is to not keep long-lived references to internally generated objects - which is bad practice anyway IMHO.

> 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?

Experience has shown that this creates as many problems as it solves when applied generally. The approach would be useful to apply in specific cases (ie. with C++ instances that are known to have been created internally) but SIP doesn't have the ability to detect those cases. I'll add it to the TODO list.

Phil


More information about the PyQt mailing list