Search code examples
c++multithreadingc++11libusb-1.0

Libusb hangs when using C++11 async


I'm new to libusb, so I don't really understand it very well. I'm trying to do some USB communication.

I'm using the hotplug feature which works quite well. So I thought when I detect a device arrived event, I would do all communication with USB on another thread using the C++11 asynchronous feature, so I could do synchronous I/O with multiple devices to simply codes. Their asynchronous I/O is a bit confusing to me. Hopefully I can use the synchronous I/O with C++ asynchronous feature.

But I'm having a problem where some libusb call seem to hang when the code is running in C++11 asynchronous feature. When it is not running in C++11 asynchronous feature it works without any problem.

So I'm assuming it is my C++11 asynchronous feature code that's the problem.

Here is my hotplug callback:

int LIBUSB_CALL hotplug_callback(libusb_context *ctx, libusb_device *dev, libusb_hotplug_event event, void *user_data)
{
  std::future<void> result(std::async([] (libusb_device *d) {

    libusb_device_handle *h;

    printf("%s\n", "Opening");
    int rc = libusb_open(d, &h);
    if(rc != LIBUSB_SUCCESS) {
      printf("ERROR: %s\n", libusb_error_name(rc));
      return;
    }
    printf("%s\n", "Opened");

    printf("%s\n", "Closing");
    libusb_close(h);
    printf("%s\n", "Closed!");

  }, dev));

  result.get();

  return 0;
}

So with this code it hangs on libusb_close

It outputs:

Opening
Opened
Closing

The main code looks like this:

int main(int argc, char* argv[]) {

  int vendor_id = 0x1234;
  int product_id = 0x4556;

  libusb_hotplug_callback_handle *hp = nullptr;
  libusb_context *context = nullptr;
  int rc = libusb_init(&context);

  if(rc < 0)
  {
    return rc;
  }

  libusb_set_debug(context, LIBUSB_LOG_LEVEL_WARNING);

  rc = libusb_hotplug_register_callback(
    context,
    LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED,
    LIBUSB_HOTPLUG_NO_FLAGS,
    vendor_id,
    product_id,
    LIBUSB_HOTPLUG_MATCH_ANY,
    hotplug_callback,
    NULL,
    hp
    );

  if (LIBUSB_SUCCESS != rc) {
    libusb_exit (context);
    return rc;
  }

  while(1) {
    rc = libusb_handle_events(context);
  }

  return 0;
}

Mind you this code is more prototypical, so it is not really well written. It is still in the exploration mode of libusb.


Solution

  • According to their own website, using multi-threading with libusb (and std::async is essentially multi-threading if it isn’t serialised) requires quite some considerations.

    For example, they specifically state:

    libusb_close() will remove a file descriptor from the poll set. There are all kinds of race conditions that could arise here, so it is important that nobody is doing event handling at this time.

    In your code, there is no synchronisation between the call to libusb_close in the one thread and libusb_handle_events in the other. Related (from the same source as above):

    The issue is that if two or more threads are concurrently calling poll() or select() on libusb's file descriptors then only one of those threads will be woken up when an event arrives. The others will be completely oblivious that anything has happened.

    It is entirely imaginable that what happens here is that libusb_handle_events in the main thread is "stealing" an event that libusb_close is waiting for, making it never return.

    The bottom line is that you need to either:

    1. follow the linked article, which explains in detail how to use libusb with multi-threading, or
    2. understand the asynchronous API of libusb and make use of that instead.