You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Segfault in test_key_hold: CanvasHud timer lambda runs after the collector clears it #1614
make test crashes with a segfault in tests/test_key_hold.py. The crash comes from the lambda on the CanvasHud message timer (negpy/desktop/view/canvas/hud.py:126). It is not a flaky test: 5 of 5 logged runs of pytest tests/ --cov=negpy --cov-report=term-missing crashed at the same line on macOS, and main crashed there too.
tests/test_key_hold.py .Fatal Python error: Segmentation fault
Current thread (most recent call first):
File "negpy/desktop/view/canvas/hud.py", line ??? in <lambda>
File "tests/test_key_hold.py", line 43 in test_long_press_holds_until_release
The native stack is QTest::qWait → QTimer::timerEvent → PyQtSlot::call → _PyEval_EvalFrameDefault, with KERN_INVALID_ADDRESS at 0x18.
Cause
The lambda closes over self, and the garbage collector can clear it while the Qt timer that calls it is still alive.
test_hud_message_wrap.py leaves a HUD with a 5 second message timer in a reference cycle (hud._keepalive = parent).
A collection runs on a background thread. In a crashing run it was Thread-85, during test_hue_trim.py, the next file. It clears the lambda's closure, then PyQt releases the wrapper. On the wrong thread, PyQt calls deleteLater() and does not delete the C++ widget.
The next tests do not run the Qt event loop, so the timer expires and the deferred delete waits.
QTest.qWait in test_key_hold.py is the next event loop spin. Due timers run before the deferred delete, so the timer calls the lambda. The lambda reads a NULL closure: 0x18 is the offset of a tuple's items.
--cov only moves the timing, so this can happen without it.
Reproduction
This crashes with exit code 139 in 3 of 3 runs. Replace the lambda with a bound method, or call gc.collect() on the main thread, and it does not crash.
Connect the timer to a bound method. PyQt does not hold a closure for a bound method, so the collector has nothing to clear. The _progress_timer beside it does this already. pick_better_window.py:109 has the same lambda on a timer.
I have no report of this crash in the running app. A HUD normally lives as long as the window, but the pattern is unsafe for any object that a background thread can collect.
Activity
kurtwindisch commentedon Nov 5, 2026
/assign