Skip to content

Drop a pending flash registration when Qt destroys its widget (0.3.30) - #19

Merged
trissim merged 2 commits into
mainfrom
fix/B3-pending-flash-registration-lifetime
Oct 10, 2026
Merged

trissim merged 2 commits into
mainfrom
fix/B3-pending-flash-registration-lifetime

Conversation

@trissim

@trissim trissim commented Oct 10, 2026

Copy link
Copy Markdown
Contributor

Cause

_GlobalFlashCoordinator keeps flash registrations whose widget is not yet inside a flash window (QMainWindow/QDialog). Nothing removed them except a successful attach, so registrations for widgets that never reach such a window (embedded forms, test widgets) piled up for the life of the process. On every local flash, _process_pending_registrations called widget.isVisible() and widget.window() on each one and relied on RuntimeError to skip deleted widgets.

A wrapper of a Qt-created widget, such as a QScrollArea viewport reached through parentWidget(), is not always marked deleted by sip. In the OpenHCS unit suite, Qt destroyed such a viewport (its destroyed signal fired) while sip.isdeleted stayed False. The next flush then called window() on freed memory and segfaulted. This crashed an xdist worker in test_image_browser_streaming_config.py (OpenHCS B3).

Fix

  • A pending registration is now a _PendingRegistration. When it is added, a _DestructionCallback is attached to its widget and to every lifecycle widget its element closes over, so Qt destruction of any of them drops the registration. This is the same mechanism the mixin already uses for its own registration records.
  • _process_pending_registrations no longer probes deleted widgets or catches RuntimeError. A pending registration always refers to live widgets.
  • Version 0.3.30, with a changelog entry.

Tests

  • New test_pending_registration_ends_with_every_widget_it_closes_over: a viewport-backed registration and a leaf registration are dropped when the viewport or leaf is destroyed.
  • pytest tests -n 2 against the worktree libraries: 610 passed.

🤖 Generated with Claude Code

The flash coordinator held registrations for widgets outside a flash
window (QMainWindow/QDialog) for the life of the process and called
widget.isVisible()/window() on every local flash, relying on RuntimeError
for deleted widgets. A wrapper of a Qt-created widget, such as a scroll
area's viewport reached through parentWidget(), is not always marked
deleted by sip, so that call read freed memory and segfaulted.

Each pending registration now ends when Qt destroys its widget or any
widget its element closes over; the RuntimeError probe is gone. 0.3.30.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 10, 2026 19:09

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@trissim
trissim merged commit 291b1be into main Oct 10, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants