Skip to content

fix(session): Run flush callbacks registered during an output flush - #2587

Merged
schloerke merged 1 commit into
mainfrom
fix/flush-callback-registered-mid-flush
Oct 28, 2026
Merged

schloerke merged 1 commit into
mainfrom
fix/flush-callback-registered-mid-flush

Conversation

@schloerke

Copy link
Copy Markdown
Collaborator

Closes #2580. An alternative to #2581. A session.on_flush() or session.on_flushed() callback registered while the session's flush callbacks run (by one of them, or by a task that ran while an async one waited) waited for the next client input. _request_output_flush() dropped every request made during the on_flush callbacks, and during the on_flushed callbacks it only sent a "tail" that never ran the on_flushed callbacks again. A callback that missed the list being run had nothing left to run it.

Now any output flush requested while those callbacks run gets one more full output flush after the current one. The early returns and the tail are gone, so the session code gets smaller. That flush starts after a reactive round, so a client message that already arrived is handled first; the test server depends on this (it registers an on_flushed callback and then sends an input update), and running late callbacks inside the same flush, as #2581 does, made test_test_server.py fail on slower CI runners. A callback that requests a flush every time it runs (a once=False on_flushed callback that calls ui.update_*(), or one that registers itself again) would now flush forever, so that repeat flush waits REPEAT_OUTPUT_FLUSH_DELAY_SECS (10 ms). Trade-offs to review: such a callback repeats up to 100 times a second instead of stopping, reactive.flush() does not return while it does, and each ui.update_*() causes one extra, empty output flush 10 ms later. The four "does not loop" tests became "repeats paced" tests. Dropping the delay is an option if fast loops are acceptable.

Verification: new tests in tests/pytest/test_concurrency.py cover a callback registered by another callback and by a task during an async callback (both hooks), on_flush going out before on_flushed, a late on_flushed callback running after an input update that already arrived, and pacing. Seven of them fail on main, and the input-update test fails on #2581's code. pytest tests/pytest passes (1435 passed, 3 skipped), as do uv run make format check-lint check-types check-pyright and 26 Playwright tests (session, accordion, offcanvas, sidebar, async, server, bugs/2207). In a browser, the issue's app shows "second step ran" after one click (on main: "first step ran").

@schloerke
schloerke marked this pull request as ready for review October 27, 2026 19:17
@schloerke
schloerke requested a review from cpsievert October 27, 2026 19:17
@schloerke
schloerke force-pushed the fix/flush-callback-registered-mid-flush branch from 81cc464 to b8c5e3e Compare October 27, 2026 19:24

@cpsievert cpsievert left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@schloerke
schloerke merged commit f73b8fe into main Oct 28, 2026
176 checks passed
@schloerke
schloerke deleted the fix/flush-callback-registered-mid-flush branch October 28, 2026 03:38
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.

A session.on_flush()/on_flushed() callback registered during the session's output flush waits for the next client input

2 participants