New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix GC Adapter breaking and burning a full CPU core after sleep-wake on Linux #12015
Fix GC Adapter breaking and burning a full CPU core after sleep-wake on Linux #12015
Conversation
Every time you change the list of controllers, Qt calls {
Config::ConfigChangeCallbackGuard config_guard;
...
if (GCAdapter::UseAdapter())
[bool UseAdapter()] { return s_is_adapter_wanted; }
if ^
GCAdapter::StartScanThread();
else
GCAdapter::StopScanThread();
}
[config_guard.~ConfigChangeCallbackGuard()]
...
[static void RefreshConfig()] { initialize s_is_adapter_wanted }The problem is that There are multiple ways to fix the bug, with different possible side effects. You could move |
68efcbb
to
b6c6fd9
Compare
| @@ -636,6 +636,7 @@ static void AddGCAdapter(libusb_device* device) | |||
| } | |||
| } | |||
| } | |||
| libusb_free_config_descriptor(config); | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you can refactor this to MakeConfigDescriptor() then I think that's the better choice, just for the RAII cleanup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I assume I'd write something like
const auto [error, config] = LibusbUtils::MakeConfigDescriptor(device)to match the existing calls to this function.- I don't really like that you don't see the types of
errorandconfiganymore, but there's not really a better way to useMakeConfigDescriptor().
- I don't really like that you don't see the types of
- Do I need to introduce a new scope to eagerly destroy the
ConfigDescriptor(unique_ptr with dynamic destructor) when not in use? If not, should I reset the unique_ptr or leave it initialized until the function returns? And should I reuse theerrorvalue as the return value oflibusb_interrupt_transfer()below?
| LibusbUtils::ErrorWrap(error)); | ||
| if (error != 0) | ||
| { | ||
| break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would the user have to do to re-enable the GC Adapter if this break; is hit? I get how you arrived at this but it's kinda weird how the thread just dies here without informing anyone about this failure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oof. Normally Reset() checks if (s_read_adapter_thread_running.TestAndClear()) s_read_adapter_thread.join();, which happens when the USB device is unplugged.
If I ignore libusb_reset_device() return value and always assume it fails, I found that switching away from GC Adapter and back doesn't work (and the controller dialog wrongly states "Adapter Detected"). I think there needs to be some way for the GC thread to communicate to the GUI whether it's stopped on its own or not. Unfortunately std::thread lacks a non-blocking "has terminated" API (and only offers blocking join).
One possible workaround is to add a new atomic boolean variable for "is thread running". I tried setting s_adapter_error = static_cast<libusb_error>(error); s_status = AdapterStatus::Error before breaking out of the loop, but this shows a message on the UI "Error Opening Adapter: Input/Output Error" (suggesting an error on opening the adapter, not recovering from sleep-wake), and switching away from the adapter and back still shows the same error. Unfortunately even unplugging and plugging the adapter does not help for some reason.
I'll have to trace what's going on with the threads further, perhaps I didn't close a device somewhere. (Though does it really matter given that I've only ever seen this case by pretending that libusb_reset_device() fails?)
EDIT: The current code is quite designed around the assumption that Reset() will be the one cleaning up GC adapter state, not the thread terminating itself after CheckDeviceAccess() succeeds and AddGCAdapter() runs. So I don't know if I can correctly implement returning early from ReadThreadFunc() at all, without a substantial rewrite of the GC adapter threading (which may produce a better result, but I'm very much not interested in spending weeks learning libusb's threading properties and wondering what C++ code/variables is and isn't thread-safe).
Perhaps I'll call libusb_reset_device() (which blocks for 430 ms when it completes successfully, both normally and after LIBUSB_ERROR_IO), discard the error code, and if it fails then call libusb_reset_device() again on the next iteration? I don't know how long it blocks if it fails, and if it can even fail to reset the device without LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT being called.
|
re the config callback bug, I would just move the Start/StopScanThread() block outside the callback guard, that makes the most sense to me. I don't think that should have side effects but who knows... e: In fact, a5dbf6b I seem to have introduced this bug so it's probably fine to just move it outside. |
Should I fix this in this PR or another, since it's a UI-side code fix? |
|
poke, i've |
Fixes GC adapter breaking on sleep-wake on Linux and burning a full CPU core. This is cleaner than alternative approaches.
GCAdapter::UseAdapter() reads s_is_adapter_wanted, which gets initialized by config_guard.~ConfigChangeCallbackGuard(). So we must wait until after destroying the config guard to know whether we have any controllers set to GC Adapter.
e0381e9
to
c8df265
Compare
|
So what's the status here, are you planning to change anything else? This looks reasonable to me as-is, admittedly without understanding the intricacies of libusb. |
|
I don't think I'm planning to change anything else in this branch, it worked properly last time I tested the branch and I'm working on other changes now. (unrelated) ...though it would be nice if my AMD chipset/GPU on Linux wouldn't randomly fail to resume from sleep, and/or kill all USB devices in a way they don't come back even after soft-resetting my machine until I unplug and replug them. |
https://bugs.dolphin-emu.org/issues/13284
After sleep-wake on Linux, talking to the GC USB adapter fails with
LIBUSB_ERROR_IObecause the kernel driver regains ownership over the adapter device. Previously Dolphin would continually try talking to the adapter in an infinite loop, burning a full CPU core and failing to receive new input from the adapter.The minimal solution is to
libusb_detach_kernel_driver()the adapter again, but I've been told that the best approach to handlingLIBUSB_ERROR_IOis to instead calllibusb_reset_deviceand reinitialize the device. This PR callslibusb_reset_devicewhen the system sleep-wakes and talking to the GC adapter returnsLIBUSB_ERROR_IO.MakeConfigDescriptor()which wraps the error code and descriptor in an RAII handle?LIBUSB_ERROR_IO? I have not found any other ways to trigger an endless CPU-burning loop, but it may be a good idea to do so defensively.Another bug I found: