Skip to content

Close anywidget comms on cell re-execution and deletion#8159

Merged
manzt merged 1 commit intomainfrom
push-zsokqyzwnkyo
Feb 6, 2026
Merged

Close anywidget comms on cell re-execution and deletion#8159
manzt merged 1 commit intomainfrom
push-zsokqyzwnkyo

Conversation

@manzt
Copy link
Collaborator

@manzt manzt commented Feb 6, 2026

When a cell containing an anywidget is re-executed or deleted, the runtime invalidates cell state and removes UI elements, but no ModelClose message was ever sent to the frontend.

The MarimoComm.__del__ fallback never fires because MarimoCommManager.comms holds a strong reference to every comm, preventing garbage collection. This leaks frontend objects along with their event listeners.

These changes adds a CommLifecycleItem that registers with the cell lifecycle registry when a widget comm is created. When the cell is invalidated (re-execution, deletion, or error), dispose() calls comm.close(), which broadcasts ModelClose to the frontend and unregisters from the comm manager.

@vercel
Copy link

vercel bot commented Feb 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment Feb 6, 2026 7:32pm

Request Review

@manzt manzt added the bug Something isn't working label Feb 6, 2026
mscolnick
mscolnick previously approved these changes Feb 6, 2026
When a cell containing an anywidget is re-executed or deleted, the
runtime invalidates cell state and removes UI elements, but no
ModelClose message was ever sent to the frontend.

The `MarimoComm.__del__` fallback never fires because
`MarimoCommManager.comms` holds a strong reference to every comm,
preventing garbage collection. This leaks frontend objects along with
their event listeners.

These changes adds a `CommLifecycleItem` that registers with the cell
lifecycle registry when a widget comm is created. When the cell is
invalidated (re-execution, deletion, or error), `dispose()` calls
`comm.close()`, which broadcasts ModelClose to the frontend and
unregisters from the comm manager.
@manzt manzt merged commit 04059d6 into main Feb 6, 2026
41 of 43 checks passed
@manzt manzt deleted the push-zsokqyzwnkyo branch February 6, 2026 21:57
manzt added a commit that referenced this pull request Feb 6, 2026
The anywidget refactors in #8156, #8159, and #8163 separated model
lifecycle from widget binding and moved to a dedicated ModelCommand for
frontend-to-backend communication.

However, unlike UpdateUIElementCommand which goes through
SetUIElementRequestManager's drain-and-merge path, ModelCommand was
processed immediately — each rapid model update (e.g. dragging a map
widget) triggered an individual cell re-execution.

These changes puts `ModelCommand` on the same shared queue as
`UpdateUIElementCommand` so both go through the same batching pipeline.
When multiple model updates arrive in quick succession, they are now
drained and merged (last-write-wins per model ID on state keys),
matching the existing UI element behavior.

The handler for model messages also now enqueues the resulting
`UpdateUIElementCommand` back through the control queue instead of
calling `set_ui_element_value` directly, so the downstream cell
re-execution also benefits from batching.
manzt added a commit that referenced this pull request Feb 8, 2026
The anywidget refactors in #8156, #8159, and #8163 separated model
lifecycle from widget binding and moved to a dedicated ModelCommand for
frontend-to-backend communication.

However, unlike UpdateUIElementCommand which goes through
SetUIElementRequestManager's drain-and-merge path, ModelCommand was
processed immediately — each rapid model update (e.g. dragging a map
widget) triggered an individual cell re-execution.

These changes puts `ModelCommand` on the same shared queue as
`UpdateUIElementCommand` so both go through the same batching pipeline.
When multiple model updates arrive in quick succession, they are now
drained and merged (last-write-wins per model ID on state keys),
matching the existing UI element behavior.

The handler for model messages also now enqueues the resulting
`UpdateUIElementCommand` back through the control queue instead of
calling `set_ui_element_value` directly, so the downstream cell
re-execution also benefits from batching.
manzt added a commit that referenced this pull request Feb 9, 2026
The anywidget refactors in #8156, #8159, and #8163 separated model
lifecycle from widget binding and moved to a dedicated ModelCommand for
frontend-to-backend communication.

However, unlike UpdateUIElementCommand which goes through
SetUIElementRequestManager's drain-and-merge path, ModelCommand was
processed immediately — each rapid model update (e.g. dragging a map
widget) triggered an individual cell re-execution.

These changes puts `ModelCommand` on the same shared queue as
`UpdateUIElementCommand` so both go through the same batching pipeline.
When multiple model updates arrive in quick succession, they are now
drained and merged (last-write-wins per model ID on state keys),
matching the existing UI element behavior.

