From 3bc418acc81155729202f72b922e1c8dc8a3afd6 Mon Sep 17 00:00:00 2001 From: sfasano Date: Wed, 5 Aug 2026 13:34:23 -0400 Subject: [PATCH 1/4] Fix multiple instances remaining after update --- Sources/Fluid/Services/SimpleUpdater.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Fluid/Services/SimpleUpdater.swift b/Sources/Fluid/Services/SimpleUpdater.swift index bc49f8a9..f153d66c 100644 --- a/Sources/Fluid/Services/SimpleUpdater.swift +++ b/Sources/Fluid/Services/SimpleUpdater.swift @@ -918,7 +918,7 @@ final class SimpleUpdater { DebugLogger.shared.info("SimpleUpdater: Successfully relaunched app, terminating old instance", source: "SimpleUpdater") // Give the new instance time to fully start before terminating DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { - NSApp.terminate(nil) + exit(0) } } } From 0d3c01a0b065eb48be4ecaa00946a6a5e0c6939f Mon Sep 17 00:00:00 2001 From: sfasano Date: Wed, 5 Aug 2026 13:53:49 -0400 Subject: [PATCH 2/4] Address code review: use NSApp.terminate to run delegate cleanup with a fallback exit(0) --- Sources/Fluid/Services/SimpleUpdater.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/Fluid/Services/SimpleUpdater.swift b/Sources/Fluid/Services/SimpleUpdater.swift index f153d66c..90ae83f5 100644 --- a/Sources/Fluid/Services/SimpleUpdater.swift +++ b/Sources/Fluid/Services/SimpleUpdater.swift @@ -918,7 +918,11 @@ final class SimpleUpdater { DebugLogger.shared.info("SimpleUpdater: Successfully relaunched app, terminating old instance", source: "SimpleUpdater") // Give the new instance time to fully start before terminating DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { - exit(0) + NSApp.terminate(nil) + + DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { + exit(0) + } } } } From 927cb51767ddc31586edee20f4a9b3fd3626757a Mon Sep 17 00:00:00 2001 From: sfasano Date: Wed, 5 Aug 2026 14:02:31 -0400 Subject: [PATCH 3/4] Address review: move fallback exit(0) to a global queue with a 20s delay to allow AppKit cleanup to finish --- Sources/Fluid/Services/SimpleUpdater.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/Fluid/Services/SimpleUpdater.swift b/Sources/Fluid/Services/SimpleUpdater.swift index 90ae83f5..d3b075bc 100644 --- a/Sources/Fluid/Services/SimpleUpdater.swift +++ b/Sources/Fluid/Services/SimpleUpdater.swift @@ -920,7 +920,10 @@ final class SimpleUpdater { DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { NSApp.terminate(nil) - DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { + // Fallback: forcefully terminate if AppKit gets stuck during or after termination. + // The AppDelegate cleanup can take up to 16 seconds (8s for Private AI + 8s for ASR). + // We wait 20 seconds on a background queue to ensure it only fires if truly hung. + DispatchQueue.global().asyncAfter(deadline: .now() + 20.0) { exit(0) } } From d29fa29f194fa38cfcc031dbe7ef772ba1e4bff7 Mon Sep 17 00:00:00 2001 From: sfasano Date: Wed, 5 Aug 2026 14:06:28 -0400 Subject: [PATCH 4/4] Address review: Arm the fallback watchdog before calling NSApp.terminate to ensure it schedules before termination blocks the main thread --- Sources/Fluid/Services/SimpleUpdater.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/Fluid/Services/SimpleUpdater.swift b/Sources/Fluid/Services/SimpleUpdater.swift index d3b075bc..74d01583 100644 --- a/Sources/Fluid/Services/SimpleUpdater.swift +++ b/Sources/Fluid/Services/SimpleUpdater.swift @@ -918,14 +918,14 @@ final class SimpleUpdater { DebugLogger.shared.info("SimpleUpdater: Successfully relaunched app, terminating old instance", source: "SimpleUpdater") // Give the new instance time to fully start before terminating DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { - NSApp.terminate(nil) - - // Fallback: forcefully terminate if AppKit gets stuck during or after termination. + // Arm the fallback watchdog BEFORE calling terminate, because terminate blocks. // The AppDelegate cleanup can take up to 16 seconds (8s for Private AI + 8s for ASR). // We wait 20 seconds on a background queue to ensure it only fires if truly hung. DispatchQueue.global().asyncAfter(deadline: .now() + 20.0) { exit(0) } + + NSApp.terminate(nil) } } }