From 806aed4b2ac3c21371cc21b54b13c40e2936681f Mon Sep 17 00:00:00 2001
From: Aditya Kamath <Aditya.Kamath1@ibm.com>
Date: Wed, 20 May 2026 03:52:49 -0500
Subject: [PATCH 09/10] Make AIX using blocking IO for UV.

**Purpose**: Fix infinite hanging when building packages on AIX
**Why needed**: Tokio async I/O does not detect EOF on AIX pipes
**Solution**: Use blocking I/O with threads instead of async I/O on AIX
**Impact**: CRITICAL - Without this, uv install hangs on any package build
---
 crates/uv-build-frontend/src/lib.rs | 210 +++++++++++++++++++++-------
 1 file changed, 156 insertions(+), 54 deletions(-)

diff --git a/crates/uv-build-frontend/src/lib.rs b/crates/uv-build-frontend/src/lib.rs
index 5ad3497ed..7e2074d45 100644
--- a/crates/uv-build-frontend/src/lib.rs
+++ b/crates/uv-build-frontend/src/lib.rs
@@ -24,7 +24,6 @@ use serde::de::{self, IntoDeserializer, SeqAccess, Visitor, value};
 use serde::{Deserialize, Deserializer};
 use tempfile::TempDir;
 use tokio::io::AsyncBufReadExt;
-use tokio::process::Command;
 use tokio::sync::{Mutex, Semaphore};
 use tracing::{Instrument, debug, info_span, instrument, warn};
 use uv_auth::CredentialsCache;
@@ -1200,62 +1199,165 @@ impl PythonRunner {
 
         let _permit = self.concurrent_build_slots.acquire().await.unwrap();
 
-        let mut child = Command::new(venv.python_executable())
-            .args(["-c", script])
-            .current_dir(source_tree.simplified())
-            .envs(environment_variables)
-            .env(EnvVars::PATH, modified_path)
-            .env(EnvVars::VIRTUAL_ENV, venv.root())
-            // NOTE: it would be nice to get colored output from build backends,
-            // but setting CLICOLOR_FORCE=1 changes the output of underlying
-            // tools, which might mess with wrappers trying to parse their
-            // output.
-            .env(EnvVars::PYTHONIOENCODING, "utf-8:backslashreplace")
-            // Remove potentially-sensitive environment variables.
-            .env_remove(EnvVars::PYX_API_KEY)
-            .env_remove(EnvVars::UV_API_KEY)
-            .env_remove(EnvVars::PYX_AUTH_TOKEN)
-            .env_remove(EnvVars::UV_AUTH_TOKEN)
-            .stdout(std::process::Stdio::piped())
-            .stderr(std::process::Stdio::piped())
-            .spawn()
-            .map_err(|err| Error::CommandFailed(venv.python_executable().to_path_buf(), err))?;
-
-        // Create buffers to capture `stdout` and `stderr`.
-        let mut stdout_buf = Vec::with_capacity(1024);
-        let mut stderr_buf = Vec::with_capacity(1024);
-
-        // Create separate readers for `stdout` and `stderr`.
-        let stdout_reader = tokio::io::BufReader::new(child.stdout.take().unwrap()).split(b'\n');
-        let stderr_reader = tokio::io::BufReader::new(child.stderr.take().unwrap()).split(b'\n');
-
-        // Asynchronously read from the in-memory pipes.
-        let printer = Printer::from(self.level);
-        let result = tokio::join!(
-            read_from(stdout_reader, printer, &mut stdout_buf),
-            read_from(stderr_reader, printer, &mut stderr_buf),
-        );
-        match result {
-            (Ok(()), Ok(())) => {}
-            (Err(err), _) | (_, Err(err)) => {
-                return Err(Error::CommandFailed(
-                    venv.python_executable().to_path_buf(),
-                    err,
-                ));
-            }
+        // On AIX, use blocking I/O instead of async to avoid pipe EOF issues
+        #[cfg(target_os = "aix")]
+        {
+            use std::io::{BufRead, BufReader};
+            use std::process::{Command as StdCommand, Stdio};
+            
+            let python_exe = venv.python_executable().to_path_buf();
+            let source_tree_path = source_tree.simplified().to_path_buf();
+            let venv_root = venv.root().to_path_buf();
+            let script_owned = script.to_string();
+            let env_vars: Vec<(OsString, OsString)> = environment_variables.iter()
+                .map(|(k, v)| (k.clone(), v.clone()))
+                .collect();
+            let modified_path_owned = modified_path.clone();
+            let printer_level = self.level;
+            
+            // Run the command in a blocking task to avoid blocking the async runtime
+            let (stdout_buf, stderr_buf, status) = tokio::task::spawn_blocking(move || {
+                let mut command = StdCommand::new(&python_exe);
+                command
+                    .args(["-c", &script_owned])
+                    .current_dir(&source_tree_path)
+                    .envs(env_vars)
+                    .env(EnvVars::PATH, &modified_path_owned)
+                    .env(EnvVars::VIRTUAL_ENV, &venv_root)
+                    .env(EnvVars::PYTHONIOENCODING, "utf-8:backslashreplace")
+                    .env_remove(EnvVars::PYX_API_KEY)
+                    .env_remove(EnvVars::UV_API_KEY)
+                    .env_remove(EnvVars::PYX_AUTH_TOKEN)
+                    .env_remove(EnvVars::UV_AUTH_TOKEN)
+                    .stdout(Stdio::piped())
+                    .stderr(Stdio::piped());
+                
+                let mut child = command.spawn()
+                    .map_err(|err| Error::CommandFailed(python_exe.clone(), err))?;
+                
+                let stdout = child.stdout.take().unwrap();
+                let stderr = child.stderr.take().unwrap();
+                
+                let mut stdout_buf = Vec::with_capacity(1024);
+                let mut stderr_buf = Vec::with_capacity(1024);
+                
+                // Use blocking I/O to read from pipes
+                let stdout_handle = std::thread::spawn(move || {
+                    let mut lines = Vec::new();
+                    let reader = BufReader::new(stdout);
+                    let mut printer = Printer::from(printer_level);
+                    for line in reader.lines() {
+                        match line {
+                            Ok(line) => {
+                                let _ = writeln!(printer, "{}", line);
+                                lines.push(line);
+                            }
+                            Err(_) => break,
+                        }
+                    }
+                    lines
+                });
+                
+                let stderr_handle = std::thread::spawn(move || {
+                    let mut lines = Vec::new();
+                    let reader = BufReader::new(stderr);
+                    let mut printer = Printer::from(printer_level);
+                    for line in reader.lines() {
+                        match line {
+                            Ok(line) => {
+                                let _ = writeln!(printer, "{}", line);
+                                lines.push(line);
+                            }
+                            Err(_) => break,
+                        }
+                    }
+                    lines
+                });
+                
+                // Wait for both threads to finish reading
+                stdout_buf = stdout_handle.join().unwrap();
+                stderr_buf = stderr_handle.join().unwrap();
+                
+                // Wait for child process
+                let status = child.wait()
+                    .map_err(|err| Error::CommandFailed(python_exe, err))?;
+                
+                Ok::<_, Error>((stdout_buf, stderr_buf, status))
+            }).await
+            .map_err(|err| Error::CommandFailed(venv.python_executable().to_path_buf(),
+                io::Error::new(io::ErrorKind::Other, err.to_string())))??;
+            
+            return Ok(PythonRunnerOutput {
+                stdout: stdout_buf,
+                stderr: stderr_buf,
+                status,
+            });
         }
 
-        // Wait for the child process to finish.
-        let status = child
-            .wait()
-            .await
-            .map_err(|err| Error::CommandFailed(venv.python_executable().to_path_buf(), err))?;
+        #[cfg(not(target_os = "aix"))]
+        {
+            use tokio::process::Command;
+            
+            let mut command = Command::new(venv.python_executable());
+            command
+                .args(["-c", script])
+                .current_dir(source_tree.simplified())
+                .envs(environment_variables)
+                .env(EnvVars::PATH, modified_path)
+                .env(EnvVars::VIRTUAL_ENV, venv.root())
+                // NOTE: it would be nice to get colored output from build backends,
+                // but setting CLICOLOR_FORCE=1 changes the output of underlying
+                // tools, which might mess with wrappers trying to parse their
+                // output.
+                .env(EnvVars::PYTHONIOENCODING, "utf-8:backslashreplace")
+                // Remove potentially-sensitive environment variables.
+                .env_remove(EnvVars::PYX_API_KEY)
+                .env_remove(EnvVars::UV_API_KEY)
+                .env_remove(EnvVars::PYX_AUTH_TOKEN)
+                .env_remove(EnvVars::UV_AUTH_TOKEN)
+                .stdout(std::process::Stdio::piped())
+                .stderr(std::process::Stdio::piped());
+
+            let mut child = command
+                .spawn()
+                .map_err(|err| Error::CommandFailed(venv.python_executable().to_path_buf(), err))?;
+
+            // Create buffers to capture `stdout` and `stderr`.
+            let mut stdout_buf = Vec::with_capacity(1024);
+            let mut stderr_buf = Vec::with_capacity(1024);
+
+            // Create separate readers for `stdout` and `stderr`.
+            let stdout_reader = tokio::io::BufReader::new(child.stdout.take().unwrap()).split(b'\n');
+            let stderr_reader = tokio::io::BufReader::new(child.stderr.take().unwrap()).split(b'\n');
+
+            // Asynchronously read from the in-memory pipes.
+            let printer = Printer::from(self.level);
+            let result = tokio::join!(
+                read_from(stdout_reader, printer, &mut stdout_buf),
+                read_from(stderr_reader, printer, &mut stderr_buf),
+            );
+            
+            let status = child
+                .wait()
+                .await
+                .map_err(|err| Error::CommandFailed(venv.python_executable().to_path_buf(), err))?;
+            
+            match result {
+                (Ok(()), Ok(())) => {}
+                (Err(err), _) | (_, Err(err)) => {
+                    return Err(Error::CommandFailed(
+                        venv.python_executable().to_path_buf(),
+                        err,
+                    ));
+                }
+            }
 
-        Ok(PythonRunnerOutput {
-            stdout: stdout_buf,
-            stderr: stderr_buf,
-            status,
-        })
+            return Ok(PythonRunnerOutput {
+                stdout: stdout_buf,
+                stderr: stderr_buf,
+                status,
+            });
+        }
     }
 }
 
-- 
2.51.2

