Skip to content

fix: a recreated module widget must invalidate the stale module - #82

Merged
maartenbreddels merged 1 commit into
masterfrom
fix/invalidate-module-on-recreate
Jul 28, 2026
Merged

fix: a recreated module widget must invalidate the stale module#82
maartenbreddels merged 1 commit into
masterfrom
fix/invalidate-module-on-recreate

Conversation

@maartenbreddels

Copy link
Copy Markdown
Contributor

The bug

Hot reload of an ES module can permanently stop reaching the browser: the page keeps rendering the previous version of the module, forever.

Module.initialize() calls addModule() but never invalidateModule() — only the change:code/change:url/change:dependencies handler does. A freshly created widget can carry a name that is already in the page-global registry: solara closes the per-kernel module widgets on a hot reload (a trait update on a closed widget never reaches the browser) and creates new ones. The registry then still holds the previous, already-resolved module, so a consumer rendered after the reload resolves that one instantly — and nothing re-renders it when the new module is provided a moment later.

It is a race between the new widget's async load and the consumer's requestModule: module first → correct new version; consumer first → stale version, permanently.

Evidence

Reproduced with solara's esm_test.py::test_ipyreact_module_hot_reload in a Linux container limited to 1 CPU (the race is essentially never lost on an unloaded machine, which is why this only shows on busy CI runners):

ipyreact result (3 runs, 150s budget each)
unpatched 2–3 runs stuck on the old version, ~157s each (never arrives)
this fix 3/3 correct version in 5.8–7.1s

The behaviour is bimodal — the new module arrives within seconds, or never — confirming it is a lost update rather than slowness.

The test

test_module_recreated_widget_hot_reload covers the recreate flow (render, close the module widget as context.restart() does, define again, render again). Note it needs CPU pressure to fail deterministically without the fix, so it documents the path rather than guarding it on a fast machine; the container comparison above is the real proof.

🤖 Generated with Claude Code

A fresh module widget can carry a name that is already in the page
global registry: solara closes the per-kernel module widgets on a hot
reload (a trait update on a closed widget never reaches the browser)
and creates new ones. Only change:code/url/dependencies invalidated the
registry, so consumers rendered after the reload could resolve the
previous module - permanently, since nothing re-renders them when the
new module is provided later. Whether that happened depended on which
of the two won the race, so hot reload silently kept serving the old
module on loaded machines.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@maartenbreddels
maartenbreddels force-pushed the fix/invalidate-module-on-recreate branch from 7e52c51 to c379e66 Compare July 28, 2026 15:58
@maartenbreddels
maartenbreddels merged commit 0962e16 into master Jul 28, 2026
12 checks passed
@maartenbreddels
maartenbreddels deleted the fix/invalidate-module-on-recreate branch July 28, 2026 16:02
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.

1 participant