Skip to content

improve overall stability when adding/removing devices - #27

Open
rustyx wants to merge 2 commits into
InputActions:mainfrom
rustyx:fix-stale-device-udev
Open

rustyx wants to merge 2 commits into
InputActions:mainfrom
rustyx:fix-stale-device-udev

Conversation

@rustyx

@rustyx rustyx commented Oct 19, 2026

Copy link
Copy Markdown

Two commits to improve overall stability when adding/removing devices:

client/input: remove dead devices on read failure (fixes InputActions/discussions#492)

  • poll(): remove the device on -ENODEV to avoid the client spinning at 100% CPU until udev's "remove" event arrives.
  • evdevDeviceAdded(), tryAddEvdevDevice(): never hold two devices for one path.
  • evdevDeviceRemoved(): also take the path out of the retry queue.

client/input: unregister devices dropped by the retry timer

  • consistently remove the device with removeDeviceAt(), also in the retry timer. Before, it freed the device without unregistering it, leading to a heap-use-after-free.

The switch to libudev made removal notifications reliable in the common
case, but the client still relies on them exclusively. The kernel hangs
up the evdev descriptor as soon as the device is unregistered, while the
udev remove event only arrives after udevd has processed it. Until then
(or forever, if the event is lost to a monitor socket overflow) the
descriptor stays POLLERR | POLLHUP, libevdev returns -ENODEV, and poll()
ignores it, pinning a core and starving event relay for other devices.

- poll(): treat -ENODEV as device removal
- evdevDeviceAdded(): drop any device still held for the path, since a
  new node there means the remove event for the old one was missed
- tryAddEvdevDevice(): never hold two devices for one path, which the
  retry queue could otherwise cause
- evdevDeviceRemoved(): also drop the path from the retry queue

Reproducible by pausing udevd (udevadm control --stop-exec-queue) and
disconnecting a grabbed device.

Fixes InputActions/discussions#492
When libinput keeps rejecting a device's event injection device, the
retry timer gave up by erasing the device from m_devices without calling
removeDevice(). InputBackend kept the freed pointer in its device list
and event handler chain, so anything iterating devices afterwards (e.g.
`inputactions devices list`, keyboard modifier queries) read freed
memory.

Remove the device through removeDeviceAt() like every other path, and
log why it was dropped.
@rustyx rustyx changed the title Fix stale device udev improve overall stability when adding/removing devices Oct 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant