fix(desktop): auto-cleanup stale server processes on startup (#711)

When updating OpenChamber, stale openchamber-server processes from previous
versions can prevent the new version from starting. The app shows a loading
screen indefinitely with no error message.

This change adds a kill_stale_sidecar_processes() function that terminates
any existing openchamber-server processes before spawning a new one, ensuring
a clean startup every time.

- macOS/Linux: uses pkill -x for exact process name match
- Windows: uses taskkill /F /IM

Fixes startup issues after version updates.
This commit is contained in:
jwcrystal
2026-03-20 13:39:29 +02:00
committed by GitHub
parent bf61ccedc7
commit fd31a8cc2d
+50
View File
@@ -1799,7 +1799,57 @@ fn build_local_url(port: u16) -> String {
format!("http://127.0.0.1:{port}")
}
/// Kills any stale openchamber-server processes that may be lingering from
/// previous app sessions or incomplete shutdowns. This ensures a clean
/// startup and prevents port conflicts.
fn kill_stale_sidecar_processes() {
let process_name = if cfg!(windows) {
"openchamber-server.exe"
} else {
"openchamber-server"
};
let result = if cfg!(target_os = "macos") {
// macOS: use pkill to terminate by process name
std::process::Command::new("pkill")
.arg("-x") // exact match
.arg(process_name)
.output()
} else if cfg!(target_os = "linux") {
// Linux: use pkill
std::process::Command::new("pkill")
.arg("-x")
.arg(process_name)
.output()
} else if cfg!(windows) {
// Windows: use taskkill
std::process::Command::new("taskkill")
.arg("/F")
.arg("/IM")
.arg(process_name)
.output()
} else {
return;
};
// Log result for debugging (pkill returns 1 if no processes found, which is fine)
if let Ok(output) = result {
log::debug!(
"[sidecar] cleanup result: exit_code={:?}, stdout={}, stderr={}",
output.status.code(),
String::from_utf8_lossy(&output.stdout).trim(),
String::from_utf8_lossy(&output.stderr).trim()
);
}
// Brief pause to let the OS clean up the processes
std::thread::sleep(Duration::from_millis(100));
}
async fn spawn_local_server(app: &tauri::AppHandle) -> Result<String> {
// Clean up any stale sidecar processes from previous sessions
kill_stale_sidecar_processes();
let stored_port = read_desktop_local_port_from_disk();
let mut candidates: Vec<Option<u16>> = Vec::new();
if let Some(port) = stored_port {