Why does macOS MultitouchSupport framework crash if MTDeviceStop is called immediately after MTUnregisterContactFrameCallback?

5 days ago 4
ARTICLE AD BOX

I'm using Apple's private MultitouchSupport.framework to receive raw trackpad touch data on macOS. The basic pattern is:

MTDeviceCreateList() / MTDeviceCreateDefault() to get device references

MTRegisterContactFrameCallback() to register a callback

MTDeviceStart() to begin receiving touches

Later: MTUnregisterContactFrameCallback() then MTDeviceStop() to clean up

Issue: If I call MTDeviceStop() immediately after MTUnregisterContactFrameCallback(), the app intermittently crashes with CFRelease() called with NULL or EXC_BAD_ACCESS. The crash happens on a framework-internal thread named com.apple.MultitouchSupport.gesturestats or similar.

Adding a ~50ms delay between unregister and stop eliminates the crashes. But I found this value through trial and error: I don't understand why it's needed.

Minimal reproduction (Swift):

swift

// Bindings to private framework @_silgen_name("MTDeviceCreateDefault") func MTDeviceCreateDefault() -> UnsafeMutableRawPointer? @_silgen_name("MTDeviceStart") func MTDeviceStart(_ device: UnsafeMutableRawPointer, _ mode: Int32) @_silgen_name("MTDeviceStop") func MTDeviceStop(_ device: UnsafeMutableRawPointer) @_silgen_name("MTRegisterContactFrameCallback") func MTRegisterContactFrameCallback(_ device: UnsafeMutableRawPointer, _ callback: @convention(c) (UnsafeMutableRawPointer?, UnsafeMutableRawPointer?, Int32, Double, Int32) -> Int32) @_silgen_name("MTUnregisterContactFrameCallback") func MTUnregisterContactFrameCallback(_ device: UnsafeMutableRawPointer, _ callback: (@convention(c) (UnsafeMutableRawPointer?, UnsafeMutableRawPointer?, Int32, Double, Int32) -> Int32)?) // Callback let callback: @convention(c) (UnsafeMutableRawPointer?, UnsafeMutableRawPointer?, Int32, Double, Int32) -> Int32 = { _, _, _, _, _ in return 0 } // Start guard let device = MTDeviceCreateDefault() else { return } MTRegisterContactFrameCallback(device, callback) MTDeviceStart(device, 0) // ... later, stop (THIS CRASHES INTERMITTENTLY): MTUnregisterContactFrameCallback(device, callback) MTDeviceStop(device) // CFRelease(NULL) or EXC_BAD_ACCESS

Workaround that prevents crashes:

MTUnregisterContactFrameCallback(device, callback) Thread.sleep(forTimeInterval: 0.05) // 50ms delay - WHY IS THIS NEEDED? MTDeviceStop(device) // Now safe

Notes:

The crash occurs on a background thread owned by the framework, not my code

Stack traces show CFRelease, __CFArrayReleaseValues, and sometimes IOHIDEventRelease

The crash is not deterministic, it depends on timing (whether a touch callback is in-flight)

50ms is sufficient on my machine; 10ms sometimes still crashes

The issue is worse during system wake from sleep (more timing sensitivity I assume)

Environment:

macOS 14/15

Apple Silicon and Intel

Built-in trackpad

What I'm looking for:

An explanation of what the framework's internal thread is doing between callback unregistration and device stop that causes this race condition, and ideally, a proper synchronization mechanism (API, notification, or signal) to wait for safe cleanup rather than an arbitrary sleep.

Read Entire Article