Repository navigation
Flush window title updates without waiting for a prompt - #1790
Merged
Merged
Conversation
# Conflicts: # CHANGELOG.md
…king the output Replace the locked output proxy from #1783 with a smaller change confined to set_window_title(). The title is written and flushed on main_session.output. When another thread calls it while a prompt or select() menu is running, the write is scheduled on that prompt's event loop so it doesn't interleave with rendering. A pending title is written when the prompt finishes in case the loop stops before running the callback. This drops the _LockedOutput proxy, the main_session property setter, and the forced per-Cmd output driver, restoring prompt-toolkit's dumb-terminal fallback, the ambient AppSession output when stdout is sys.stdout, and isinstance checks on the renderer's output.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1790 +/- ##
=======================================
Coverage 99.75% 99.75%
=======================================
Files 23 23
Lines 6503 6574 +71
=======================================
+ Hits 6487 6558 +71
Misses 16 16
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. |
The loop thread can write the title before the worker checks the stream, which happens on free-threaded Python. Only a call on the loop thread is guaranteed to have written it before returning.
- Read the select() AppSession and its app once in _get_prompt_loop(), since the main thread clears them when select() finishes. - Ignore output errors when a title is written on a prompt's event loop or during prompt and select() cleanup, so they can't disrupt the prompt or replace its result or exception. - Use a reentrant lock so a signal handler which sets the title while one is being written doesn't deadlock. - Write titles to a terminal stdout when stdin isn't a terminal and the main session uses a DummyOutput. - Document that a title set from another thread appears once the prompt's loop runs its next callback.
- When a signal handler sets the title while this thread is writing one, only record it. The outer call writes pending titles until none remain, so the latest title wins and writes never nest in the same stream. - Ignore ValueError from a closed stream as well as OSError when writing a title quietly. - Write a title left pending by a prompt after patch_stdout() exits, so it can't race with its writer thread. - Create the fallback terminal output the first time a title needs it, from the stdout captured when the app was created. This also covers a main_session replaced later by one with a DummyOutput, and titles set while output is redirected. - Share the list of terminal initialization errors between the main session and the title fallback.
- Treat a prompt as running while its application's loop is set, which covers prompt-toolkit's teardown after it clears is_running but can still write to the terminal. - Schedule the title on the prompt's loop even when called on that loop, so a signal handler can't nest a title write inside prompt-toolkit's own flush. - Check whether the fallback stdout is a terminal the first time a title needs it, so constructing a Cmd with a stdout that lacks isatty() or is closed works as it did before. - Describe the stdout fallback in the set_window_title() docstring and the prompt docs. - Run test workers through futures so their exceptions reach the test without a blind except and noqa.
…t fallback - While a title callback is queued on a prompt's loop, later titles only replace the pending title, so a burst of updates schedules one callback. The callback and prompt cleanup clear the queued loop. - Keep the stdout this app was created with in its own field, and track whether its fallback output has been checked with a flag instead of clearing that field. A cached_property doesn't work here, because inspect.getmembers() evaluates properties while a Cmd is constructed. - Describe all the cases where prompts use a dummy output, and note that a direct write from another thread isn't synchronized with other writers to the terminal.
On a Windows CI runner the default app session output is a Win32Output, which raises NoConsoleScreenBufferError because there's no console. Run the test inside create_app_session() like the other prompt tests.
Member
Author
|
@KennyMcSimpson @kmvanbrunt If either of you have time to review, I would appreciate it. At first I thought this would be simple ;-) But when I delved into it, there were a lot of edge cases. Fundamentally the reason it is complex is some of this should be fixed in |
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.
This continues #1783 by @KennyMcSimpson, whose commits are merged into this branch unchanged. The
investigation, the breaking change that makes
set_window_title()an instance method, the docs andexample cleanup, and most of the regression tests are Kenny's work. Thank you, Kenny!
Closes #1782. Supersedes #1783.
What changed on top of #1783
#1783 serialized title writes with rendering by wrapping each
Cmd's output driver in a locking proxyand always creating a per-
Cmddriver. Review found side effects in the rendering path:output=toPromptSessiondisabled prompt-toolkit'sTERM=dumbfallback.create_app_session(output=...)(for example prompt-toolkit's SSH/telnet servers)no longer used that session's output when
stdoutissys.stdout.isinstancechecks onrenderer.output, such as the Windows mouse handler's, failed on the proxy.This PR keeps the change inside
set_window_title():main_session.output, the same output the main prompt, custominput prompts, and
select()menus use.prompt's event loop with
call_soon_threadsafe(), so it never runs concurrently with the renderer.Calls on the loop thread, or with no prompt running, write directly.
_read_raw_input()andselect()writes the pending title, so it isn't lost._LockedOutput, themain_sessionproperty setter, and the forcedcreate_output()call are removed.Tests
select()menus, concurrent instances,interrupted setup,
DummyOutput, Win32, and replacingmain_session. Tests for worker threads nowcheck that the title is written on the prompt's event loop rather than before
set_window_title()returns.
AppSessionoutput, for a loop that stops before running the callback,for a closed loop, and for errors with and without a running prompt.
make check,make test(2816 passed), andmake docs-testpass locally.Follow-up review fixes
_get_prompt_loop()reads theselect()AppSession and its app once, since the main thread clearsthem when
select()finishes.select()cleanup, so they can't trigger prompt-toolkit's "Press ENTER to continue..." handler orreplace the prompt's result or exception. Errors still propagate when no prompt is running.
A nested call only records its title, and the outer call keeps writing until nothing is pending, so
the latest title wins and writes never nest in the same stream.
ValueErrorfrom a closed stream as well asOSError.patch_stdout()exits, so it can't race with itswriter thread.
the main session's
DummyOutput. The fallback output is created the first time a title needs it,from the stdout captured when the app was created, so titles still reach the terminal while a
command's output is redirected.
the prompt's loop runs its next callback.
Third review round
teardown, which clears
is_runningwhile its loop can still write to the terminal.set_window_title()iscalled on that loop. This stops a signal handler from nesting a title write inside prompt-toolkit's
own flush.
constructing a
Cmdwith a stdout that lacksisatty()or is closed works as before.set_window_title()docstring and the prompt docs describe the stdout fallback.exceptor# noqa: BLE001.select()is trackedin set_window_title(): find any running prompt-toolkit app on the terminal, not just two known ones #1792.
Fourth review round
so a burst of updates schedules one callback.
checked in a flag. (A
cached_propertydoesn't work here, becauseinspect.getmembers()evaluatesproperties while a
Cmdis constructed.)a direct write from another thread isn't synchronized with other writers to the terminal.
patch_stdout()'swriter thread during teardown, are tracked in set_window_title(): find any running prompt-toolkit app on the terminal, not just two known ones #1792 alongside the app-discovery redesign.