From 4f32dd83d6c5d3bc8c0bdcc748ba6cfb77816360 Mon Sep 17 00:00:00 2001 From: Austin Schaefer Date: Mon, 17 Aug 2026 12:20:35 +0200 Subject: [PATCH] perf: lock stdout once for the whole report stream, not per chunk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- deep_research/src/core.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/deep_research/src/core.rs b/deep_research/src/core.rs index fcfbc9d..e1e0dcb 100644 --- a/deep_research/src/core.rs +++ b/deep_research/src/core.rs @@ -178,6 +178,11 @@ async fn write_report( drop(spinner); 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 { match chunk? { @@ -189,13 +194,16 @@ async fn write_report( // Terminal stdout is line-buffered, so a flush is needed here — // otherwise a chunk without a trailing newline sits in the // buffer instead of appearing as it streams in. - print!("{text}"); - std::io::stdout().flush()?; + write!(handle, "{text}")?; + handle.flush()?; } _ => 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) }