diff --git a/adb/adb.cpp b/adb/adb.cpp index a7706a09f..bfb1144d4 100644 --- a/adb/adb.cpp +++ b/adb/adb.cpp @@ -1257,6 +1257,10 @@ void update_transport_status() { void adb_notify_device_scan_complete() { { std::lock_guard lock(init_mutex); + if (device_scan_complete) { + return; + } + device_scan_complete = true; } diff --git a/adb/adb_utils.h b/adb/adb_utils.h index c1d5549b0..20c63b39d 100644 --- a/adb/adb_utils.h +++ b/adb/adb_utils.h @@ -17,7 +17,10 @@ #ifndef _ADB_UTILS_H_ #define _ADB_UTILS_H_ +#include +#include #include +#include #include @@ -53,4 +56,32 @@ extern int adb_close(int fd); bool forward_targets_are_valid(const std::string& source, const std::string& dest, std::string* error); +// A thread-safe blocking queue. +template +class BlockingQueue { + std::mutex mutex; + std::condition_variable cv; + std::vector queue; + + public: + void Push(const T& t) { + { + std::unique_lock lock(mutex); + queue.push_back(t); + } + cv.notify_one(); + } + + template + void PopAll(Fn fn) { + std::unique_lock lock(mutex); + cv.wait(lock, [this]() { return !queue.empty(); }); + + for (const T& t : queue) { + fn(t); + } + queue.clear(); + } +}; + #endif diff --git a/adb/client/usb_libusb.cpp b/adb/client/usb_libusb.cpp index 9477c5620..7e77b5ede 100644 --- a/adb/client/usb_libusb.cpp +++ b/adb/client/usb_libusb.cpp @@ -37,6 +37,7 @@ #include #include "adb.h" +#include "adb_utils.h" #include "transport.h" #include "usb.h" @@ -387,10 +388,9 @@ static std::atomic connecting_devices(0); static void device_connected(libusb_device* device) { #if defined(__linux__) // Android's host linux libusb uses netlink instead of udev for device hotplug notification, - // which means we can get hotplug notifications before udev has updated ownership/perms on the - // device. Since we're not going to be able to link against the system's libudev any time soon, - // hack around this by checking for accessibility in a loop. - ++connecting_devices; + // which means we can get hotplug notifications before udev has updated ownership/perms on + // the device. Since we're not going to be able to link against the system's libudev any + // time soon, hack around this by checking for accessibility in a loop. auto thread = std::thread([device]() { std::string device_path = get_device_dev_path(device); auto start = std::chrono::steady_clock::now(); @@ -402,7 +402,9 @@ static void device_connected(libusb_device* device) { } process_device(device); - --connecting_devices; + if (--connecting_devices == 0) { + adb_notify_device_scan_complete(); + } }); thread.detach(); #else @@ -425,17 +427,33 @@ static void device_disconnected(libusb_device* device) { } } +static auto& hotplug_queue = *new BlockingQueue>(); +static void hotplug_thread() { + adb_thread_setname("libusb hotplug"); + while (true) { + hotplug_queue.PopAll([](std::pair pair) { + libusb_hotplug_event event = pair.first; + libusb_device* device = pair.second; + if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED) { + device_connected(device); + } else if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT) { + device_disconnected(device); + } + }); + } +} + static int hotplug_callback(libusb_context*, libusb_device* device, libusb_hotplug_event event, void*) { - // We're called with the libusb lock taken. Call these on the main thread outside of this + // We're called with the libusb lock taken. Call these on a separate thread outside of this // function so that the usb_handle mutex is always taken before the libusb mutex. - fdevent_run_on_main_thread([device, event]() { - if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED) { - device_connected(device); - } else if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT) { - device_disconnected(device); - } - }); + static std::once_flag once; + std::call_once(once, []() { std::thread(hotplug_thread).detach(); }); + + if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED) { + ++connecting_devices; + } + hotplug_queue.Push({event, device}); return 0; } @@ -457,13 +475,6 @@ void usb_init() { LOG(FATAL) << "failed to register libusb hotplug callback"; } - // Wait for all of the connecting devices to finish. - while (connecting_devices != 0) { - std::this_thread::sleep_for(10ms); - } - - adb_notify_device_scan_complete(); - // Spawn a thread for libusb_handle_events. std::thread([]() { adb_thread_setname("libusb");