Skip to content

Do not hold a global lock while a widget is constructed - #72

Merged
maartenbreddels merged 1 commit into
masterfrom
fix/create-lock-per-thread
Sep 30, 2026
Merged

maartenbreddels merged 1 commit into
masterfrom
fix/create-lock-per-thread

Conversation

@maartenbreddels

Copy link
Copy Markdown
Contributor

Summary

Element.create_lock is removed: no lock is held while a widget is constructed anymore.
The recording of side-effect widgets is per thread instead.

Why

The lock was one lock for the whole process, held around widget.__init__.
Constructing a widget opens a comm, and in a server (solara) that is a websocket send, which can wait for a client that stopped reading, for example a laptop that went to sleep.
One such client then blocked widget creation for every other user of the server, until TCP gave up.

The lock was added in 2022 (efe7f61, "make thread save") because orphans were detected with a before/after diff of the global widgets dict: "Because we look before and after, we need a lock. A different implementation might avoid this."
The construction hook (20efc14) replaced the diff, but kept the recording in one module level list, so the lock stayed.
With the recording per thread (a typed threading.local), each thread only sees its own constructions, and the lock has nothing left to protect.

It also fixes a bug: a widget that another thread created while a render constructed a widget was recorded as that widget's side effect, and closed together with it.

Tests

In reacton/core_test.py:

  • a construction that blocks on one thread does not block widget creation on another thread, and side-effect widgets are still attributed to their own render context;
  • a widget created by another thread during a construction is not an orphan;
  • nested recordings: the inner one collects its own widgets, and the outer one continues afterwards.

The first two fail on master, the third tests the new helper.

🤖 Generated with Claude Code

Element.create_lock was one lock for the whole process, held while a widget
is constructed. Construction opens a comm, and in a server that is a send to
the browser, which can wait for a client that stopped reading (a laptop
that went to sleep). One such client then blocked widget creation for every
other user of the server, for as long as TCP took to give up.

The lock only existed to keep the recording of side-effect widgets (the
widget's Layout and Style, closed together with the widget) apart between
threads: first a before/after diff of the global widgets dict, since the
construction hook a module level list. Recording per thread keeps them apart
without a lock, so the lock goes.

This also fixes a widget that another thread created during a construction
being recorded as a side effect of the constructed widget, and closed with it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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