Hotplug (macOS): self-join deadlock in hid_internal_hotplug_cleanup #794
Labels
No labels
3rdparty
Android
API
Author does not respond
bsd
bug
build system/CI
ci-virtual-device
CMake
Contributions Welcome
Core
dependencies
device-specific
documentation
don't_merge
duplicate
enhancement
Example
Haiku
hidraw
hotplug
info required
invalid
libusb
macOS
Misc
Not an issue
question
Release
Test
Test GUI
Windows
wontfix
No milestone
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
gh-libusb/hidapi#794
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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
Related
Drafted with Claude Code.