diff --git a/MiddleDrag/Managers/DeviceMonitor.swift b/MiddleDrag/Managers/DeviceMonitor.swift index a611716..5eef19e 100644 --- a/MiddleDrag/Managers/DeviceMonitor.swift +++ b/MiddleDrag/Managers/DeviceMonitor.swift @@ -90,10 +90,11 @@ class DeviceMonitor: TouchDeviceProviding { /// Delay between unregistering callbacks and stopping devices. /// This allows the MultitouchSupport framework's internal thread (mt_ThreadedMTEntry) /// to complete any in-flight callback processing before we stop devices. - /// Value determined empirically: 100ms is sufficient to avoid CFRelease(NULL) crashes - /// and EXC_BREAKPOINT exceptions while keeping the stop operation reasonably fast. - /// Increased from 50ms to handle rapid foreground/background toggling scenarios. - static let frameworkCleanupDelay: TimeInterval = 0.1 + /// Value determined empirically: 500ms is sufficient to avoid CFRelease(NULL) crashes + /// and EXC_BREAKPOINT exceptions during rapid connectivity changes (wifi ↔ none). + /// Increased from 100ms to handle rapid connectivity toggling that triggers multiple + /// restart cycles in quick succession. + static let frameworkCleanupDelay: TimeInterval = 0.5 // MARK: - Properties @@ -266,9 +267,14 @@ class DeviceMonitor: TouchDeviceProviding { // This sleep is intentionally done with lock unlocked to avoid blocking other threads. unsafe Thread.sleep(forTimeInterval: Self.frameworkCleanupDelay) - // Now safe to stop devices + // Now safe to stop devices. Acquire lock before each MTDeviceStop to ensure + // the framework's internal thread (mt_ThreadedMTEntry) cannot access the device + // during the critical stop operation. This prevents CFRelease(NULL) crashes + // from the framework trying to release device resources concurrently. for unsafe deviceRef in unsafe devicesToStop { + unsafe os_unfair_lock_lock(&gCallbackLock) unsafe MTDeviceStop(deviceRef) + unsafe os_unfair_lock_unlock(&gCallbackLock) } Log.info("DeviceMonitor stopped", category: .device) diff --git a/MiddleDrag/Managers/MultitouchManager.swift b/MiddleDrag/Managers/MultitouchManager.swift index aadc55e..5bba8ea 100644 --- a/MiddleDrag/Managers/MultitouchManager.swift +++ b/MiddleDrag/Managers/MultitouchManager.swift @@ -11,15 +11,18 @@ final class MultitouchManager: @unchecked Sendable { /// Delay after stopping before restarting devices during wake-from-sleep. /// This allows the MultitouchSupport framework's internal thread (mt_ThreadedMTEntry) /// to fully complete cleanup before we start new devices. - /// Value determined empirically: 250ms is sufficient to avoid CFRelease(NULL) crashes - /// caused by the framework accessing deallocated resources. Increased from 100ms to - /// handle rapid foreground/background toggling that can trigger multiple restart cycles. - static let restartCleanupDelay: TimeInterval = 0.25 + /// Value determined empirically: 500ms is sufficient to avoid CFRelease(NULL) crashes + /// and EXC_BREAKPOINT exceptions during rapid connectivity changes (wifi ↔ none). + /// Increased from 250ms to handle rapid connectivity toggling that triggers multiple + /// restart cycles in quick succession. + static let restartCleanupDelay: TimeInterval = 0.5 /// Minimum delay between restart operations to prevent race conditions. - /// When multiple restart triggers occur in rapid succession, we debounce them - /// by waiting at least this long after the last restart completed. - static let minimumRestartInterval: TimeInterval = 0.3 + /// When multiple restart triggers occur in rapid succession (e.g., rapid connectivity + /// changes wifi ↔ none), we debounce them by waiting at least this long after the + /// last restart completed. This prevents overlapping restart attempts that can expose + /// race conditions in the MultitouchSupport framework's internal thread. + static let minimumRestartInterval: TimeInterval = 0.6 // MARK: - Properties