The handler for model messages also now enqueues the resulting
`UpdateUIElementCommand` back through the control queue instead of
calling `set_ui_element_value` directly, so the downstream cell
re-execution also benefits from batching.
manzt added a commit that referenced this pull request Feb 10, 2026
The anywidget refactors in #8156, #8159, and #8163 separated model
lifecycle from widget binding and moved to a dedicated ModelCommand for
frontend-to-backend communication.

However, unlike UpdateUIElementCommand which goes through
SetUIElementRequestManager's drain-and-merge path, ModelCommand was
processed immediately — each rapid model update (e.g. dragging a map
widget) triggered an individual cell re-execution.

These changes puts `ModelCommand` on the same shared queue as
`UpdateUIElementCommand` so both go through the same batching pipeline.
When multiple model updates arrive in quick succession, they are now
drained and merged (last-write-wins per model ID on state keys),
matching the existing UI element behavior.

The handler for model messages also now enqueues the resulting
`UpdateUIElementCommand` back through the control queue instead of
calling `set_ui_element_value` directly, so the downstream cell
re-execution also benefits from batching.
manzt added a commit that referenced this pull request Feb 10, 2026
The anywidget refactors in #8156, #8159, and #8163 separated model
lifecycle from widget binding and moved to a dedicated ModelCommand for
frontend-to-backend communication.

However, unlike UpdateUIElementCommand which goes through
SetUIElementRequestManager's drain-and-merge path, ModelCommand was
processed immediately — each rapid model update (e.g. dragging a map
widget) triggered an individual cell re-execution.

These changes puts `ModelCommand` on the same shared queue as
`UpdateUIElementCommand` so both go through the same batching pipeline.
When multiple model updates arrive in quick succession, they are now
drained and merged (last-write-wins per model ID on state keys),
matching the existing UI element behavior.

The handler for model messages also now enqueues the resulting
`UpdateUIElementCommand` back through the control queue instead of
calling `set_ui_element_value` directly, so the downstream cell
re-execution also benefits from batching.
manzt added a commit that referenced this pull request Feb 10, 2026
The anywidget refactors in #8156, #8159, and #8163 separated model
lifecycle from widget binding and moved to a dedicated ModelCommand for
frontend-to-backend communication.

However, unlike UpdateUIElementCommand which goes through
SetUIElementRequestManager's drain-and-merge path, ModelCommand was
processed immediately — each rapid model update (e.g. dragging a map
widget) triggered an individual cell re-execution.

These changes puts `ModelCommand` on the same shared queue as
`UpdateUIElementCommand` so both go through the same batching pipeline.
When multiple model updates arrive in quick succession, they are now
drained and merged (last-write-wins per model ID on state keys),
matching the existing UI element behavior.

The handler for model messages also now enqueues the resulting
`UpdateUIElementCommand` back through the control queue instead of
calling `set_ui_element_value` directly, so the downstream cell
re-execution also benefits from batching.
manzt added a commit that referenced this pull request Feb 10, 2026
The anywidget refactors in #8156, #8159, and #8163 separated model
lifecycle from widget binding and moved to a dedicated ModelCommand for
frontend-to-backend communication.

However, unlike UpdateUIElementCommand which goes through
SetUIElementRequestManager's drain-and-merge path, ModelCommand was
processed immediately — each rapid model update (e.g. dragging a map
widget) triggered an individual cell re-execution.

These changes puts `ModelCommand` on the same shared queue as
`UpdateUIElementCommand` so both go through the same batching pipeline.
When multiple model updates arrive in quick succession, they are now
drained and merged (last-write-wins per model ID on state keys),
matching the existing UI element behavior.

The handler for model messages also now enqueues the resulting
`UpdateUIElementCommand` back through the control queue instead of
calling `set_ui_element_value` directly, so the downstream cell
re-execution also benefits from batching.
manzt added a commit that referenced this pull request Feb 10, 2026
The anywidget refactors in #8156, #8159, and #8163 separated model
lifecycle from widget binding and moved to a dedicated ModelCommand for
frontend-to-backend communication.

However, unlike UpdateUIElementCommand which goes through
SetUIElementRequestManager's drain-and-merge path, ModelCommand was
processed immediately — each rapid model update (e.g. dragging a map
widget) triggered an individual cell re-execution.

These changes puts `ModelCommand` on the same shared queue as
`UpdateUIElementCommand` so both go through the same batching pipeline.
When multiple model updates arrive in quick succession, they are now
drained and merged (last-write-wins per model ID on state keys),
matching the existing UI element behavior.

The handler for model messages also now enqueues the resulting
`UpdateUIElementCommand` back through the control queue instead of
calling `set_ui_element_value` directly, so the downstream cell
re-execution also benefits from batching.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants