Skip to content
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

Remove atomic usage and fix mutex locking in GCAdapter code #10540

Merged
merged 3 commits into from Apr 23, 2022

Conversation

nyanpasu64
Copy link
Contributor

Remove unnecessary atomic usage in GCAdapter.cpp

Makes s_controller_payload_size non-atomic. You can safely read or write non-atomic integers on multiple threads, as long as every thread reading or writing it holds the same mutex while doing so (here, s_mutex).

Removing the atomic accesses makes the code faster, but the actual performance difference is probably negligible.

Remove unnecessary atomic usage in GCAdapter_Android.cpp

Makes s_controller_payload_size non-atomic for the same reason. However s_controller_write_payload_size must remain atomic to avoid UB (though I doubt it needs to be seq_cst), since it's being read without a lock held. This threading system may have race conditions, I'm not sure.

Along the way I also noticed that ResetRumble() (apparently called on Core.cpp#void SetState(State state)) holds the wrong mutex, so I fixed that as well. I did not test my changes on-device to verify this does not introduce deadlocks this cannot introduce deadlocks since ResetRumble() is never called on Android! If it does, the old code was wrong as well, and a different change will have to be made (either different usage of mutexes, or a lock-free handshake), requiring that I study the control flow more carefully (which is difficult, and I would benefit from help on).

Background

The atomic usage was introduced in 07caff3 and a62a9b8. The ResetRumble() bug dates back to the file's creation at e62503c. I did not look into why only the Android implementation has two mutexes.

Android fails to ResetRumble entirely!

I decided to test ResetRumble() on Android:

  • Install Dolphin (https://dl.dolphin-emu.org/builds/3a/50/dolphin-master-5.0-16101.apk) on my Pixel 4a running Android 12 (2022-03)
  • Enable controller adapter support
  • Plug in a Mayflash GameCube Controller Adapter (also connect gray plug for rumble support), set to Wii U mode (to a USB C-to-A hub)
  • Boot Wind Waker and press Back to open the side menu
  • Roll into a wall, and press "Pause Emulation" while rumble is active

This caused the rumble to remain running.

If I read the code properly, this is supposed to call MENU_ACTION_PAUSE_EMULATION -> NativeLibrary.PauseEmulation() = Java_org_dolphinemu_dolphinemu_NativeLibrary_PauseEmulation -> namespace Core::SetState(Core::State::Paused) -> ResetRumble() -> GCAdapter::ResetRumble(). Unfortunately, CMakeLists.txt doesn't set __LIBUSB__ on Android, so Core.cpp#ResetRumble() doesn't call GCAdapter::ResetRumble() which would map to GCAdapter_Android.cpp#GCAdapter::ResetRumble(). Consequently, Android never calls ResetRumble(), so the current code is dead and the wrong mutex would never be reached!

Should I remove Core.cpp #if defined(__LIBUSB__) in yet another commit in this PR, or open a separate PR? At that point, I'd probably want to compile an Android build for testing. Also I didn't review all other uses of __LIBUSB__ for correctness, though the build seems to work on all OSes right now.

You can safely read or write non-atomic integers on multiple threads,
as long as every thread reading or writing it holds the same mutex
while doing so (here, s_mutex).

Removing the atomic accesses makes the code faster, but the actual
performance difference is probably negligible.
s_controller_write_payload_size needs to remain an atomic because Read()
loads and stores without holding a mutex, Output() stores while holding
s_write_mutex, and ResetRumble() stores while holding s_read_mutex! I'm
pretty sure this code is wrong, specifically ResetRumble().
I am not confident there are no race conditions between s_write_mutex,
s_controller_write_payload_size, and s_controller_write_payload. But
this code should be safer than before.
@JosJuice
Copy link
Member

I would say let's change #if defined(__LIBUSB__) to #if defined(__LIBUSB__) || defined(ANDROID) and do it in a separate PR. Entirely removing the check would break things on platforms that neither have libusb nor are Android.

@nyanpasu64
Copy link
Contributor Author

bump

  • s_controller_payload_size was initialized differently in PC/Android from the get-go. Should I change one or both initializations in this PR (and/or s_controller_write_payload_size on GCAdapter_Android.cpp)?
  • As far as I can tell, __LIBUSB__ is set in CMakeLists.txt on all platforms except Android (link), and unconditionally in the VS Base.props (link). Unless I made a reading error or missed other build systems, I think defined(__LIBUSB__) || defined(ANDROID) is unconditionally true. Is that define still worth adding (for example for the unofficial Dolphin on iOS fork)?

Copy link
Contributor

@AdmiralCurtiss AdmiralCurtiss left a comment

Choose a reason for hiding this comment

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

Untested, but this looks correct to me.

@AdmiralCurtiss
Copy link
Contributor

If you have further changes it's probably better to make another PR for them.

@JosJuice
Copy link
Member

As far as I can tell, __LIBUSB__ is set in CMakeLists.txt on all platforms except Android (link), and unconditionally in the VS Base.props (link). Unless I made a reading error or missed other build systems, I think defined(__LIBUSB__) || defined(ANDROID) is unconditionally true. Is that define still worth adding (for example for the unofficial Dolphin on iOS fork)?

Hmm... Not sure then. I guess it's fine either way.

@JosJuice JosJuice merged commit e0afcb3 into dolphin-emu:master Apr 23, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
3 participants