Skip to content

Keep direct window title errors with their caller - #1793

Merged
tleonhardt merged 1 commit into
python-cmd2:fix/window-title-flush-v2from
KennyMcSimpson:fix/window-title-error-ownership
Oct 29, 2026
Merged

tleonhardt merged 1 commit into
python-cmd2:fix/window-title-flush-v2from
KennyMcSimpson:fix/window-title-error-ownership

Conversation

@KennyMcSimpson

Copy link
Copy Markdown
Contributor

When no prompt is running, set_window_title() releases its lock after storing the pending title but before flushing it. Concurrent prompt cleanup can consume that request and suppress its OSError or ValueError, so the synchronous caller returns successfully even though its write failed.

Keep the existing reentrant lock through the direct flush, including the closed-loop fallback, so the caller retains its request and receives its output error. The existing reentrant-write guard still handles signal handlers.

This is a single-commit follow-up to #1790, targeting fix/window-title-flush-v2 at e72ceb558.

The deterministic regression pauses the direct caller before flushing and runs concurrent quiet cleanup. All eight cases fail on the unchanged #1790 implementation and pass with this fix, covering absent/closed loops, both output operations, and both exception types. It also checks which thread receives the failure and that pending/write state is cleared.

Validation on Windows:

  • Full suite, Python 3.13: 2,682 passed, 159 skipped.
  • Window-title suite, Python 3.14 free-threaded with the GIL disabled: 43 passed.
  • Make check equivalents passed: uv lock --locked, prek run -a, ty check, and mypy ..
  • Strict documentation build: zensical build -s passed.

@codecov

codecov Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.75%. Comparing base (e72ceb5) to head (521e045).

Additional details and impacted files
@@                    Coverage Diff                     @@
##           fix/window-title-flush-v2    #1793   +/-   ##
==========================================================
  Coverage                      99.75%   99.75%           
==========================================================
  Files                             23       23           
  Lines                           6574     6574           
==========================================================
  Hits                            6558     6558           
  Misses                            16       16           
Flag Coverage Δ
unittests 99.75% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@tleonhardt tleonhardt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for catching and fixing this edge case!!

@tleonhardt
tleonhardt merged commit 4472a0a into python-cmd2:fix/window-title-flush-v2 Oct 29, 2026
30 checks passed
tleonhardt added a commit that referenced this pull request Oct 30, 2026
* Flush window title updates on the prompt event loop

* Fix window title routing during select

* Explain select session traversal for title updates

* Route synchronous title updates through each Cmd output

* Write window titles on the running prompt's event loop instead of locking 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.

* Don't assert when a worker's title becomes visible during a prompt

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.

* Harden window title writes found in review

- 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.

* Fix window title edge cases found in second review

- 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.

* Schedule every title on a running prompt's loop and check stdout lazily

- 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.

* Coalesce queued title callbacks and clarify when titles use the stdout 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.

* Give the prompt cleanup test a VT100 output for patch_stdout()

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.

* Keep synchronous window title errors with their caller (#1793)

---------

Co-authored-by: KennyMcSimpson <70093673+KennyMcSimpson@users.noreply.github.com>
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