Skip to content

Windows: cache the stdio write mode for the duration of a lock session - #159562

Open
Joel-Wwalker wants to merge 2 commits into
rust-lang:mainfrom
Joel-Wwalker:154071-stdio-handle-cache
Open

Windows: cache the stdio write mode for the duration of a lock session#159562
Joel-Wwalker wants to merge 2 commits into
rust-lang:mainfrom
Joel-Wwalker:154071-stdio-handle-cache

Conversation

@Joel-Wwalker

@Joel-Wwalker Joel-Wwalker commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

On Windows, every write to stdout/stderr calls GetStdHandle and GetConsoleMode (and GetConsoleOutputCP for consoles) to decide how to write. For bulk writes through a locked handle that is most of the work; #154071 measured 14% of CPU time in is_console alone.

This caches the handle and the console verdict in the sys-level Stdout/Stderr and re-queries when a new lock session begins, as suggested in the issue. Holding a StdoutLock pins the stream; unlocked writes acquire the lock per call, so they re-query per write exactly as before, and SetStdHandle calls made between lock sessions keep working (the #40490 behavior is preserved).

Numbers from the issue's workload shape (16 KiB chunks to NUL, 4 GiB total, Windows 11):

mode before after
stdout().lock() 0.478 s (8,561 MiB/s) 0.120 s (34,350 MiB/s)
unlocked 0.467 s 0.293 s

The unlocked case also improves because one implicit lock session can contain several sys-level writes when LineWriter splits a chunk at newlines.

The console path cannot run in CI, so it was verified under a pseudoconsole: several writes through one lock session on a CP437 console came through WriteConsoleW intact (non-ASCII included), and after SetConsoleOutputCP plus a new lock session the refresh picked up the change.

The refresh hook is a small #[cfg(windows)] shim on StdoutRaw/StderrRaw; happy to change it to a method on every platform's sys type instead if that is preferred.

Fixes #154071

Every write to stdout/stderr re-queried GetStdHandle and GetConsoleMode
(and GetConsoleOutputCP for consoles) to decide how to write, which
dominates bulk writes through a locked handle. Cache the handle and the
console verdict in the sys-level Stdout/Stderr and re-query when a new
lock session begins, so SetStdHandle calls made between lock sessions
keep working.
@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Jul 19, 2026
@rustbot

rustbot commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

r? @aapoalas

rustbot has assigned @aapoalas.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: @ChrisDenton, libs
  • @ChrisDenton, libs expanded to 13 candidates
  • Random selection from 7 candidates

Comment thread library/std/src/io/stdio.rs Outdated
// stream state at the next write. Skipped if the cell is already
// borrowed (a nested lock during an ongoing write); such a lock is
// part of the outer session anyway.
if let Ok(mut w) = lock.inner.try_borrow_mut() {

@aapoalas aapoalas Jul 23, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue: Does the inner try-borrow-mut optimise out on Linux and other targets where the refresh is a no-op?

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Probably, since after inlining the whole thing is just borrow-flag manipulation with no observable effect, but I did not want to rely on that. Gated the block with #[cfg(windows)] at the call site instead (and made the refresh impls windows-only), so on other targets lock() compiles to exactly the pre-PR code. Checked the change against x86_64-unknown-linux-gnu as well as Windows.

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jul 23, 2026
@rustbot

rustbot commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Reminder, once the PR becomes ready for a review, use @rustbot ready.

On targets where refresh is a no-op, lock() now compiles to exactly
the code it had before, instead of relying on the borrow-flag dance
optimizing out.
@Joel-Wwalker

Copy link
Copy Markdown
Contributor Author

@rustbot ready

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Jul 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stdout and StdoutLock make unnecessary repeated syscalls via is_console on Windows

3 participants