From 4b1609e021da2cdc33b953e154b4b79016b2e481 Mon Sep 17 00:00:00 2001 From: Prince Karikari Date: Fri, 25 Sep 2026 12:56:23 -0500 Subject: [PATCH] Issue #180 solved: surface IPC socket conflicts instead of failing silently ipc_connect created the listener inside the thread it spawned, so a bind failure could not reach the caller. The AddrInUse arm returned Ok(()), reporting success having bound nothing, and ipc_thread_handle was set regardless. The is_some() guard then rejected every later Connect IPC press for the life of the process. Since ipc_connect runs at startup, the relay booted into this state whenever baton.sock was occupied, with ":( No Baton Connection" as the only symptom. Bind the listener on the calling thread instead. create_sync() and set_nonblocking() now return Err, which the existing handlers in main.rs and update.rs already report, and the thread handle is only assigned once a bound listener exists, so retry works after the socket is freed. --- relay/src/state.rs | 67 ++++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/relay/src/state.rs b/relay/src/state.rs index 6d7b8ee..21c45ee 100644 --- a/relay/src/state.rs +++ b/relay/src/state.rs @@ -361,44 +361,41 @@ impl State { if self.ipc_thread_handle.is_some() { bail!("IPC thread already exists.") } + // Bind the listener on the calling thread. Doing it inside the spawned + // thread meant a bind failure had no way back to the caller: the thread + // reported `Ok(())`, `ipc_thread_handle` was set for a thread that owned + // no listener, and the guard above then rejected every later retry. + let printname = "baton.sock"; + let name = if GenericNamespaced::is_supported() { + printname.to_ns_name::()? + } else { + let mut path = std::env::temp_dir(); + path.push(printname); + path.to_fs_name::()? + }; + + println!("[RELAY] listening on socket: {:?}", name.borrow()); + + let listener = ListenerOptions::new() + .name(name) + .create_sync() + .map_err(|e| match e.kind() { + std::io::ErrorKind::AddrInUse => anyhow!( + "{} is already in use. Another relay instance may still be running.", + printname + ), + _ => anyhow!("Failed to create listener: {}", e), + })?; + + listener + .set_nonblocking(interprocess::local_socket::ListenerNonblockingMode::Both) + .map_err(|e| anyhow!("Error setting non-blocking mode on listener: {}", e))?; + + human_log("IPC", &format!("Server running at {}", printname)); + let (ipc_bichannel, mut child_bichannel) = bichannel::create_bichannels::(); let ipc_thread_handle = spawn(move || { - let printname = "baton.sock"; - let name = if GenericNamespaced::is_supported() { - printname.to_ns_name::().unwrap() - } else { - let mut path = std::env::temp_dir(); - path.push(printname); - path.to_fs_name::().unwrap() - }; - - println!("[RELAY] listening on socket: {:?}", name.borrow()); - - let opts = ListenerOptions::new().name(name.clone()); - let listener = match opts.create_sync() { - Err(e) if e.kind() == std::io::ErrorKind::AddrInUse => { - human_log("IPC", &format!( - "Could not start server because the socket file is occupied. Check if {} is in use.", - printname - )); - return Ok(()); - } - Ok(l) => { - println!("✓ Successfully created named pipe listener"); - l - } - Err(e) => { - eprintln!("✗ Failed to create listener: {} (kind: {:?})", e, e.kind()); - return Err(anyhow!("Failed to create listener: {}", e)); - } - }; - - listener - .set_nonblocking(interprocess::local_socket::ListenerNonblockingMode::Both) - .expect("Error setting non-blocking mode on listener"); - - human_log("IPC", &format!("Server running at {}", printname)); let mut buffer = String::with_capacity(256); while !child_bichannel.is_killswitch_engaged() {