windows: deliver hotplug ENUMERATE pass asynchronously on the event context#823
Open
Youw wants to merge 2 commits into
Open
windows: deliver hotplug ENUMERATE pass asynchronously on the event context#823Youw wants to merge 2 commits into
Youw wants to merge 2 commits into
Conversation
…nt context Implement the hotplug contract documented in #790 for the windows backend: The HID_API_HOTPLUG_ENUMERATE initial pass is now a deep-copied registration-time snapshot of the matching connected devices, replayed on a threadpool work item (the same critical-section-serialized event context that delivers live events) instead of running synchronously inside hid_hotplug_register_callback on the application thread. A callback's pending snapshot is flushed before any live event is delivered to it, so each device connection is reported exactly once and the pass always precedes live events; a non-zero callback return stops the remainder of the pass and deregisters the callback. Also per the contract: - hid_hotplug_deregister_callback returns -1 for unknown or stale handles (0 only when the callback was found and deregistered) - every register/deregister failure path sets the global error string, and *callback_handle is zeroed on registration failure - undelivered snapshots are freed on deregistration and hid_exit - CM_Unregister_Notification is no longer called from within the notification callback (forbidden, deadlocks) nor while holding the hotplug critical section (deadlocks against in-flight notifications): the teardown is detached under the lock and performed outside it, deferred to the work item when triggered from the notification itself Addresses #793 for the windows backend. Assisted-by: claude-code:claude-fable-5
…TE pass
- Arm the CM notification BEFORE taking the registration-time device
snapshot, and deduplicate arrivals by path in the notification callback:
a device connecting between the two is now caught by the notification
and absorbed by the dedupe instead of being missed by both (it was
reported by neither before)
- Only (re)build the device cache when actually arming a notification;
while one is attached the cache is kept current by its callbacks
(re-enumerating during a deferred teardown could double-report)
- Serialize new-notification arming against in-flight
CM_Unregister_Notification calls (pending counter + condition variable):
a detached handle stays live at the OS until its unregistration
completes, and overlapping registrations would deliver events twice;
hid_exit waits for the same quiescence before destroying state
- Treat a failed CM_Unregister_Notification as a poisoned epoch: hid_exit
reports the failure, returns -1 and deliberately leaks the critical
section, work item and loaded DLLs rather than let a still-live
notification touch destroyed state
- Guard the machinery bootstrap with a statically-initialized SRWLOCK
(two racing first registrations could both initialize the critical
section), and serialize all mutations of the global error string with
another SRWLOCK (concurrent thread-safe failures could double-free it)
- Distinguish a genuinely failed enumeration from an empty system at
registration (fail the registration on the former), and clear the stale
"No HID devices found" error on successful registration
- Fail registration with an error when the callback handle space is
exhausted instead of signed-overflowing and wrapping onto live handles
- Replace { 0 } struct initializers with memset in hid.c for
-Wmissing-field-initializers cleanliness when built as C++ with GNU
compilers; use the WinAPI error helper for CreateThreadpoolWork failures
Addresses round-1 review of the windows part of #793.
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 via #790 for the windows backend (
windows/hid.conly).What changed
hid_hotplug_register_callbackno longer runs theHID_API_HOTPLUG_ENUMERATEpass synchronously on the calling thread. Instead it takes a deep-copied snapshot of the matching entries of the cached device list (under the hotplug critical section, only when the flag is set andeventsincludesDEVICE_ARRIVED) into a per-callbackreplaylist, and submits a threadpool work item (CreateThreadpoolWork/SubmitThreadpoolWork) that delivers it on the same critical-section-serialized context that delivers liveCM_Register_Notificationevents. The pass never fires from withinregisteritself and never on an application thread.device->nextis nowNULLfor every invocation, including the synthetic ones (the old sync pass handed out devices still linked into the cached list).0only when the handle was found and deregistered;-1for unknown, stale or already-self-deregistered handles (safe no-op).hid_error(NULL);*callback_handleis zeroed on any registration failure.CM_Unregister_Notificationis documented to deadlock when called from its own notification callback, and it also deadlocks if called while holding a lock the notification callback takes. The teardown now detaches the notification handle under the critical section and unregisters outside it; when the last callback removes itself from inside the notification callback, the actual teardown is deferred to the threadpool work item.hid_exitfrees undelivered snapshots, waits for in-flight work items (WaitForThreadpoolWorkCallbacks) before deleting the critical section, and sweeps anything a last-gasp notification appended to the device cache.Design notes
PTP_WORKcreated with the first callback registration and closed byhid_exit. I usedCreateThreadpoolWorkrather thanTrySubmitThreadpoolCallbackbecause teardown needsWaitForThreadpoolWorkCallbacksto guarantee no work item can touch the critical section afterhid_exitdeletes it; the fire-and-forget API offers no way to wait.mutex_in_useset: whichever of the work item or a live event acquires the lock first delivers the pass, the other findsreplay == NULL._WIN32_WINNTto0x0600when it is set lower (the dynamically-resolved CM notification API already requires Windows 8 at runtime).Addresses #793 for the windows backend.
Assisted-by: claude-code:claude-fable-5