udev: don't assert on worker cap after killing a broken idle worker

manager_can_process_event() considers an event processable if either
there is room below children_max to spawn, or an idle worker exists.
When only the latter holds, event_run() picks the idle worker and
tries device_monitor_send(). If that send fails, event_run() SIGKILLs
the worker, marks it WORKER_KILLED and continues the loop. With no
other idle worker available, it falls through to worker_spawn(),
guarded by:

    assert(hashmap_size(manager->workers) < manager->config.children_max);

The just-killed worker is still in manager->workers until its SIGCHLD
is reaped by on_worker_exit(), so at the cap this assertion trips and
udevd aborts:

    Assertion 'hashmap_size(manager->workers) < manager->config.children_max'
    failed at src/udev/udev-manager.c:635, function event_run(). Aborting.

Instead of asserting, bail out when we are already at the worker
limit. The event remains in EVENT_QUEUED; once the killed worker's
SIGCHLD arrives and frees it from the hashmap, on_post() re-runs
event_queue_start() and the event is retried.
This commit is contained in:
Daan De Meyer
2026-04-27 21:14:08 +02:00
committed by Daan De Meyer
parent b2d72eabcd
commit 181a9f65a7
+6 -1
View File
@@ -631,8 +631,13 @@ static int event_run(Event *event) {
return 0;
}
/* No idle worker could accept the event. If we already reached the worker limit, e.g. because
* we just killed the only idle worker above, leave the event queued and wait for SIGCHLD of an
* exiting worker to free up a slot. on_post() will retry processing the queue. */
if (hashmap_size(manager->workers) >= manager->config.children_max)
return 0;
/* start new worker and pass initial device */
assert(hashmap_size(manager->workers) < manager->config.children_max);
r = worker_spawn(manager, event);
if (r < 0)
return r;