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.
This commit is contained in:
Tunglies
2026-09-10 09:09:14 +08:00
parent f77f60ecd5
commit bf6f43f15b
+10 -2
View File
@@ -54,7 +54,16 @@ pub async fn check_singleton() -> Result<SingletonDisposition> {
.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<SingletonDisposition> {
}
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);