How to force a libusb event so that libusb_handle_events() returns
Asked Answered
P

3

5

Suppose I have a libusb program that just uses the hotplug API. You register a callback and then apparently have to call libusb_handle_events() in a loop which then calls your hotplug callback.

int LIBUSB_CALL hotplugCallback(libusb_context* ctx,
                                libusb_device* device,
                                libusb_hotplug_event event,
                                void* user_data)
{
    cout << "Device plugged in or unplugged";
}

void main()
{
    libusb_init(nullptr);

    libusb_hotplug_register_callback(nullptr,
                                    static_cast<libusb_hotplug_event>(LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT),
                                    LIBUSB_HOTPLUG_NO_FLAGS,
                                    LIBUSB_HOTPLUG_MATCH_ANY,
                                    LIBUSB_HOTPLUG_MATCH_ANY,
                                    LIBUSB_HOTPLUG_MATCH_ANY,
                                    &hotplugCallback,
                                    this,
                                    &hotplugCallbackHandle);

    for (;;)
    {
        if (libusb_handle_events_completed(nullptr, nullptr) != LIBUSB_SUCCESS)
            return 1;
    }

    return 0;
}

The question is, without timeout hacks how can I exit this event loop cleanly? I can't find any functions that force libusb_handle_events() (or libusb_handle_events_completed()) to return. In theory they could just never return.

Precautionary answered 14/8, 2017 at 10:40 Comment(0)
G
7

Sorry if this is late.

The question could have been phrased better but I'm assuming (from your comment updates) that your actual program resembles something a little closer to this:

int LIBUSB_CALL hotplugCallback(libusb_context *ctx,
                                libusb_device *device,
                                libusb_hotplug_event event,
                                void *user_data) {
    cout << "Device plugged in or unplugged";
}

void SomeClass::someFunction() {
    libusb_init(nullptr);
    libusb_hotplug_register_callback(nullptr,
                                static_cast<libusb_hotplug_event>(LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT),
                                LIBUSB_HOTPLUG_NO_FLAGS,
                                LIBUSB_HOTPLUG_MATCH_ANY,
                                LIBUSB_HOTPLUG_MATCH_ANY,
                                LIBUSB_HOTPLUG_MATCH_ANY,
                                &hotplugCallback,
                                this,
                                &hotplugCallbackHandle);

    this->thread = std::thread([this]() {
        while (this->handlingEvents) {
            int error = libusb_handle_events_completed(context, nullptr);
        }
    });
}

Let's say your object is being deallocated and, no matter what is happening on the USB bus, you don't care and you want to clean up your thread.

You negate this->handlingEvents and you call thread.join() and the thread hangs for 60 seconds and then execution resumes.

This is done because the default behavior of libusb_handle_events_completed calls libusb_handle_events_timeout_completed and passes in a 60 second timeout interval with plans to make it infinite.

The way you force libusb_handle_events_completed to return is you call libusb_hotplug_deregister_callback which wakes up libusb_handle_events(), causing the function to return.

There is more info about this behavior in the docs.

So your destructor (or wherever you want to stop listening immediately) for the class could look something like this:

SomeClass::~SomeClass() {
    this->handlingEvents = false;
    libusb_hotplug_deregister_callback(context, hotplugCallbackHandle);
    if (this->thread.joinable()) this->thread.join();

    libusb_exit(this->context);
}
Galvanize answered 7/2, 2020 at 18:35 Comment(2)
Ah very nice. Better late than never!Precautionary
Thanks! Another gotcha that devs might overlook is that the hotplug callback can also be called from hotplug_register_callback() if you set HOTPLUG_ENUMERATE. In this case, deregistering the callback by returning true won't work, you will need to set the flag you check around your while loop and exit early or deregister it manually. I'm not even sure if deregistering it manually inside the callback will work.Fosterling
M
0

In the function:

int libusb_handle_events_completed(libusb_context* ctx, int* completed) 

You can change the value of the completed to "1" so the function will return without blocking

According to their docs:

If the parameter completed is not NULL then after obtaining the event handling lock this function will return immediately if the integer pointed to is not 0. This allows for race free waiting for the completion of a specific transfer.

Mcdougald answered 20/3, 2018 at 13:27 Comment(1)
I can't see how that would work unless it keeps polling *completed, which is just another timeout hack.Precautionary
M
0

There is no functions in libusb that force libusb_handle_events() to return.

It's recommended to use libusb_handle_events() in a dedicated thread so your main thread will not be blocked by this call. Even though, if you need to manipulate the call of the event handler you can put the call in a while(condition) and change the condition state in your main thread.

Libusb documentation details this here.

Matinee answered 1/11, 2018 at 14:31 Comment(1)
Sorry, this doesn't answer the question at all. libusb_handle_events() is already in a dedicated thread (in my actual code). The question is how to exit it cleanly.Precautionary

© 2022 - 2024 — McMap. All rights reserved.