Skip to content

fix: Resolve boot time races and deadlocks - #750

Merged
pastaq merged 3 commits into
mainfrom
pastaq/udev-init-race
Oct 10, 2026
Merged

pastaq merged 3 commits into
mainfrom
pastaq/udev-init-race

Conversation

@pastaq

@pastaq pastaq commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

This solves 3 similar issues during startup with races that either cause deadlocks or unexpected behavior.

Patch 1: During udevadm trigger udev devices appear to the manager udev watcher prior to is_initialized being set to true. This was only being checking in the inotify path, which doesn't fire during retrigger, so partially initialized devices would get processed. These devices lacked udev properties (such as ID_INPUT_JOYSTICK) so they would silently fail to re-attach after the hide and would result in loss of input devices on the CompositeDevice

Patch 2: During startup device nodes with identical properties race (i.e. eventX/jsY from the same input device). Whichever device lands first would be sent to the CompositeDevice. Devices without a driver would then fail to add, but Manager is "fire and forget" to avoid deadlocks, so the failure was never reported and the failed device lived forever in source_devices_used. If the entry was unique and an invalid device landed first it would then hold the unique entry. When the valid entry then hits it cannot be added and would spawn a new CompositeDevice.

Patch 3: TargetDevice creation loop is blocking, causing external client commands run at CompositeDevice detection to deadlock and fail.

Closes: #725

pastaq added 3 commits October 9, 2026 20:37
The startup re-trigger makes udev reprocess every input device. During
that window the udev enumerator reports devices whose database entry
is incomplete. Only the five properties synthesized from sysfs
(SUBSYSTEM, MINOR, DEVNAME, DEVPATH, MAJOR) are present, so the device
fails to match its config. Discovery only runs once at startup, so the
device was lost for the whole session. Prior to this change
initialization checks were only done in the inotify branch.

UdevDevice now tracks whether udev finished initializing it. The
manager parks not-yet-initialized devices instead of matching them and
rechecks them every second until udev is done or the device is
removed. Rechecking is only scheduled while devices are parked.

A local LLM model was used for root cause analysis and brainstorming.

Assisted-by: LLM
Currently the Manager records a config entry immediately after a fire and
forget SourceDeviceAdded CompositeCommand. Since the add runs
asynchronously inside the composite and Manager cannot await the result
without risking a deadlock, the possibility exists for a device to fail to
run and the Manager is not informed of the failure. When the add fails
the entry stays recorded for the whole session. For devices with unique:
true set in their config, a later device matching the same entry then
fails the uniqueness check and forks a duplicate composite device.
Examples include a gamepad who's js device matches its evdev, or a numlock
LED matches the keyboard entry. In both cases the device will fail to
find a driver, error, and be dropped from the CompositeDevice, but the
Manager will still record the unique slot filled.

Add an mpsc transmit channel to the CompositeDevice. This channel must never
be awaited or it can cause deadlocks, so use try_send only. On add
failure, report the failed device to the Manager, which removes the
device id from the source devices and source devices used maps.

A local LLM model was used for root cause analysis and brainstorming.
Assisted-by: LLM
…tion

In the CompositeDevice run loop, set_target_devices() is run, joining on
CreateTargetDevice responses from the Manager. During the boot device
flood the Manager's channel saturates and the run loop sits blocked for
the whole creation window. Any commands queued behind it, such as
GetName/GetCapabilities when the DBus interface starts, hit the client's
5 second timeout, log POSSIBLE DEADLOCK, and drop their receivers,
producing late SendError responses. External clients, such as
steamos-manager, that read CompositeDevice properties immediately after a
composite appears observe the failure.

Move the stop and create work out of the run loop into a spawned task. Old
targets are removed from the routing maps synchronously, then stopped in
the background. A target_creation_in_progress flag is set at spawn time so
the set_target_devices re-queue guard still covers the async window until
the created target paths are known. The spawned task reports completion
with a new TargetDevicesCreated command, which clears the flag and enqueues
the created paths for attachment.

While the guard is active, incoming set_target_devices() are deferred in
pending_set_devices, last request wins, and executed once when the guard
clears.

A local LLM model was used for root cause analysis and brainstorming.
Assisted-by: LLM
@pastaq
pastaq force-pushed the pastaq/udev-init-race branch from dd698e6 to 4072b94 Compare October 10, 2026 04:11
@pastaq
pastaq merged commit 410e6d2 into main Oct 10, 2026
6 checks passed
@pastaq
pastaq deleted the pastaq/udev-init-race branch October 10, 2026 04:31
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 0.82.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Loss of controls after wakeup on MSI Claw A8

1 participant