From bf6f43f15b8bef6500e0c6404c5c60550d4db1a0 Mon Sep 17 00:00:00 2001 From: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Thu, 10 Sep 2026 09:09:14 +0800 Subject: [PATCH] fix(singleton): let a restart-spawned child take over the instance lock The secondary wait only polled the notify endpoint, which a restarting instance has already shut down before spawning its successor, so a child that lost the spawn-vs-exit race could never acquire the freed lock and died after 20s behind a startup-failure dialog. Retry try_lock_instance on every poll so the child promotes as soon as the old process exits. Double-launch handoff and wedged-holder timeout behavior are unchanged. --- src-tauri/src/utils/server.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src-tauri/src/utils/server.rs b/src-tauri/src/utils/server.rs index 983c8800a..91e9946b5 100644 --- a/src-tauri/src/utils/server.rs +++ b/src-tauri/src/utils/server.rs @@ -54,7 +54,16 @@ pub async fn check_singleton() -> Result { .context("failed to initialize singleton lock")?; if !try_lock_instance(&lock).context("failed to acquire singleton lock")? { let deadline = std::time::Instant::now() + Duration::from_secs(20); - while std::time::Instant::now() < deadline { + loop { + if std::time::Instant::now() >= deadline { + bail!("another app instance is starting"); + } + // A restarting instance releases this lock only after spawning this + // process, and its embedded server is already down, so waiting on + // the notify probe alone can never promote this instance. + if try_lock_instance(&lock).context("failed to acquire singleton lock")? { + break; + } if let Ok(record) = read_instance_record(&record_path) && notify_existing_instance(&record).await { @@ -62,7 +71,6 @@ pub async fn check_singleton() -> Result { } tokio::time::sleep(Duration::from_millis(50)).await; } - bail!("another app instance is starting"); } let preferred = read_instance_record(&record_path).ok().map(|record| record.port);