From b916fe51cc33f724910d3bbafb0978842f68369a Mon Sep 17 00:00:00 2001 From: Stephen <158498287+StephenDev0@users.noreply.github.com> Date: Thu, 27 Aug 2026 09:18:18 -0400 Subject: [PATCH] fix: prevent stalled tunnel connections from exhausting threads --- StikDebug/Device/JITEnableContext.swift | 4 +++- StikDebug/Device/MountingProgress.swift | 20 +++++++++++++++++++ .../InstalledAppsViewModel.swift | 7 ++++++- StikDebug/Views/InstalledAppsListView.swift | 4 +++- 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/StikDebug/Device/JITEnableContext.swift b/StikDebug/Device/JITEnableContext.swift index ff6e7cc..7a2d8a3 100644 --- a/StikDebug/Device/JITEnableContext.swift +++ b/StikDebug/Device/JITEnableContext.swift @@ -188,7 +188,9 @@ final class JITEnableContext { tunnelLock.unlock() if let waitSemaphore { - waitSemaphore.wait() + guard waitSemaphore.wait(timeout: .now() + .seconds(15)) == .success else { + throw makeError("Timed out waiting for the tunnel connection", code: -19) + } waitSemaphore.signal() } diff --git a/StikDebug/Device/MountingProgress.swift b/StikDebug/Device/MountingProgress.swift index 20edd26..46f9595 100644 --- a/StikDebug/Device/MountingProgress.swift +++ b/StikDebug/Device/MountingProgress.swift @@ -13,11 +13,29 @@ final class MountingProgress: ObservableObject { @Published private(set) var mountingThread: Thread? @Published private(set) var coolisMounted: Bool = false + private let mountCheckLock = NSLock() + private var mountCheckInProgress = false + private init() {} func checkforMounted() { + guard TunnelManager.shared.isConnected else { return } + + mountCheckLock.lock() + guard !mountCheckInProgress else { + mountCheckLock.unlock() + return + } + mountCheckInProgress = true + mountCheckLock.unlock() + DispatchQueue.global(qos: .utility).async { let mounted = isMounted() + + self.mountCheckLock.lock() + self.mountCheckInProgress = false + self.mountCheckLock.unlock() + DispatchQueue.main.async { self.coolisMounted = mounted } @@ -32,6 +50,8 @@ final class MountingProgress: ObservableObject { } func pubMount() { + guard TunnelManager.shared.isConnected else { return } + DispatchQueue.global(qos: .utility).async { [weak self] in self?.mount() } diff --git a/StikDebug/Features/InstalledApps/InstalledAppsViewModel.swift b/StikDebug/Features/InstalledApps/InstalledAppsViewModel.swift index 5a93389..5b36ebb 100644 --- a/StikDebug/Features/InstalledApps/InstalledAppsViewModel.swift +++ b/StikDebug/Features/InstalledApps/InstalledAppsViewModel.swift @@ -20,10 +20,15 @@ final class InstalledAppsViewModel: ObservableObject { private let cacheKeyDebuggable = "cachedDebuggableApps" private let cacheKeyNonDebuggable = "cachedNonDebuggableApps" private let cacheKeySystem = "cachedSystemApps" + private var cancellables: Set = [] init() { loadCachedApps() - refreshAppLists() + TunnelManager.shared.$isConnected + .removeDuplicates() + .filter { $0 } + .sink { [weak self] _ in self?.refreshAppLists() } + .store(in: &cancellables) } func refreshAppLists() { diff --git a/StikDebug/Views/InstalledAppsListView.swift b/StikDebug/Views/InstalledAppsListView.swift index 69437a6..3f22192 100644 --- a/StikDebug/Views/InstalledAppsListView.swift +++ b/StikDebug/Views/InstalledAppsListView.swift @@ -83,7 +83,9 @@ struct InstalledAppsListView: View { handleLoadingChange(isLoading) } .onReceive(NotificationCenter.default.publisher(for: .pairingFileImported)) { _ in - viewModel.refreshAppLists() + if TunnelManager.shared.isConnected { + viewModel.refreshAppLists() + } } }