Skip to content

tests: Add tests/testcore.c, a multi-threaded stress test that hammers#1809

Open
sonatique wants to merge 1 commit intolibusb:masterfrom
sonatique:testcore-program-from-PR-1795
Open

tests: Add tests/testcore.c, a multi-threaded stress test that hammers#1809
sonatique wants to merge 1 commit intolibusb:masterfrom
sonatique:testcore-program-from-PR-1795

Conversation

@sonatique
Copy link
Copy Markdown
Member

libusb_get_device_list / libusb_free_device_list from N threads in parallel. It exists primarily to reproduce the concurrent-enumeration crashes reported in #1793 and the related set_composite_interface race surfaced during PR #1795 review.
The stressed code paths include:

  • usbi_get_device_by_session_id()
  • usbi_alloc_device()
  • usbi_connect_device()
  • libusb_unref_device() / ctx->usb_devs_lock
  • winusb_device_priv setup races in set_composite_interface and set_hid_interface (Windows, non-hotplug builds) The original C++20 reproducer was posted by smarvonohr and the C version with Win32 threads by mcuee in
    Address issue: race in libusb get device list and libusb free device list with win usb backend + claim_interface race #1795 (comment) This version is adapted from mcuee's C version, with two changes that make it portable and easier to build:
  • Use the same PLATFORM_POSIX / PLATFORM_WINDOWS thread abstraction that stress_mt.c uses (pthread_create on POSIX, _beginthreadex on Windows, CreateThread on Cygwin), instead of bare pthread. The original would not build with MSVC.
  • Print the device list once before starting the worker threads, so the operator can confirm at a glance which devices are present. This is useful because the bug only manifests when enumeration touches certain device shapes (e.g. composite devices with HID children that exercise set_composite_interface). Add msvc/testcore.vcxproj following the same template as msvc/stress_mt.vcxproj so the test builds out of the box in Visual Studio against libusb_static.
    Notes for runners:
  • The test only triggers the enumeration races in non-HOTPLUG builds. With Windows hotplug enabled, libusb_get_device_list is served from a cache and winusb_get_device_list never runs concurrently, so the bug does not manifest.
  • The default LOOPS = 100000 is intentionally large for the original bug-hunting use case but takes hours of real work even when the library is correct (each non-hotplug get_device_list does a full Windows USB enumeration, ~10-100 ms). Reduce LOOPS for a quick smoke test.
  • At least one composite USB device with HID-class child interfaces (USB game controller, headset with controls, Logitech Unifying receiver, etc.) must be present to exercise set_composite_interface. Pure UVC/UAC composite devices like webcams will NOT trigger that path because their child interfaces are not HID class and are not bound to any libusb-supported driver.

Closes #1808

@sonatique sonatique requested a review from mcuee April 22, 2026 09:00
@sonatique sonatique force-pushed the testcore-program-from-PR-1795 branch from 3a6cca4 to 1d05add Compare April 22, 2026 09:01
libusb_get_device_list / libusb_free_device_list from N threads in
parallel. It exists primarily to reproduce the concurrent-enumeration
crashes reported in libusb#1793 and the related set_composite_interface
race surfaced during PR libusb#1795 review.
The stressed code paths include:
 - usbi_get_device_by_session_id()
 - usbi_alloc_device()
 - usbi_connect_device()
 - libusb_unref_device() / ctx->usb_devs_lock
 - winusb_device_priv setup races in set_composite_interface and
   set_hid_interface (Windows, non-hotplug builds)
The original C++20 reproducer was posted by smarvonohr and the C
version with Win32 threads by mcuee in
libusb#1795 (comment)
This version is adapted from mcuee's C version, with two changes that
make it portable and easier to build:
 - Use the same PLATFORM_POSIX / PLATFORM_WINDOWS thread abstraction
   that stress_mt.c uses (pthread_create on POSIX, _beginthreadex on
   Windows, CreateThread on Cygwin), instead of bare pthread. The
   original would not build with MSVC.
 - Print the device list once before starting the worker threads, so
   the operator can confirm at a glance which devices are present.
   This is useful because the bug only manifests when enumeration
   touches certain device shapes (e.g. composite devices with HID
   children that exercise set_composite_interface).
