Skip to content

Hotplug (macOS): self-join deadlock in hid_internal_hotplug_cleanup #794

@Youw

Description

@Youw

Summary

When a hotplug callback that self-deregisters runs in response to a device-removal event on macOS, `hid_internal_hotplug_cleanup` is reached from within the hotplug thread itself and calls `pthread_join(hid_hotplug_context.thread, NULL)` on its own thread — which hangs forever.

Call chain

```
hotplug_thread()
CFRunLoopRunInMode() [hotplug thread's run loop]
hid_internal_hotplug_disconnect_callback() [IOHID callback, on hotplug thread]
pthread_mutex_lock() [recursive OK]
hid_internal_invoke_callbacks()
user_callback() returns non-zero [self-deregister]
callback->events = 0; cb_list_dirty = 1
mutex_in_use = 0 [guard reset]
hid_internal_hotplug_remove_postponed()
callback freed; hotplug_cbs == NULL
hid_internal_hotplug_cleanup() [mac/hid.c ~line 1089]
mutex_in_use == 0 guard passes
hotplug_cbs == NULL so cleanup proceeds
thread_state = 2; signal run loop
pthread_join(hid_hotplug_context.thread) [*** SELF-JOIN ***]
HANG
```

The `mutex_in_use` guard in `hid_internal_invoke_callbacks` resets to 0 before returning (mac/hid.c:997), so it does not protect `hid_internal_hotplug_cleanup` from running when the path enters from an IOHID callback.

Proposed fix

In `hid_internal_hotplug_cleanup`, detect the self-thread case and use `pthread_detach` instead of `pthread_join`:

```c
if (pthread_equal(pthread_self(), hid_hotplug_context.thread)) {
pthread_detach(hid_hotplug_context.thread);
} else {
pthread_join(hid_hotplug_context.thread, NULL);
}
```

Alternatively, defer the teardown signal until the thread's next run-loop iteration so the cleanup never runs on the self thread.

Reproducer

A callback registered with `HID_API_HOTPLUG_EVENT_DEVICE_LEFT` that returns non-zero when the event fires, on a device that is then physically (or programmatically) unplugged, hangs the removal thread.

Scope

  • `mac/hid.c` only.
  • Verify analogous paths in `linux/hid.c`, `libusb/hid.c`, `windows/hid.c` don't share the same problem (`windows/hid.c` already does self-thread guards in some places).

Related


Drafted with Claude Code.

Metadata

Metadata

Assignees

No one assigned

    Labels

    hotplugRelated to hotplugmacOSRelated to macOS backend

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions