Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. Problem with exception handling in QOpenGLWidget

Problem with exception handling in QOpenGLWidget

Scheduled Pinned Locked Moved Solved Qt for Python
qt for pythonpyside
5 Posts 2 Posters 326 Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • A Offline
    A Offline
    Arctoros
    wrote on last edited by Arctoros
    #1

    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 an IndexError in the initializeGL and paintGL methods. 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 1
    

    If I then add override for the paintGL method 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?

    JonBJ 1 Reply Last reply
    0
    • A Arctoros

      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 an IndexError in the initializeGL and paintGL methods. 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 1
      

      If I then add override for the paintGL method 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?

      JonBJ Offline
      JonBJ Offline
      JonB
      wrote on last edited by JonB
      #2

      @Arctoros
      I don't know anything particular about QOpenGLWidget, 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.

      A 1 Reply Last reply
      1
      • JonBJ JonB

        @Arctoros
        I don't know anything particular about QOpenGLWidget, 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.

        A Offline
        A Offline
        Arctoros
        wrote on last edited by Arctoros
        #3

        @JonB

        @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: Exception
        

        I also tried replacing sys.excepthook with 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_exception slot. However, something strange happens with QOpenGLWidget.
        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.excepthook is not being called when I raise exception inside initializeGL.
        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 1
        

        Could this be a bug in PySide6?

        JonBJ 1 Reply Last reply
        0
        • A Arctoros

          @JonB

          @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: Exception
          

          I also tried replacing sys.excepthook with 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_exception slot. However, something strange happens with QOpenGLWidget.
          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.excepthook is not being called when I raise exception inside initializeGL.
          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 1
          

          Could this be a bug in PySide6?

          JonBJ Offline
          JonBJ Offline
          JonB
          wrote on last edited by
          #4

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

          1 Reply Last reply
          0
          • A Offline
            A Offline
            Arctoros
            wrote on last edited by Arctoros
            #5

            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 use logging.error to get an exception traceback.
            Thank you for your time!

            1 Reply Last reply
            0
            • A Arctoros has marked this topic as solved on

            • Login

            • Login or register to search.
            • First post
              Last post
            0
            • Categories
            • Recent
            • Tags
            • Popular
            • Users
            • Groups
            • Search
            • Get Qt Extensions
            • Unsolved