Problem with exception handling in QOpenGLWidget
-
Hello everybody!
I'm using PySide6 (6.10.1) on Linux Debian 12 (Bookworm), Python 3.11.
I tried using QOpenGLWidget to display a 3D model. But the application crashed, returning a segmentation violation signal. At first, I thought the problem was with buffer initialization, but then I noticed a typo that would inevitably trigger anIndexErrorin theinitializeGLandpaintGLmethods. I started investigating why there were no exceptions visible in the terminal.
To do this, I used following code:import sys from PySide6 import QtWidgets, QtOpenGLWidgets class MyWidget(QtOpenGLWidgets.QOpenGLWidget): def initializeGL(self, /) -> None: print("initializeGL") raise Exception("GL initialization exception") def main(): app = QtWidgets.QApplication(sys.argv) w = MyWidget() w.show() return app.exec() if __name__ == '__main__': sys.exit(main())If I run it like this, everything works as expected—a traceback appears in the terminal:
initializeGL Traceback (most recent call last): File "main.py", line 23, in <module> sys.exit(main()) ^^^^^^ File "main.py", line 17, in main w.show() File "main.py", line 9, in initializeGL raise Exception("GL initialization exception") Exception: Error calling Python override of QOpenGLWidget::initializeGL(): GL initialization exception Process finished with exit code 1If I then add override for the
paintGLmethod like that:... def paintGL(self, /) -> None: raise Exception("GL paint exception")After launching, despite the exception in initializeGL and paintGL, the application runs as if nothing happened. And it doesn't show any exceptions.
It looks like the problem is somewhere in the QOpenGLWidget class wrapper. I managed to somewhat solve the problem by wrapping the method bodies like this:try: <method body> except Exception as e: print(e)But I don't like this solution because of potential debugging issues. Could anyone suggest other ways to work around this problem, or other ways to draw using OpenGL in PySide6?
-
Hello everybody!
I'm using PySide6 (6.10.1) on Linux Debian 12 (Bookworm), Python 3.11.
I tried using QOpenGLWidget to display a 3D model. But the application crashed, returning a segmentation violation signal. At first, I thought the problem was with buffer initialization, but then I noticed a typo that would inevitably trigger anIndexErrorin theinitializeGLandpaintGLmethods. I started investigating why there were no exceptions visible in the terminal.
To do this, I used following code:import sys from PySide6 import QtWidgets, QtOpenGLWidgets class MyWidget(QtOpenGLWidgets.QOpenGLWidget): def initializeGL(self, /) -> None: print("initializeGL") raise Exception("GL initialization exception") def main(): app = QtWidgets.QApplication(sys.argv) w = MyWidget() w.show() return app.exec() if __name__ == '__main__': sys.exit(main())If I run it like this, everything works as expected—a traceback appears in the terminal:
initializeGL Traceback (most recent call last): File "main.py", line 23, in <module> sys.exit(main()) ^^^^^^ File "main.py", line 17, in main w.show() File "main.py", line 9, in initializeGL raise Exception("GL initialization exception") Exception: Error calling Python override of QOpenGLWidget::initializeGL(): GL initialization exception Process finished with exit code 1If I then add override for the
paintGLmethod like that:... def paintGL(self, /) -> None: raise Exception("GL paint exception")After launching, despite the exception in initializeGL and paintGL, the application runs as if nothing happened. And it doesn't show any exceptions.
It looks like the problem is somewhere in the QOpenGLWidget class wrapper. I managed to somewhat solve the problem by wrapping the method bodies like this:try: <method body> except Exception as e: print(e)But I don't like this solution because of potential debugging issues. Could anyone suggest other ways to work around this problem, or other ways to draw using OpenGL in PySide6?
@Arctoros
I don't know anything particular aboutQOpenGLWidget, but in general Qt does not throw exceptions (C++) and does not expect any code you write to throw exceptions, e.g. if you override a method. (See e.g. https://doc.qt.io/qtforpython-6.5/overviews/exceptionsafety.html, though confusingly this is just a direct copy of the C++ situation without reference to Python/PySide.)In the Python case every call to a Qt C++ function is effectively wrapped for Python code. It is likely that if that throws a (Python) exception behaviour is also undefined.
If we were writing in C++ and chose to use exceptions in our own code we would expect/have to catch any and all exceptions, handle them and allow continue back in original C++ caller, else undefined behaviour/crashes. I would expect to have to do just the same if I were writing in Python and they might raise exceptions.
There may be better approached (I am not a Python expert), but to help debugging you might write everything like this:
try: <your code> except Exception as e: unhandled_exception(e) <suitable continuation> def unhandled_exception(e): print (e)and put a permanent Python debugger breakpoint in
unhandled_exception()so that at least you will know whenever it is hit. And/or you may as well stop/exit once you hit an (unhandled) exception in your code being executed from a Qt function as you must sort this out before allowing any continuation.From a quick Google, read e.g. https://stackoverflow.com/questions/45787237/exception-handled-surprisingly-in-pyqt-pyside-slots. You should probably also read https://pytest-qt.readthedocs.io/en/latest/virtual_methods.html, which states a mechanism apparently introduced at PySide6 6.5.2. However I think that only applies to (Python) exceptions inside slots on signals, which is a different case from inside virtual overrides.
-
@Arctoros
I don't know anything particular aboutQOpenGLWidget, but in general Qt does not throw exceptions (C++) and does not expect any code you write to throw exceptions, e.g. if you override a method. (See e.g. https://doc.qt.io/qtforpython-6.5/overviews/exceptionsafety.html, though confusingly this is just a direct copy of the C++ situation without reference to Python/PySide.)In the Python case every call to a Qt C++ function is effectively wrapped for Python code. It is likely that if that throws a (Python) exception behaviour is also undefined.
If we were writing in C++ and chose to use exceptions in our own code we would expect/have to catch any and all exceptions, handle them and allow continue back in original C++ caller, else undefined behaviour/crashes. I would expect to have to do just the same if I were writing in Python and they might raise exceptions.
There may be better approached (I am not a Python expert), but to help debugging you might write everything like this:
try: <your code> except Exception as e: unhandled_exception(e) <suitable continuation> def unhandled_exception(e): print (e)and put a permanent Python debugger breakpoint in
unhandled_exception()so that at least you will know whenever it is hit. And/or you may as well stop/exit once you hit an (unhandled) exception in your code being executed from a Qt function as you must sort this out before allowing any continuation.From a quick Google, read e.g. https://stackoverflow.com/questions/45787237/exception-handled-surprisingly-in-pyqt-pyside-slots. You should probably also read https://pytest-qt.readthedocs.io/en/latest/virtual_methods.html, which states a mechanism apparently introduced at PySide6 6.5.2. However I think that only applies to (Python) exceptions inside slots on signals, which is a different case from inside virtual overrides.
@JonB said in Problem with exception handling in QOpenGLWidget:
From a quick Google, read e.g. https://stackoverflow.com/questions/45787237/exception-handled-surprisingly-in-pyqt-pyside-slots. You should probably also read https://pytest-qt.readthedocs.io/en/latest/virtual_methods.html, which states a mechanism apparently introduced at PySide6 6.5.2. However I think that only applies to (Python) exceptions inside slots on signals, which is a different case from inside virtual overrides.
Yes, in the version of PySide6 I'm using, exceptions that occur during event or signal processing are displayed in the terminal. For example, I created a button whose click signal I connected to a slot like this:
... @QtCore.Slot() def do_exception(self): raise RuntimeError("Exception")Which gives following in the terminal, when I press the button:
Traceback (most recent call last): File "<project>/main.py", line 50, in do_crash raise RuntimeError("Exception") RuntimeError: ExceptionI also tried replacing
sys.excepthookwith the following function:def excepthook(cls, exception, traceback): logging.error("Unhandled exception", exc_info=exception)Essentially, it prints an "Unhandled exception" message to the terminal, and then prints the stack trace. The function is called when an exception is raised in the
do_exceptionslot. However, something strange happens withQOpenGLWidget.
This is what the class currently looks like:class MyWidget(QtOpenGLWidgets.QOpenGLWidget): def initializeGL(self, /) -> None: logging.info("initializeGL") raise Exception("GL initialization exception") def paintGL(self, /) -> None: logging.info("paintGL") raise Exception("GL paint exception") def resizeGL(self, w: int, h: int, /) -> None: logging.info("resizeGL") raise Exception("GL resize exception")And if I run this code, the following is printed:
INFO:root:initializeGL INFO:root:paintGL Traceback (most recent call last): File "<project>/main.py", line 94, in <module> sys.exit(main()) ^^^^^^ File "<project>/main.py", line 85, in main w.show() File "<project>/main.py", line 67, in paintGL raise Exception("GL paint exception") Exception: Error calling Python override of QOpenGLWidget::paintGL(): GL paint exception Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)sys.excepthookis not being called when I raise exception insideinitializeGL.
But interestingly, in PySide6 6.7.3, this it works as expected:INFO:root:initializeGL ERROR:root:Unhandled exception Traceback (most recent call last): File "<project>/main.py", line 94, in <module> sys.exit(main()) ^^^^^^ File "<project>/main.py", line 85, in main w.show() File "<project>/main.py", line 63, in initializeGL raise Exception("GL initialization exception") Exception: GL initialization exception Process finished with exit code 1Could this be a bug in PySide6?
-
@JonB said in Problem with exception handling in QOpenGLWidget:
From a quick Google, read e.g. https://stackoverflow.com/questions/45787237/exception-handled-surprisingly-in-pyqt-pyside-slots. You should probably also read https://pytest-qt.readthedocs.io/en/latest/virtual_methods.html, which states a mechanism apparently introduced at PySide6 6.5.2. However I think that only applies to (Python) exceptions inside slots on signals, which is a different case from inside virtual overrides.
Yes, in the version of PySide6 I'm using, exceptions that occur during event or signal processing are displayed in the terminal. For example, I created a button whose click signal I connected to a slot like this:
... @QtCore.Slot() def do_exception(self): raise RuntimeError("Exception")Which gives following in the terminal, when I press the button:
Traceback (most recent call last): File "<project>/main.py", line 50, in do_crash raise RuntimeError("Exception") RuntimeError: ExceptionI also tried replacing
sys.excepthookwith the following function:def excepthook(cls, exception, traceback): logging.error("Unhandled exception", exc_info=exception)Essentially, it prints an "Unhandled exception" message to the terminal, and then prints the stack trace. The function is called when an exception is raised in the
do_exceptionslot. However, something strange happens withQOpenGLWidget.
This is what the class currently looks like:class MyWidget(QtOpenGLWidgets.QOpenGLWidget): def initializeGL(self, /) -> None: logging.info("initializeGL") raise Exception("GL initialization exception") def paintGL(self, /) -> None: logging.info("paintGL") raise Exception("GL paint exception") def resizeGL(self, w: int, h: int, /) -> None: logging.info("resizeGL") raise Exception("GL resize exception")And if I run this code, the following is printed:
INFO:root:initializeGL INFO:root:paintGL Traceback (most recent call last): File "<project>/main.py", line 94, in <module> sys.exit(main()) ^^^^^^ File "<project>/main.py", line 85, in main w.show() File "<project>/main.py", line 67, in paintGL raise Exception("GL paint exception") Exception: Error calling Python override of QOpenGLWidget::paintGL(): GL paint exception Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)sys.excepthookis not being called when I raise exception insideinitializeGL.
But interestingly, in PySide6 6.7.3, this it works as expected:INFO:root:initializeGL ERROR:root:Unhandled exception Traceback (most recent call last): File "<project>/main.py", line 94, in <module> sys.exit(main()) ^^^^^^ File "<project>/main.py", line 85, in main w.show() File "<project>/main.py", line 63, in initializeGL raise Exception("GL initialization exception") Exception: GL initialization exception Process finished with exit code 1Could this be a bug in PySide6?
@Arctoros
I am not sure what you are saying/pointing out. Are we agreed/do you follow that e.g. an exception during a slot on a signal --- and perhaps on an event --- are at least in principal "recoverable", insofar as one can stop handling the signal/slot/event and maybe report it at a later stage without interfering with Qt's processing, while an exception in an override interferes with Qt's processing and (so far as I am concerned) cannot/should not be allowed at all? -
Yes, sorry. I've got carried away. I do understand your point. And I agree that it's best to avoid situations where there are unhandled exceptions in Python code called from Qt. Because it may prevent some important code on the C++ side from executing. I'm just confused by the difference in this case, as the behavior changes between versions and when overriding methods in other classes. The thing is, in my short experience with PySide6, I somehow assumed the wrapper handled exception forwarding back to Python automatically. Apparently, that is not the case and I've been lucky until recently.
I'll take your suggestion.
@JonB said in Problem with exception handling in QOpenGLWidget:There may be better approached (I am not a Python expert), but to help debugging you might write everything like this:
try: <your code> except Exception as e: unhandled_exception(e) <suitable continuation> def unhandled_exception(e): print (e)The only difference is that instead of
print, I'll uselogging.errorto get an exception traceback.
Thank you for your time! -
A Arctoros has marked this topic as solved on