diff --git a/src/ghidra/bridge.rs b/src/ghidra/bridge.rs index 6373e71..3742154 100644 --- a/src/ghidra/bridge.rs +++ b/src/ghidra/bridge.rs @@ -134,8 +134,9 @@ pub fn is_bridge_running(project_path: &Path) -> Option { return None; } - // Verify TCP connect - TcpStream::connect(format!("127.0.0.1:{}", port)) + // Verify TCP connect (with timeout to avoid long hangs on Windows) + let addr: std::net::SocketAddr = format!("127.0.0.1:{}", port).parse().ok()?; + TcpStream::connect_timeout(&addr, Duration::from_secs(5)) .map(|_| Some(port)) .unwrap_or(None) } @@ -151,8 +152,10 @@ pub fn ensure_bridge_running( if let Ok(Some(port)) = read_port_file(project_path) { if let Ok(Some(pid)) = read_pid_file(project_path) { if is_pid_alive(pid) { - // Verify TCP connect - if TcpStream::connect(format!("127.0.0.1:{}", port)).is_ok() { + // Verify TCP connect (with timeout to avoid long hangs on Windows) + let addr: std::net::SocketAddr = + format!("127.0.0.1:{}", port).parse().unwrap(); + if TcpStream::connect_timeout(&addr, Duration::from_secs(5)).is_ok() { info!("Bridge already running on port {}", port); return Ok(port); } @@ -346,7 +349,7 @@ pub fn stop_bridge(project_path: &Path) -> Result<()> { #[cfg(windows)] { let _ = std::process::Command::new("taskkill") - .args(["/PID", &pid.to_string(), "/F"]) + .args(["/PID", &pid.to_string(), "/F", "/T"]) .output(); } } diff --git a/src/ipc/client.rs b/src/ipc/client.rs index 5ba4f4c..8c57bf5 100644 --- a/src/ipc/client.rs +++ b/src/ipc/client.rs @@ -36,9 +36,13 @@ impl BridgeClient { command: &str, args: Option, ) -> Result { - let mut stream = TcpStream::connect(format!("127.0.0.1:{}", self.port)).map_err(|e| { - anyhow::anyhow!("Failed to connect to bridge on port {}: {}", self.port, e) - })?; + let addr: std::net::SocketAddr = format!("127.0.0.1:{}", self.port) + .parse() + .map_err(|e| anyhow::anyhow!("Invalid address: {}", e))?; + let mut stream = + TcpStream::connect_timeout(&addr, Duration::from_secs(10)).map_err(|e| { + anyhow::anyhow!("Failed to connect to bridge on port {}: {}", self.port, e) + })?; stream.set_read_timeout(Some(Duration::from_secs(300))).ok(); stream.set_write_timeout(Some(Duration::from_secs(30))).ok(); diff --git a/src/lib.rs b/src/lib.rs index 64bf190..3c4bc22 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,3 +4,9 @@ #[path = "ipc/mod.rs"] pub mod ipc; + +/// Re-export bridge module for integration tests. +#[path = "ghidra"] +pub mod ghidra { + pub mod bridge; +} diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 579b4a4..26ffbf5 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -233,14 +233,33 @@ impl DaemonTestHarness { impl Drop for DaemonTestHarness { fn drop(&mut self) { - // Send shutdown command to bridge - let client = ghidra_cli::ipc::client::BridgeClient::new(self.port); - let _ = client.shutdown(); + // Read PID BEFORE shutdown (Java deletes PID file during shutdown) + let pid = ghidra_cli::ghidra::bridge::read_pid_file(&self.project_path) + .ok() + .flatten(); - // Wait for process to exit - std::thread::sleep(Duration::from_secs(2)); + // Use stop_bridge for proper graceful shutdown + force-kill + let _ = ghidra_cli::ghidra::bridge::stop_bridge(&self.project_path); - // Clean up + // Wait for process to fully exit and release project lock. + // Critical on Windows where JVM cleanup is slow. + if let Some(pid) = pid { + let max_wait = if cfg!(windows) { + Duration::from_secs(30) + } else { + Duration::from_secs(10) + }; + let start = std::time::Instant::now(); + while start.elapsed() < max_wait { + if !ghidra_cli::ghidra::bridge::is_pid_alive(pid) { + break; + } + std::thread::sleep(Duration::from_millis(500)); + } + } + + // Final cleanup of any remaining stale files + let _ = ghidra_cli::ghidra::bridge::cleanup_stale_files(&self.project_path); let _ = std::fs::remove_dir_all(&self.data_dir); } }