From 19e636b706c1119e955f8f713469f6e48ba1be04 Mon Sep 17 00:00:00 2001 From: 0okay <1170488531@qq.com> Date: Tue, 3 Feb 2026 22:39:05 +0800 Subject: [PATCH 1/2] fix(daemon): improve Windows daemon reliability with dynamic ports This change fixes EADDRINUSE errors on Windows by using port 0 for the daemon and discovering the actual port via a .port file. --- cli/src/connection.rs | 56 ++++++++++++++++++++++++------------------- src/daemon.ts | 34 +++++++++++++++++++++----- 2 files changed, 60 insertions(+), 30 deletions(-) diff --git a/cli/src/connection.rs b/cli/src/connection.rs index d53efe02..0c513b61 100644 --- a/cli/src/connection.rs +++ b/cli/src/connection.rs @@ -140,14 +140,13 @@ fn get_port_path(session: &str) -> PathBuf { } #[cfg(windows)] -fn get_port_for_session(session: &str) -> u16 { - let mut hash: i32 = 0; - for c in session.chars() { - hash = ((hash << 5).wrapping_sub(hash)).wrapping_add(c as i32); - } - // Correct logic: first take absolute modulo, then cast to u16 - // Using unsigned_abs() to safely handle i32::MIN - 49152 + ((hash.unsigned_abs() as u32 % 16383) as u16) +fn get_port_for_session(session: &str) -> Option { + let port_path = get_port_path(session); + if let Ok(content) = fs::read_to_string(&port_path) { + content.trim().parse::().ok() + } else { + None + } } #[cfg(unix)] @@ -172,12 +171,15 @@ fn is_daemon_running(session: &str) -> bool { if !pid_path.exists() { return false; } - let port = get_port_for_session(session); - TcpStream::connect_timeout( - &format!("127.0.0.1:{}", port).parse().unwrap(), - Duration::from_millis(100), - ) - .is_ok() + if let Some(port) = get_port_for_session(session) { + TcpStream::connect_timeout( + &format!("127.0.0.1:{}", port).parse().unwrap(), + Duration::from_millis(100), + ) + .is_ok() + } else { + false + } } fn daemon_ready(session: &str) -> bool { @@ -188,12 +190,15 @@ fn daemon_ready(session: &str) -> bool { } #[cfg(windows)] { - let port = get_port_for_session(session); - TcpStream::connect_timeout( - &format!("127.0.0.1:{}", port).parse().unwrap(), - Duration::from_millis(50), - ) - .is_ok() + if let Some(port) = get_port_for_session(session) { + TcpStream::connect_timeout( + &format!("127.0.0.1:{}", port).parse().unwrap(), + Duration::from_millis(50), + ) + .is_ok() + } else { + false + } } } @@ -465,10 +470,13 @@ fn connect(session: &str) -> Result { } #[cfg(windows)] { - let port = get_port_for_session(session); - TcpStream::connect(format!("127.0.0.1:{}", port)) - .map(Connection::Tcp) - .map_err(|e| format!("Failed to connect: {}", e)) + if let Some(port) = get_port_for_session(session) { + TcpStream::connect(format!("127.0.0.1:{}", port)) + .map(Connection::Tcp) + .map_err(|e| format!("Failed to connect: {}", e)) + } else { + Err("Port file not found (daemon not running?)".to_string()) + } } } diff --git a/src/daemon.ts b/src/daemon.ts index ee4545f9..899c56f0 100644 --- a/src/daemon.ts +++ b/src/daemon.ts @@ -86,6 +86,14 @@ export function getSocketDir(): string { export function getSocketPath(session?: string): string { const sess = session ?? currentSession; if (isWindows) { + try { + const portFile = getPortFile(sess); + if (fs.existsSync(portFile)) { + return fs.readFileSync(portFile, 'utf8').trim(); + } + } catch { + // Ignore errors + } return String(getPortForSession(sess)); } return path.join(getSocketDir(), `${sess}.sock`); @@ -135,6 +143,17 @@ export function getConnectionInfo( ): { type: 'unix'; path: string } | { type: 'tcp'; port: number } { const sess = session ?? currentSession; if (isWindows) { + try { + const portFile = getPortFile(sess); + if (fs.existsSync(portFile)) { + const port = parseInt(fs.readFileSync(portFile, 'utf8').trim(), 10); + if (!isNaN(port)) { + return { type: 'tcp', port }; + } + } + } catch { + // Ignore errors + } return { type: 'tcp', port: getPortForSession(sess) }; } return { type: 'unix', path: path.join(getSocketDir(), `${sess}.sock`) }; @@ -370,12 +389,15 @@ export async function startDaemon(options?: { fs.writeFileSync(pidFile, process.pid.toString()); if (isWindows) { - // Windows: use TCP socket on localhost - const port = getPortForSession(currentSession); - const portFile = getPortFile(); - fs.writeFileSync(portFile, port.toString()); - server.listen(port, '127.0.0.1', () => { - // Daemon is ready on TCP port + // Windows: use TCP socket on localhost with random port + // We bind to 0 to let the OS assign a free port, avoiding collisions + server.listen(0, '127.0.0.1', () => { + const address = server.address(); + if (address && typeof address === 'object') { + const port = address.port; + const portFile = getPortFile(); + fs.writeFileSync(portFile, port.toString()); + } }); } else { // Unix: use Unix domain socket From 9b0e0b1870e48facbec1b25fae3a6b2db5f9cd4d Mon Sep 17 00:00:00 2001 From: 0okay <1170488531@qq.com> Date: Tue, 3 Feb 2026 23:41:11 +0800 Subject: [PATCH 2/2] fix(cli): increase connection timeout to 250ms for better robustness on Windows --- cli/src/connection.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/src/connection.rs b/cli/src/connection.rs index 0c513b61..ef63a858 100644 --- a/cli/src/connection.rs +++ b/cli/src/connection.rs @@ -174,7 +174,7 @@ fn is_daemon_running(session: &str) -> bool { if let Some(port) = get_port_for_session(session) { TcpStream::connect_timeout( &format!("127.0.0.1:{}", port).parse().unwrap(), - Duration::from_millis(100), + Duration::from_millis(250), ) .is_ok() } else { @@ -193,7 +193,7 @@ fn daemon_ready(session: &str) -> bool { if let Some(port) = get_port_for_session(session) { TcpStream::connect_timeout( &format!("127.0.0.1:{}", port).parse().unwrap(), - Duration::from_millis(50), + Duration::from_millis(250), ) .is_ok() } else {