Repository navigation
Conversation
jat255
added a commit
that referenced
this pull request
Oct 2, 2026
This was referenced Oct 2, 2026
jat255
added this pull request to stack #2516
October 2, 2026 19:19
jat255
added a commit
that referenced
this pull request
Oct 2, 2026
jat255
force-pushed
the
jat255/698-lock-flush-replacement
branch
from
October 2, 2026 19:22
1150d27 to
e0ff53b
Compare
Shiny no longer takes the lock, so holding it doesn't protect anything. `reactive.lock()` now warns and returns an object that never blocks. Setting a reactive value schedules a flush, so background tasks only need to call `.set()`; `session.run_once_when_idle()` applies a change once a session's effects have finished. Docs, the bundled skill and the CHANGELOG point to these instead of `lock()` + `flush()`.
`_NoOpLock` now subclasses `asyncio.Lock`, so the public signature and `isinstance` checks keep working. The warning says the function will be removed, and the docs say holders no longer exclude each other. The new background-task test no longer passes `context=` to `create_task()`, which needs Python 3.11. The skill's producer example starts its task in a fresh context and survives a failed fetch.
jat255
force-pushed
the
jat255/698-lock-flush-replacement
branch
from
October 3, 2026 14:40
e0ff53b to
56b49e6
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
After #2508, Shiny does not take
reactive.lock(), so the lock protects nothing. This PR deprecates it and documents what to do instead: set the reactive value directly.Stacked on #2517 (which is stacked on #2515 and #2508). It closes the #2508 TODO "What replaces
reactive.lock()+await reactive.flush()".Summary
reactive.lock()is deprecated (_core.py). Each call emits aShinyDeprecationWarning. The warning names the replacements and says that the function will be removed. The function returns anasyncio.Locksubclass that never blocks. The return type staysasyncio.Lock, so type checks andisinstance()continue to work.await reactive.flush()is not necessary after it. To apply a change only after a session's running effects finish, usesession.run_once_when_idle()(feat(session): Addsession.run_once_when_idle()#2517).reactive.flush()does not change. Its docstring now says that app code does not need it afterset(), and that it waits on the effects of all sessions.reactive.lockmoves to the "Deprecated" section of both quartodoc configs.await reactive.flush().reactivity.md) gets a section on how to set values from background tasks. Its example is a producer that all sessions share (the Design pattern for global async reactive #698 use case).Review Notes
reactive.lock()as a general mutex does not get mutual exclusion anymore. Shiny itself has not used the lock since feat: Run reactive effects #2508. We chose a no-op instead of a working lock that warns, because the lock does not protect reactive state. If it continued to exclude other holders, users could think that it still does. The CHANGELOG tells code that needs a mutex to create its ownasyncio.Lock.run_once_when_idle()). R has no equivalent. The only use I found for the wait was in examples that the old docs told users to write.lock()importswarn_deprecatedinside the function, becauseshiny/_deprecated.pyimportsshiny.reactive.genai-tools.qmduses the old pattern);_history.pydocstring, which relies on the old process-wide lock.Testing
test_concurrency.py:lock()warns on each call, and the warning points to the caller.asyncio.Lock, and an exception raised insideasync withpropagates._core.py. The background-task test also passes on the base branch, because feat: Run reactive effects #2508 made this behavior work. It protects the pattern that the docs now recommend.uv run make format check-lintand pyright are clean. pyrefly is clean when it is run with--use-ignore-files=false. (This worktree is in a git-excluded folder, so themake check-typestarget finds no files there.)pytest tests/pytestsuite passes: 1316 tests. I also ran the new tests on Python 3.10.test_value_set_in_download_handler_updates_outputs_and_effects[async](from feat: Run reactive effects #2508) fails in most runs of the fulltest_concurrency.pyfile. It passes when it runs alone. This PR does not cause it: the base branch failed 9 of 10 runs, and this branch failed 10 of 10.Refs #698