Skip to content

[Bug]: FigureCanvasQT is seemingly prematurely freed under certain conditions #29618

Closed

Description

@ezra-varady

Bug summary

I'm in the process of updating some code using matplotlib and pyside6. Using version 6.6.0 of PySide there is no error, but on versions 6.7.0 and newer I get an error indicating that the underlying C++ object has been prematurely freed. I've had a hard time identifying the source of the bug. I have opened an issue with pyside, but as I have been unable to recreate the bug without matplotlib components I thought I would open an issue here and see if anyone had any ideas. Sorry to bother, and thanks for the help.

Code for reproduction

from PySide6.QtWidgets import QWidget, QMainWindow, QVBoxLayout, QApplication
from matplotlib.backends.backend_qt import FigureCanvasQT

from matplotlib.figure import Figure
class test_class(QMainWindow):
    def __init__(self, parent):
        super().__init__(parent)
        self.testfig = None
        self.canvas = FigureCanvasQT(self.testfig)
        self.vbox = QWidget(self)
        self.canvas.setParent(self.vbox)

def test_testing():
    qtbot = QApplication([])
    c1 = test_class(None)
    c1.setVisible(True)

test_testing()
QApplication.instance().processEvents()

Actual outcome

Traceback (most recent call last):
File "/home/ez/test.py", line 20, in
QApplication.instance().processEvents()
File "/home/ez/micromamba/envs/pycodas/lib/python3.11/site-packages/matplotlib/backends/backend_qt.py", line 495, in _draw_idle
if self.height() < 0 or self.width() < 0:
^^^^^^^^^^^^^
RuntimeError: Internal C++ object (FigureCanvasQT) already deleted.

Expected outcome

It shouldn't do anything, maybe briefly display a window, but the error is unexpected. I may just be using something wrong, but this code did run in the past without error

Additional information

I should mention that I copied the entire class definition of FigureCanvasQT into my test file and that runs just fine, which I find somewhat confusing. Though this may be explained by the fact that I am using version 3.9.1 of matplotlib which may differ from the main branch.

Operating system

ubuntu

Matplotlib Version

3.9.1

Matplotlib Backend

qtagg

Python version

3.11.11

Jupyter version

No response

Installation

conda

Activity

  1. tacaswell commented on Feb 14, 2025

    @tacaswell
    Member

    Does

    from PySide6.QtWidgets import QWidget, QMainWindow, QVBoxLayout, QApplication
    from matplotlib.backends.backend_qt import FigureCanvasQT
    
    from matplotlib.figure import Figure
    class test_class(QMainWindow):
        def __init__(self, parent):
            super().__init__(parent)
            self.testfig = None
            self.canvas = FigureCanvasQT(self.testfig)
            self.vbox = QWidget(self)
            self.canvas.setParent(self.vbox)
    
    def test_testing():
        qtbot = QApplication([])
        c1 = test_class(None)
        c1.setVisible(True)
        return c1
    
    c = test_testing()
    QApplication.instance().processEvents()

    this fix the problem? I suspect that Python and Qt's gcs are having conflicts.

  2. ezra-varady commented on Feb 14, 2025

    @ezra-varady
    Author

    It does, I also found that if I manually shut down the application at the end of the function it resolves this. Using pdb in the version where the error is generated, the sys.getrefcount is non zero at the time it is generated. This makes me think you're correct that Qt's GC doesn't agree with python. I have not as of yet however been able to isolate why this is. Thanks for your response!

  3. efiring commented on Aug 22, 2025

    @efiring
    Member

    Is any action needed on the matplotlib side, or can this be closed now? My interpretation is that application code needs to keep Python references to (some?) objects created by Qt until they are no longer used, implicitly or explicitly. Or, is there something that can be done in the matplotlib library to prevent the problem?

  4. timhoffm commented on Aug 22, 2025

    @timhoffm
    Member

    @tacaswell‘s example is a workaround, not a solution. There should not hechte need for the user to keep a reference to the canvas. OTOH, _draw_idle should not needed to have to worry about a deleted Qt object. Therefore, I also don‘t see a clear solution in the matplotlib side.

    Given the example is rather theoretical and it depends on the Qt version I will close this under the assumption that it‘s an issue between Qt and PyQt. We are happy to reopen if there’s a more realistic example, which hopefully will inform us better about theorizing cause of the issue.

  5. tacaswell commented on Aug 22, 2025

    @tacaswell
    Member

    Reading this again, I think the issue is that the QMainWindow that the user is creating is getting deleted by Python [there are no circular refs and it simply goes out of scope] that causes the c++ side of the main window to get cleaned up. I speculate that this in turn cleans up all of the widgets in the window (as without a window they can not do anything!) on the c++ side. We have enough circular references that our Python side objects stick around for a bit (and the signal/slot machinery for draw on show may be enough to keep some non-circular hard-refs around) so we are trying to draw to a non-existent widget in a non existent window.

    Agree with Tim's plan to re-open if we have a more realistic example.

  6. tacaswell commented on Aug 29, 2025

    @tacaswell
    Member

    I have hit a real-world use case: bluesky/bluesky-widgets#203

    The very short version, we have some classes that wrap Matplotlib and are using a pytest extension to manage testing the Qt aspect. On every test this drives to the case where the c++ side is cleaned up, the Python side still exists, and a process_events tries to do one last draw_idle on a non-existent widget.

  7. added this to the v3.10.6 milestone on Aug 29, 2025
  8. added a commit that references this issue on Aug 29, 2025
    4821cd6
  9. added a commit that references this issue on Aug 29, 2025
    4b0a26a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions