diff --git a/libs/cua-driver/rust/crates/cua-driver/src/serve.rs b/libs/cua-driver/rust/crates/cua-driver/src/serve.rs index f8cd787cff..7efa76ec38 100644 --- a/libs/cua-driver/rust/crates/cua-driver/src/serve.rs +++ b/libs/cua-driver/rust/crates/cua-driver/src/serve.rs @@ -358,13 +358,16 @@ pub fn read_pid_file(pid_file_path: &str) -> Option { /// Uses a 3-second connect timeout (by polling) and a 10-second read timeout. #[cfg(unix)] pub fn send_request(socket_path: &str, req: &DaemonRequest) -> anyhow::Result { - use std::io::{BufRead, BufReader, Write}; + use std::io::{Read, Write}; use std::os::unix::net::UnixStream; - use std::time::Duration; + use std::time::{Duration, Instant}; - let stream = UnixStream::connect(socket_path) + let mut stream = UnixStream::connect(socket_path) .map_err(|e| anyhow::anyhow!("connect to {socket_path}: {e}"))?; stream.set_write_timeout(Some(Duration::from_secs(5)))?; + // Per-read timeout acts as a liveness poll, NOT a hard cap on the whole + // response: an AX-heavy `get_window_state` (slow tree walk) or a multi-MB + // SOM screenshot can legitimately take longer than one window to produce. stream.set_read_timeout(Some(Duration::from_secs(10)))?; let mut w = stream.try_clone()?; @@ -372,11 +375,50 @@ pub fn send_request(socket_path: &str, req: &DaemonRequest) -> anyhow::Result = Vec::with_capacity(64 * 1024); + let mut chunk = [0u8; 64 * 1024]; + let resp_line = loop { + if let Some(nl) = buf.iter().position(|&b| b == b'\n') { + break String::from_utf8_lossy(&buf[..nl]).into_owned(); + } + match stream.read(&mut chunk) { + Ok(0) => { + // EOF. Use whatever we buffered (some daemons close right after + // a final unterminated line); otherwise the daemon hung up. + if buf.is_empty() { + anyhow::bail!("daemon closed connection without response"); + } + break String::from_utf8_lossy(&buf).into_owned(); + } + Ok(n) => buf.extend_from_slice(&chunk[..n]), + Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue, + Err(e) + if matches!( + e.kind(), + std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut + ) => + { + if Instant::now() >= overall_deadline { + anyhow::bail!( + "timed out after 120s waiting for daemon response \ + (received {} bytes so far)", + buf.len() + ); + } + continue; + } + Err(e) => return Err(e.into()), + } + }; let resp: DaemonResponse = serde_json::from_str(&resp_line)?; Ok(resp) }