perf: lock stdout once for the whole report stream, not per chunk

print!/println! each acquire stdout's lock internally; doing that per
streamed chunk in a tight loop adds needless contention. Lock once up
front and write!/writeln! through the held handle instead — which also
means the trailing newline must go through that same handle rather than
println!, since re-locking from the same thread would deadlock.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Austin Schaefer 2026-08-17 12:20:35 +02:00
parent 469b7cf8c6
commit 4f32dd83d6

View file

@ -178,6 +178,11 @@ async fn write_report(
drop(spinner); drop(spinner);
let mut report = String::new(); let mut report = String::new();
// Locked once for the whole stream rather than per chunk (as print!
// would do internally) — chunks arrive in a tight loop, so re-acquiring
// the lock on every one adds up.
let stdout = std::io::stdout();
let mut handle = stdout.lock();
while let Some(chunk) = response_stream.next().await { while let Some(chunk) = response_stream.next().await {
match chunk? { match chunk? {
@ -189,13 +194,16 @@ async fn write_report(
// Terminal stdout is line-buffered, so a flush is needed here — // Terminal stdout is line-buffered, so a flush is needed here —
// otherwise a chunk without a trailing newline sits in the // otherwise a chunk without a trailing newline sits in the
// buffer instead of appearing as it streams in. // buffer instead of appearing as it streams in.
print!("{text}"); write!(handle, "{text}")?;
std::io::stdout().flush()?; handle.flush()?;
} }
_ => continue, _ => continue,
} }
} }
println!(); // `handle` still holds stdout's lock here, so this must go through it
// rather than `println!` — reacquiring the same lock from this thread
// would deadlock.
writeln!(handle)?;
Ok(report) Ok(report)
} }