Skip to content

refactor(reactive): Give each meaning of "flush" its own name - #2534

Merged
jat255 merged 3 commits into
schloerke/async-pr-2182-reimplfrom
jat255/2508-flush-vocabulary
Oct 8, 2026
Merged

jat255 merged 3 commits into
schloerke/async-pr-2182-reimplfrom
jat255/2508-flush-vocabulary

Conversation

@jat255

@jat255 jat255 commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

In #2508, the word "flush" names four different things, so the code and its comments are hard to read. This PR gives each meaning its own name and defines the names in one place.

This PR is stacked on #2508 and targets its branch. It changes names, comments, and docstrings only. It does not change behavior.

Motivation

On main, one flush did all the work in sequence: it ran every effect to completion, then sent each session's outputs. One word was enough for that.

#2508 splits that work into parts that run separately, but all of the parts kept the name "flush". The same word now means four things:

  1. One drain of the queue of invalidated effects (ReactiveEnvironment.flush()). It starts each effect in a task and does not wait for it.
  2. The machinery that schedules those drains (request_flush(), _rerun_flush, _in_flush).
  3. A session that sends its outputs to the client (AppSession._flush(), _request_flush()). This happens later, once per session.
  4. The public reactive.flush(). It waits until all of the above is complete, so its contract is the opposite of ReactiveEnvironment.flush().

As a result, a reader cannot tell which meaning a comment or method name refers to. For example, flush_pass() and flush_settled() look similar, but one waits for a single drain and the other waits until all work stops. Also, AppSession._request_flush() and ReactiveEnvironment.request_flush() have almost the same name, but they request different things.

The new vocabulary

The ReactiveEnvironment docstring defines these terms. All comments and private names now use them.

Term Meaning Scope
Reactive effect queue The single priority queue of effect contexts that wait to run. Invalidating an effect adds it to this queue. Global
Round One drain of the reactive effect queue, followed by the round-finished callbacks. A round starts each effect in its own task. It does not wait for the async part of any effect. Global
Idle The reactive effect queue is empty, and no round, effect, or output flush task is running. reactive.flush() runs rounds until idle. Global
Cycle One action (for example, an input change), the effects that the action starts, and the output flush that ends the cycle. #2508 already uses "cycle" this way. Per session
Output flush The session sends its outputs to the client, after all of its effects are complete. Per session

These terms nest. One cycle can span several rounds, because effects can invalidate other effects. One round can advance the cycles of several sessions.

The word "flush" alone now means only the public API: reactive.flush(), reactive.on_flushed(), session.on_flush(), and session.on_flushed().

Renamed private names

Before After
ReactiveEnvironment.flush() start_round()
flush_pass() run_next_round()
flush_settled() run_until_idle()
request_flush(), _start_requested_flush() request_round(), _start_requested_round()
_in_flush, _rerun_flush, _flush_requested, _flush_pass_waiters _round_running, _rerun_round, _round_requested, _next_round_waiters
on_flushed(), _flushed_callbacks (environment) on_round_finished(), _round_finished_callbacks
_pending_flush_queue, add_pending_flush(ctx, ...) (environment) _effect_queue, enqueue_effect(ctx, ...)
_flush_owner _enclosing_run
App._request_flush(), _sessions_needing_flush, _flush_pending_sessions(), _unregister_flush_sessions _request_output_flush(), _sessions_needing_output_flush, _start_output_flushes(), _unregister_output_flush_hook
AppSession._request_flush(), _start_flush(), _run_flush(), _flush() _request_output_flush(), _start_output_flush(), _run_output_flush(), _output_flush()
AppSession._flush_task, _flush_again, _flush_enabled, _disable_flush() _output_flush_task, _output_flush_again, _output_flush_enabled, _disable_output_flush()

Review notes

  • "Round" instead of "pass". "Pass" already has a meaning in Python, so we did not use it. We also rejected "tick" and "drain", because asyncio uses both for other things.
  • Method verbs say what each method does. start_round() starts a round and does not wait for the effects. If a round is already running, it returns right away. run_next_round() and run_until_idle() start rounds themselves, so their names use "run" and not "wait". run_until_idle() follows asyncio's loop.run_until_complete().
  • Public names do not change. No public name changes, and no public behavior changes. The docstrings of reactive.flush() and reactive.on_flushed() now say what they wait for and when they run.
  • Context methods keep their names. add_pending_flush(), on_flush(), and execute_flush_callbacks() stay as they are, because get_current_context() gives user code access to Context. Their docstrings now use the new terms.
  • Left out on purpose. The OTel docs and the bookmark comments still say "flush cycle". Later commits in this stack rewrite those areas (74ca1306 for per-session reactive_update spans, and a45110cb for the bookmark restore order). Changing the text here only adds rebase conflicts. Test names that refer to the public reactive.flush() also keep their names.
  • Downstream use. A GitHub code search of posit-dev repositories found no other package that uses the renamed private names.
  • architecture.md now points to the glossary.

Testing

  • black, isort, flake8, pyright, and pyrefly report no errors.
  • All 1294 unit tests pass.
  • The renames change no logic, so this PR adds no new tests. Tests that use the renamed private names now use the new names.

Refs #2508

…t flushes

"Flush" referred to one drain of the reactive environment's queue, the machinery
that schedules those drains, a session sending its outputs, and the public
`reactive.flush()` that waits for all of it. Give each its own name, and define
them in the `ReactiveEnvironment` docstring:

- round: one drain of the reactive effect queue (`run_round()`,
  `wait_for_next_round()`, `request_round()`, `on_round_finished()`)
- reactive effect queue: the process-wide queue a round drains (`_effect_queue`,
  `enqueue_effect()`)
- idle: what `reactive.flush()` waits for (`wait_for_idle()`)
- output flush: a session sending its outputs (`_request_output_flush()`,
  `_start_output_flush()`, ...)

Public names (`reactive.flush()`, `reactive.on_flushed()`, `session.on_flush()`,
`session.on_flushed()`) are unchanged. `Context`'s methods keep their names
because `get_current_context()` exposes `Context` to user code.
@jat255
jat255 added this pull request to stack #2535 October 8, 2026 15:18
Comment thread shiny/reactive/_core.py Outdated
`wait_for_idle()` and `wait_for_next_round()` both start rounds themselves, so
"wait" undersold them. Rename them to `run_until_idle()` (after asyncio's
`run_until_complete()`) and `run_next_round()`. Rename `run_round()` to
`start_round()`, since it returns right away when a round is already running and
never waits for an effect's async part.
@jat255
jat255 merged commit 38d74df into main Oct 8, 2026
176 checks passed
@jat255
jat255 deleted the jat255/2508-flush-vocabulary branch October 8, 2026 15:55
@jat255 jat255 mentioned this pull request Oct 8, 2026
14 of 19 tasks
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