Repository navigation
fix(session): Run flush callbacks registered during an output flush - #2587
Merged
Merged
Conversation
schloerke
marked this pull request as ready for review
October 27, 2026 19:17
schloerke
force-pushed
the
fix/flush-callback-registered-mid-flush
branch
from
October 27, 2026 19:24
81cc464 to
b8c5e3e
Compare
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.
Closes #2580. An alternative to #2581. A
session.on_flush()orsession.on_flushed()callback registered while the session's flush callbacks run (by one of them, or by a task that ran while anasyncone waited) waited for the next client input._request_output_flush()dropped every request made during theon_flushcallbacks, and during theon_flushedcallbacks it only sent a "tail" that never ran theon_flushedcallbacks 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_flushedcallback and then sends an input update), and running late callbacks inside the same flush, as #2581 does, madetest_test_server.pyfail on slower CI runners. A callback that requests a flush every time it runs (aonce=Falseon_flushedcallback that callsui.update_*(), or one that registers itself again) would now flush forever, so that repeat flush waitsREPEAT_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 eachui.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.pycover a callback registered by another callback and by a task during anasynccallback (both hooks),on_flushgoing out beforeon_flushed, a lateon_flushedcallback running after an input update that already arrived, and pacing. Seven of them fail onmain, and the input-update test fails on #2581's code.pytest tests/pytestpasses (1435 passed, 3 skipped), as douv run make format check-lint check-types check-pyrightand 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 (onmain: "first step ran").