From 9af7df0dac287c2c8e16bfb6d6c67821c4fe04b6 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Tue, 17 Feb 2026 21:50:21 -0800 Subject: [PATCH] Fix socket accept loop not restarted after Sparkle update relaunch After a Sparkle auto-update relaunches cmux, the control socket stops accepting connections because start() early-returns when isRunning is true, without checking if the accept loop thread is actually alive. - Add acceptLoopAlive flag to track accept loop thread liveness - Fix start() early-return to also check acceptLoopAlive, so a dead thread triggers full socket re-creation - Break acceptLoop() after 50 consecutive accept() failures with 10ms backoff instead of tight-spinning forever - Clean up socket in applicationWillTerminate and updaterWillRelaunchApplication for clean teardown before relaunch --- Sources/AppDelegate.swift | 1 + Sources/TerminalController.swift | 22 +++++++++++++++++++--- Sources/Update/UpdateDelegate.swift | 1 + 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 8f0c4bc2..496e8722 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -424,6 +424,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent } func applicationWillTerminate(_ notification: Notification) { + TerminalController.shared.stop() BrowserHistoryStore.shared.flushPendingSaves() PostHogAnalytics.shared.flush() notificationStore?.clearAll() diff --git a/Sources/TerminalController.swift b/Sources/TerminalController.swift index c4487325..2c990f6a 100644 --- a/Sources/TerminalController.swift +++ b/Sources/TerminalController.swift @@ -13,6 +13,7 @@ class TerminalController { private nonisolated(unsafe) var socketPath = "/tmp/cmux.sock" private nonisolated(unsafe) var serverSocket: Int32 = -1 private nonisolated(unsafe) var isRunning = false + private nonisolated(unsafe) var acceptLoopAlive = false private var clientHandlers: [Int32: Thread] = [:] private var tabManager: TabManager? private var accessMode: SocketControlMode = .full @@ -77,7 +78,7 @@ class TerminalController { self.accessMode = accessMode if isRunning { - if self.socketPath == socketPath { + if self.socketPath == socketPath && acceptLoopAlive { self.accessMode = accessMode return } @@ -143,7 +144,14 @@ class TerminalController { unlink(socketPath) } - private func acceptLoop() { + private nonisolated func acceptLoop() { + acceptLoopAlive = true + defer { + acceptLoopAlive = false + isRunning = false + } + + var consecutiveFailures = 0 while isRunning { var clientAddr = sockaddr_un() var clientAddrLen = socklen_t(MemoryLayout.size) @@ -156,11 +164,19 @@ class TerminalController { guard clientSocket >= 0 else { if isRunning { - print("TerminalController: Accept failed") + consecutiveFailures += 1 + print("TerminalController: Accept failed (\(consecutiveFailures) consecutive)") + if consecutiveFailures >= 50 { + print("TerminalController: Too many consecutive accept failures, exiting accept loop") + break + } + usleep(10_000) // 10ms backoff } continue } + consecutiveFailures = 0 + // Handle client in new thread Thread.detachNewThread { [weak self] in self?.handleClient(clientSocket) diff --git a/Sources/Update/UpdateDelegate.swift b/Sources/Update/UpdateDelegate.swift index 38163ec3..d9af9dbd 100644 --- a/Sources/Update/UpdateDelegate.swift +++ b/Sources/Update/UpdateDelegate.swift @@ -87,6 +87,7 @@ extension UpdateDriver: SPUUpdaterDelegate { } func updaterWillRelaunchApplication(_ updater: SPUUpdater) { + TerminalController.shared.stop() NSApp.invalidateRestorableState() for window in NSApp.windows { window.invalidateRestorableState()