Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions MiddleDrag/Managers/DeviceMonitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Copilot AI Feb 6, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The term "wifi" should be capitalized as "Wi-Fi" for proper spelling.

Suggested change
/// and EXC_BREAKPOINT exceptions during rapid connectivity changes (wifi ↔ none).
/// and EXC_BREAKPOINT exceptions during rapid connectivity changes (Wi-Fi ↔ none).

Copilot uses AI. Check for mistakes.
/// Increased from 100ms to handle rapid connectivity toggling that triggers multiple
/// restart cycles in quick succession.
static let frameworkCleanupDelay: TimeInterval = 0.5

// MARK: - Properties

Expand Down Expand Up @@ -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)
Expand Down
17 changes: 10 additions & 7 deletions MiddleDrag/Managers/MultitouchManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Copilot AI Feb 6, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The term "wifi" should be capitalized as "Wi-Fi" for proper spelling.

Copilot uses AI. Check for mistakes.
/// 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

Copilot AI Feb 6, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The term "wifi" should be capitalized as "Wi-Fi" for proper spelling.

Copilot uses AI. Check for mistakes.
/// 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

Expand Down
Loading