Add msvc/testcore.vcxproj following the same template as
msvc/stress_mt.vcxproj so the test builds out of the box in Visual
Studio against libusb_static.
Notes for runners:
 - The test only triggers the enumeration races in non-HOTPLUG builds.
   With Windows hotplug enabled, libusb_get_device_list is served from
   a cache and winusb_get_device_list never runs concurrently, so the
   bug does not manifest.
 - The default LOOPS = 100000 is intentionally large for the original
   bug-hunting use case but takes hours of real work even when the
   library is correct (each non-hotplug get_device_list does a full
   Windows USB enumeration, ~10-100 ms). Reduce LOOPS for a quick
   smoke test.
 - At least one composite USB device with HID-class child interfaces
   (USB game controller, headset with controls, Logitech Unifying
   receiver, etc.) must be present to exercise set_composite_interface.
   Pure UVC/UAC composite devices like webcams will NOT trigger that
   path because their child interfaces are not HID class and are not
   bound to any libusb-supported driver.

Closes libusb#1808
Closes libusb#1809
@mcuee
Copy link
Copy Markdown
Member

mcuee commented Apr 22, 2026

Two quick comments.

  1. Need to add the project to VS solution file.
  2. Need to add the test to auto-tools build as well.

@mcuee mcuee added the test related to tests, split from example label label Apr 22, 2026
@mcuee
Copy link
Copy Markdown
Member

mcuee commented Apr 22, 2026

For example, under macOS, without adding the new test to auto-tools build, it will not build testcore by default.

Manual build:

mcuee in ~/build/libusb/libusb_pr1809/tests on testcore-program-from-PR-1795 % clang -pthread -g -I /Users/mcuee/mybin/bingit_debug/include/libusb-1.0 -I.. testcore.c -L /Users/mcuee/mybin/bingit_debug/lib -lusb-1.0 -o testcore_debug

mcuee in ~/build/libusb/libusb_pr1809/tests on testcore-program-from-PR-1795 % ./testcore_debug
Device 0: 03eb:2106 class=0xff
Device 1: 04b4:8613 class=0xff
Device 2: 05e3:0747 class=0x00
Device 3: 0bda:8156 class=0x00
Device 4: 05e3:0610 class=0x09
Device 5: 05e3:0625 class=0x09
Device 6: 05ac:8015 class=0x09
Starting 12 threads, 100000 loops each...
Completed without crash.

Comment thread tests/testcore.c
}

//libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_DEBUG);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe can add the following option to test Linux non-hotplug situation. Just as a comment, no need to be too complicated.
// This forces libusb to skip the automatic background discovery
// and only scan when you explicitly call get_device_list.
// libusb_set_option(ctx, LIBUSB_OPTION_NO_DEVICE_DISCOVERY);

Comment thread tests/testcore.c

#define THREADS 12
#define LOOPS 100000

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this will be a generic test program, it is good to add these two as argument. It is also good to print the help for the usage.

The default value can be 4 and 100000. Or shall we use 4 and 10000?

@mcuee
Copy link
Copy Markdown
Member

mcuee commented Apr 22, 2026

The default LOOPS = 100000 is intentionally large for the original bug-hunting use case but takes hours of real work even when the library is correct (each non-hotplug get_device_list does a full Windows USB enumeration, ~10-100 ms). Reduce LOOPS for a quick smoke test.

It would be good to make number of threads and number of loops to be arguments to the test program. If we choose the default loops to be 100,000, we may want to inform the user the above info.

Comment thread tests/testcore.c
Comment on lines +87 to +88
#define THREADS 12
#define LOOPS 100000
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is just test code, but it would be nice to minimise use of #define for numerical constants as we write new code. Instead:

static const int THREADS = 12;

This allows better type checking.

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

Labels

test related to tests, split from example label

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Include testcore in libusb tests

3 participants