From 70189887fdd149f9e57a95a4fd744761bf4a9b90 Mon Sep 17 00:00:00 2001 From: Andrew Kline Date: Wed, 4 Mar 2026 21:14:09 -0800 Subject: [PATCH] fix: drain stdout pipe to prevent project listing deadlock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The list_gcloud_projects() function piped stdout from `gcloud projects list` but only read it after the child process exited. When a user has enough GCP projects that the output exceeds the OS pipe buffer (~64KB), gcloud blocks on write, the parent blocks waiting for exit, and neither side makes progress — hitting the 10s timeout. Spawn a thread to drain stdout concurrently so the pipe buffer never fills up while the main thread polls for process completion. Fixes googleworkspace/cli#96 --- .changeset/fix-project-listing-deadlock.md | 11 +++++++++++ src/setup.rs | 20 +++++++++++--------- 2 files changed, 22 insertions(+), 9 deletions(-) create mode 100644 .changeset/fix-project-listing-deadlock.md diff --git a/.changeset/fix-project-listing-deadlock.md b/.changeset/fix-project-listing-deadlock.md new file mode 100644 index 000000000..e0768a67e --- /dev/null +++ b/.changeset/fix-project-listing-deadlock.md @@ -0,0 +1,11 @@ +--- +"@googleworkspace/cli": patch +--- + +fix: drain stdout pipe to prevent project listing timeout during auth setup + +Fixed `gws auth setup` timing out at step 3 (GCP project selection) for users +with many projects. The `gcloud projects list` stdout pipe was only read after +the child process exited, causing a deadlock when output exceeded the OS pipe +buffer (~64 KB). Stdout is now drained in a background thread to prevent the +pipe from filling up. diff --git a/src/setup.rs b/src/setup.rs index b5757fa7c..26d3d8194 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -545,6 +545,16 @@ fn list_gcloud_projects() -> (Vec<(String, String)>, Option) { Err(e) => return (Vec::new(), Some(format!("Failed to run gcloud: {e}"))), }; + // Drain stdout in a background thread to prevent pipe buffer deadlock. + // Without this, gcloud blocks once the OS pipe buffer (~64 KB) fills up, + // and the parent blocks waiting for gcloud to exit — a classic deadlock. + let stdout = child.stdout.take().expect("stdout was piped"); + let reader_handle = std::thread::spawn(move || { + let mut buf = String::new(); + std::io::Read::read_to_string(&mut { stdout }, &mut buf).ok(); + buf + }); + // Wait with timeout let timeout = std::time::Duration::from_secs(10); let start = std::time::Instant::now(); @@ -552,15 +562,7 @@ fn list_gcloud_projects() -> (Vec<(String, String)>, Option) { match child.try_wait() { Ok(Some(status)) => { if status.success() { - let stdout = child - .stdout - .take() - .map(|mut s| { - let mut buf = String::new(); - std::io::Read::read_to_string(&mut s, &mut buf).ok(); - buf - }) - .unwrap_or_default(); + let stdout = reader_handle.join().unwrap_or_default(); let projects = stdout .lines() .filter_map(|line| {