linux: deliver hotplug ENUMERATE pass asynchronously on the event context#822
Open
Youw wants to merge 1 commit into
Open
linux: deliver hotplug ENUMERATE pass asynchronously on the event context#822Youw wants to merge 1 commit into
Youw wants to merge 1 commit into
Conversation
Implements the hotplug contract documented in hidapi.h for the hidraw backend: - hid_hotplug_register_callback() now takes a deep-copied snapshot of the matching connected devices and the udev monitor thread replays it as synthetic "arrived" events: never on an application thread, never from within the register call itself, and always before any live events for that callback; a non-zero return stops the remainder of the pass and deregisters the callback. - Live "arrived" events are delivered as one invocation per device entry with next == NULL (multi-usage devices previously exposed the chained list to callbacks). - All register/deregister failure paths set the global error string and register resets *callback_handle to 0 on failure. - The monitor thread only takes the mutex with trylock and releases no shared state on exit; joining and monitoring-context teardown are centralized in hid_internal_hotplug_cleanup(), fixing an unjoined thread and a teardown race when the last callback removes itself on the monitor thread and a later registration re-creates the context. Assisted-by: claude-code:claude-fable-5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the hotplug contract documented in
hidapi/hidapi.h(#790) for the linux/hidraw backend.What changed
Asynchronous ENUMERATE pass.
hid_hotplug_register_callback()no longer runs theHID_API_HOTPLUG_ENUMERATEinitial pass synchronously on the calling thread. Instead it takes a deep-copied, registration-time snapshot of the matching entries of the internal device cache (only whenHID_API_HOTPLUG_EVENT_DEVICE_ARRIVEDis requested), stores it on the callback record (replay), and the udev monitor thread delivers it as synthetic "arrived" events:hid_internal_invoke_callbacks()flushes a callback's pending snapshot before handing it a live event, and the monitor thread also flushes pending snapshots at the top of its loop (the existing 5 ms select timeout caps the latency, no extra wakeup fd needed);This also covers registrations made while the monitor thread is already running, including registrations made from within another hotplug callback.
device->nextis now NULL for every invocation. The udev arrival path used to pass the chained multi-usage list to callbacks (the old TODO); each entry is now temporarily detached for the duration of its invocation, keeping the internal cache intact for later "left" matching.Error strings and out-parameter. Every failure path of register/deregister (NULL callback, empty/unknown events bits, unknown flags bits, allocation/udev/thread-start failures, unknown or stale deregister handle) now sets the global error string retrievable via
hid_error(NULL), and register sets*callback_handleto 0 on failure before any error return.Monitor-thread lifecycle fixes. Previously, when the last callback removed itself on the monitor thread (non-zero return or in-callback deregister), the thread exited on its own, was never joined, and freed the shared device list / udev monitor unlocked — racing a subsequent first registration that re-creates them. Now the exiting thread releases nothing; joining and monitoring-context teardown are centralized in
hid_internal_hotplug_cleanup()(guarded by athread_startedflag) and a new first registration reaps the previous thread before re-creating the context. To make joining with the mutex held deadlock-free, the monitor thread only ever acquires the mutex withtrylockand backs off briefly on contention, so it can always make progress to its exit check.Design decisions / deviations
replay_pendingcontext flag is read unlocked at the top of the monitor loop (mirroring the existing unlockedhotplug_cbscheck) and re-checked/cleared under the mutex; worst case the pass is picked up one 5 ms iteration later.hid_enumerate(0, 0)inside the first registration may set the "No HID devices found" global error on an empty system; register clears it, since an empty system is not a registration failure.Verification
gcc -fsyntax-only -Wall -Wextra -pedantic -Werrorandg++ -fsyntax-only -Wall -Wextra -Werrorclean; full CMake builds (C andHIDAPI_BUILD_AS_CXX=ONwith-Werror) pass.CONFIG_UHIDnot set), so the tests: virtual HID device test harness for all four backends (hidraw, libusb, winapi, darwin) #815 virtual-device harness could not drive real udev events. Instead a throwaway whitebox harness (includinglinux/hid.cdirectly and injecting fake entries into the hotplug device cache) exercised: async delivery on the monitor thread (never the registering thread), VID/PID filtering of the snapshot,next == NULLon every invocation, exactly-once vs. later-connected devices, non-zero return stopping the pass, self-deregistration and nested registration from within a callback, stale-handle deregister, last-callback self-removal followed by re-registration (thread reaping),hid_exit()with an undelivered snapshot, and all argument-validation error strings. 10/10 runs clean under ASan+UBSan+LSan; TSan reports only the pre-existing intentional unlocked flag reads. Live udev arrival/removal delivery was not exercised at runtime and deserves a check on real hardware.Addresses #792 and #793 for the linux/hidraw backend.
Assisted-by: claude-code:claude-fable-5