Skip to content

Qt widget-lifetime crash blocks CI on Python 3.13 #15

Description

@sadityakr

Blocks: moving CI from Python 3.12 to 3.13

Summary

The test suite segfaults on Python 3.13 under Linux. pyqtgraph paints a
graphics item whose underlying C++ object has already been destroyed, driven by
pytest-qt's event pump, and the process dies with SIGSEGV (make: *** Error 139).

CI is pinned to Python 3.12 as a workaround (b2a87e4). Development machines
run 3.13, so the versions are deliberately out of step until this is fixed.

Evidence

Two CI runs on Python 3.13.14 / Linux, both develop:

Run Died after Object already deleted
30541485700 tests/test_l0_keithley_6221_error_queue.py, 35% (segfault, no Python-level error shown)
30543125938 inside tests/test_gui.py, 32% ViewBox

Representative traceback:

Exceptions caught in Qt event loop:
  File "pyqtgraph/graphicsItems/AxisItem.py", line 991, in paint
    specs = self.generateDrawSpecs(painter)
  File "pyqtgraph/graphicsItems/AxisItem.py", line 960, in boundingRect
    linkedView.mapRectToItem(self, linkedView.boundingRect())
RuntimeError: wrapped C/C++ object of type ViewBox has been deleted

Fatal Python error: Segmentation fault
Current thread (most recent call first):
  File "pyqtgraph/widgets/GraphicsView.py", line 138 in paintEvent
  File "pytestqt/plugin.py", line 220 in _process_events
  File "pytestqt/plugin.py", line 188 in pytest_runtest_call

This is not new, and not Linux-only

The same crash occurred on a Windows development machine (Python 3.13.5,
PyQt6 6.7.1), roughly one full-suite run in four, with the identical stack and
RuntimeError: wrapped C/C++ object of type AxisItem has been deleted plus
Windows fatal exception: access violation.

Python 3.11 and 3.12 do not trigger it. They are quieter, not safe: the
underlying object-lifetime bug is present on every version, and 3.13's
garbage-collection timing simply makes it fire most runs instead of
occasionally.

Ruled out

  • PyQt6 version. 3.11 + PyQt6 6.11.0 completes normally; 3.13 + 6.11.0 and
    3.13 + 6.7.1 both segfault. The Qt version is not the trigger.
  • A single bad test. The two runs died at different points in the suite.
    This is a race, not one broken case.

Likely cause

A pyqtgraph plot's C++ side is garbage-collected while a paint event is still
queued for it; the event pump then paints freed memory. The usual source is a
widget whose lifetime is not tied to qtbot, or a plot nested inside a
registered parent that is torn down in an order leaving the ViewBox dead but
the GraphicsView still painting.

Registration coverage is broadly good already (144 qtbot.addWidget calls
across 20 test files), so this is unlikely to be a simple missing-addWidget
sweep.

Suggested approach

  1. Reproduce in a local Linux container with QT_QPA_PLATFORM=offscreen, not
    over CI round trips. Iterating a two-in-three-runs race through a 3-minute
    pipeline is the main time sink.
  2. Run the GUI tests with -v and faulthandler to get the exact test on each
    crash across several runs, and look for what the crashing tests share.
  3. Fix the lifetime: register the plot widgets with qtbot, or add a teardown
    that hides widgets and drains deferred deletes before the test ends.
  4. Verify with 5-10 consecutive green runs on 3.13 — a single green run means
    nothing for a nondeterministic bug.
  5. Move both CI configs back to 3.13 (.github/workflows/ci.yml and
    .gitlab-ci.yml must change together).

Estimate

4-8 hours, most of it diagnosis. Could be 1 hour if the first clean
reproduction points at a single unparented plot widget; could be two days if it
turns out to need a pyqtgraph/PyQt6 workaround.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions