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
- 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.
- 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.
- Fix the lifetime: register the plot widgets with
qtbot, or add a teardown
that hides widgets and drains deferred deletes before the test ends.
- Verify with 5-10 consecutive green runs on 3.13 — a single green run means
nothing for a nondeterministic bug.
- 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.
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 machinesrun 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:tests/test_l0_keithley_6221_error_queue.py, 35%tests/test_gui.py, 32%ViewBoxRepresentative traceback:
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 deletedplusWindows 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
3.13 + 6.7.1 both segfault. The Qt version is not the trigger.
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 aregistered parent that is torn down in an order leaving the
ViewBoxdead butthe
GraphicsViewstill painting.Registration coverage is broadly good already (144
qtbot.addWidgetcallsacross 20 test files), so this is unlikely to be a simple missing-
addWidgetsweep.
Suggested approach
QT_QPA_PLATFORM=offscreen, notover CI round trips. Iterating a two-in-three-runs race through a 3-minute
pipeline is the main time sink.
-vandfaulthandlerto get the exact test on eachcrash across several runs, and look for what the crashing tests share.
qtbot, or add a teardownthat hides widgets and drains deferred deletes before the test ends.
nothing for a nondeterministic bug.
.github/workflows/ci.ymland.gitlab-ci.ymlmust 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.