RFC: Add hid_read_interrupt API for thread-safe read cancellation#799
RFC: Add hid_read_interrupt API for thread-safe read cancellation#799
Conversation
New functions on hid_device, implemented across all five backends (linux, libusb, mac, windows, netbsd): hid_read_interrupt - asynchronously cancel a blocked read hid_is_read_interrupted - query the sticky interrupt state hid_read_clear_interrupt - clear the state, allowing reads to resume hid_read_interrupt is the only hidapi function safe to call concurrently with another function on the same device. Other device operations (write, feature/output reports, info getters) are unaffected. Allows a dedicated reader thread to be stopped without busy-polling on a short timeout. New hid_read_test tool, to check new functionality. Resolves: #146 Assisted-by: Claude:claude-opus-4.7
There was a problem hiding this comment.
Pull request overview
This PR adds a new public HIDAPI mechanism to asynchronously interrupt a blocked hid_read()/hid_read_timeout() call in a thread-safe way (intended to allow clean shutdown of dedicated reader threads without short timeouts/busy polling), and introduces a small hid_read_test utility to exercise the behavior.
Changes:
- Adds
hid_read_interrupt(),hid_is_read_interrupted(), andhid_read_clear_interrupt()to the public header and implements them across all backends. - Updates backend
hid_read_timeout()implementations to return-1with an “operation interrupted” read error when interruption is requested. - Adds a new optional
hid_read_testtool and a top-level CMake option to build it.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
windows/hid.c |
Implements interrupt via a manual-reset event + interlocked flag and integrates it into WaitForMultipleObjects() in hid_read_timeout(). |
netbsd/hid.c |
Implements interrupt via a nonblocking pipe added to the poll() fd set, plus a sticky interrupt flag. |
mac/hid.c |
Implements interrupt via a mutex-protected flag + condition broadcast integrated into the read wait loops. |
linux/hid.c |
Implements interrupt via eventfd added to poll() plus a sticky interrupt flag; adjusts failure cleanup path to call hid_close(). |
libusb/hid.c |
Implements interrupt via a mutex-protected flag + condition broadcast integrated into the read wait loops. |
hidapi/hidapi.h |
Adds the public API declarations and documents the intended thread-safety/semantics. |
hid_read_test/main.cpp |
New command-line tool that opens a device, runs a blocking read thread, and interrupts on Enter/Ctrl+C. |
hid_read_test/CMakeLists.txt |
Adds build/install rules for the new test tool (standalone or as a subdir). |
CMakeLists.txt |
Adds HIDAPI_BUILD_HID_READ_TEST option to include the new tool in the build. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 8 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -1149,12 +1165,15 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t | |||
| properly report device disconnection through read() when | |||
| in non-blocking mode. */ | |||
| int ret; | |||
| struct pollfd fds; | |||
|
|
|||
| fds.fd = dev->device_handle; | |||
| fds.events = POLLIN; | |||
| fds.revents = 0; | |||
| ret = poll(&fds, 1, milliseconds); | |||
| struct pollfd fds[2]; | |||
|
|
|||
| fds[0].fd = dev->device_handle; | |||
| fds[0].events = POLLIN; | |||
| fds[0].revents = 0; | |||
| fds[1].fd = dev->interrupt_efd; | |||
| fds[1].events = POLLIN; | |||
| fds[1].revents = 0; | |||
| ret = poll(fds, 2, milliseconds); | |||
| /* Interrupt fired and no data is ready. The pending ReadFile is | ||
| left in flight; it will resume on the next hid_read_timeout() | ||
| call after hid_read_clear_interrupt(). */ | ||
| register_string_error_to_buffer(&dev->last_read_error_str, L"hid_read_timeout: operation interrupted"); |
| #ifdef _WIN32 | ||
| std::signal(SIGINT, on_signal); | ||
| #ifdef SIGTERM | ||
| std::signal(SIGTERM, on_signal); |
| extern "C" void on_signal(int) | ||
| { | ||
| /* async-signal-safe: atomic store only. Main thread will call | ||
| hid_read_interrupt() once cin.get() returns from EINTR. */ | ||
| g_terminate.store(true, std::memory_order_release); |
| option(HIDAPI_BUILD_HID_READ_TEST "Build hid_read_test cmd-line tool" OFF) | ||
| if(HIDAPI_BUILD_HID_READ_TEST) | ||
| add_subdirectory(hid_read_test) | ||
| endif() |
| This is the only function on hid_device that may be called concurrently | ||
| with another function operating on the same device. The call is | ||
| idempotent and is safe to invoke when no read is in flight. |
| option(HIDAPI_BUILD_HID_READ_TEST "Build hid_read_test cmd-line tool" OFF) | ||
| if(HIDAPI_BUILD_HID_READ_TEST) | ||
| add_subdirectory(hid_read_test) |
| 1 if the read pipeline is interrupted, 0 if not, -1 on error. | ||
| Call hid_error(dev) to get the failure reason. |
New functions on hid_device, implemented across all five backends (linux, libusb, mac, windows, netbsd):
hid_read_interrupt - asynchronously cancel a blocked read
hid_is_read_interrupted - query the sticky interrupt state
hid_read_clear_interrupt - clear the state, allowing reads to resume
hid_read_interrupt is the only hidapi function safe to call concurrently with another function on the same device. Other device operations (write, feature/output reports, info getters) are unaffected. Allows a dedicated reader thread to be stopped without busy-polling on a short timeout.
New hid_read_test tool, to check new functionality.
Resolves: #146
Assisted-by: Claude:claude-opus-4.7