Skip to content

Commit 95e9bb6

Browse files
committed
uevent-sender: Retry udev_device_new_from_syspath() on failure
Fix a race condition in uevent_sender_send() where udev_device_new_from_syspath() transiently fails with ENOENT, even though the device directory exists in the mocked /sys filesystem. This caused a test flake: > t_mt_uevent: assertion failed (_data1_->change_count == _data1_->num_changes): (9 == 10) t_mt_uevent test creates a testbed with a device at /sys/devices/dev1, then spawns a thread that rapidly sends uevent notifications for that device by calling tb.uevent(). Each call to tb.uevent() creates a new UeventSender object, which internally creates a new libudev context via udev_new(), and then calls udev_device_new_from_syspath() to read the device information from the mocked /sys filesystem. The race occurs when multiple threads are simultaneously accessing paths under the mocked /sys through the umockdev preload library: 1. Thread A (emitter): Calls tb.uevent() → uevent_sender_send() → udev_device_new_from_syspath(sender->udev, "/sys/devices/dev1") 2. Thread B (main loop or noise): Simultaneously accessing /sys paths (e.g., reading device attributes, binding sockets) 3. The preload library uses TRAP_PATH_LOCK (a pthread mutex) to serialize access to trap_path() for all intercepted filesystem operations under /sys, /dev, and /proc. 4. When libudev's udev_device_new_from_syspath() tries to enumerate the device, it performs multiple filesystem operations (stat, readdir, readlink, etc.) on /sys/devices/dev1 and its subdirectories. 5. If another thread holds TRAP_PATH_LOCK during a critical part of libudev's device enumeration, or if libudev's internal caching/state is inconsistent due to concurrent access, udev_device_new_from_syspath() returns NULL with errno=ENOENT. 6. Importantly, the failure is **transient** - an immediate retry succeeds because the device directory hasn't actually disappeared; it was just temporarily inaccessible or libudev's state was inconsistent. This is a pragmatic workaround rather than a proper fix.
1 parent 79bfb6a commit 95e9bb6

3 files changed

Lines changed: 11 additions & 4 deletions

File tree

src/uevent_sender.c

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,15 @@ uevent_sender_send(uevent_sender * sender, const char *devpath, const char *acti
247247

248248
device = udev_device_new_from_syspath(sender->udev, devpath);
249249
if (device == NULL) {
250-
fprintf(stderr, "ERROR: uevent_sender_send: No such device %s\n", devpath);
251-
return;
250+
fprintf(stderr, "ERROR: uevent_sender_send: Failed udev_device_new_from_syspath(%s): %m\n", devpath);
251+
/* HACK: there's a race condition where libudev might not see the device immediately when multiple
252+
* threads are accessing /sys, due to TRAP_PATH_LOCK contention. Retry once. */
253+
usleep(100);
254+
device = udev_device_new_from_syspath(sender->udev, devpath);
255+
if (device == NULL) {
256+
fprintf(stderr, "ERROR: uevent_sender_send: Failed udev_device_new_from_syspath(%s) after retry: %m\n", devpath);
257+
return;
258+
}
252259
}
253260

254261
subsystem = udev_device_get_subsystem(device);

tests/test-umockdev-vala.vala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1008,7 +1008,7 @@ t_mt_uevent ()
10081008
var ml = new MainLoop ();
10091009
uint add_count = 0;
10101010
uint change_count = 0;
1011-
uint num_changes = 10;
1011+
uint num_changes = 100;
10121012

10131013
gudev.uevent.connect((client, action, device) => {
10141014
if (action == "add")

tests/test-umockdev.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -811,7 +811,7 @@ t_testbed_uevent_error(UMockdevTestbedFixture * fixture, UNUSED_DATA)
811811
fflush(stderr);
812812
rewind(stderr);
813813
g_assert(fgets(buf, sizeof(buf), stderr) != NULL);
814-
g_assert_cmpstr(buf, ==, "ERROR: uevent_sender_send: No such device /devices/unknown\n");
814+
g_assert_cmpstr(buf, ==, "ERROR: uevent_sender_send: Failed udev_device_new_from_syspath(/devices/unknown): Invalid argument\n");
815815
fclose(stderr);
816816
stderr = orig_stderr;
817817
#endif

0 commit comments

Comments
 (0)