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

Netplay: Fix possible Wii save restore race condition between Netplay and CPU threads on game shutdown by making the Wii Save Sync data part of the BootParameters. #10235

Merged
merged 4 commits into from Nov 23, 2021

Conversation

AdmiralCurtiss
Copy link
Contributor

Scenario: Two people play Netplay with Wii save syncing enabled. This works in Dolphin by making a temporary Wii NAND, copying the host's save file into there, then running on that temp NAND for the game session. Then the host disconnects from the client so that this piece of code is reached:

case ENET_EVENT_TYPE_DISCONNECT:
m_dialog->OnConnectionLost();
if (m_is_running.IsSet())
StopGame();
break;

and then in StopGame():

// stop game
m_dialog->StopGame();
ClearWiiSyncData();

m_dialog->StopGame(); can return before the shutdown is actually complete -- I'm not entirely sure on the details here, but I believe what happens is that this emit Stop() here:

void NetPlayDialog::StopGame()
{
if (m_got_stop_request)
return;
m_got_stop_request = true;
emit Stop();
}

Is handled asynchronously by the GUI thread. Which then leads the Netplay thread to call ClearWiiSyncData(); while the CPU thread is still in the process of shutting down, which:

void ClearWiiSyncData()
{
// We're just assuming it will always be here because it is
const std::string path = File::GetUserPath(D_USER_IDX) + "Wii" GC_MEMCARD_NETPLAY DIR_SEP;
if (File::Exists(path))
File::DeleteDirRecursively(path);
s_wii_sync_fs.reset();
s_wii_sync_titles.clear();
}

The important line here is the s_wii_sync_fs.reset();, which the CPU thread shutdown routine uses to detect whether it should copy back data to the main NAND or not -- if we're the host it is null, if we're the client it is not null, or that's the theory anyway.

void CleanUpWiiFileSystemContents()
{
if (!WiiRootIsTemporary() || !Config::Get(Config::SESSION_SAVE_DATA_WRITABLE) ||
NetPlay::GetWiiSyncFS())
{
return;
}

Anyway, long story short, I fixed this by just putting this data into the BootParameters so the CPU thread lifetime decides when it gets cleared, rather than some global in the Netplay files. This should circumvent this entire mess and have it work regardless of timing.

(Also note: The "Wii" GC_MEMCARD_NETPLAY directory being deleted is not, as you might assume, the temp NAND -- rather it is the staging directory used for collecting saves when they're sent to the client on netplay initialization. So that part doesn't break anything.)

@JMC47
Copy link
Contributor

JMC47 commented Nov 20, 2021

This is probably urgent to merge once it's ready, due to the nature of how this works.

Copy link
Member

@leoetlino leoetlino left a comment

Choose a reason for hiding this comment

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

Reviewed 9 of 9 files at r1, 5 of 5 files at r2, 5 of 5 files at r3, 6 of 6 files at r4, all commit messages.
Reviewable status: all files reviewed, 4 unresolved discussions (waiting on @AdmiralCurtiss)


Source/Core/Core/Core.cpp, line 483 at r3 (raw file):

  BootSessionData boot_session_data = std::move(boot->boot_session_data);
  const std::optional<std::string> savestate_path = boot_session_data.GetSavestatePath();

Making a copy of the savestate path seems unnecessary now, so this can be turned into a reference


Source/Core/Core/Core.cpp, line 621 at r3 (raw file):

  // with the correct title context since save copying requires title directories to exist.
  Common::ScopeGuard wiifs_guard{
      [&boot_session_data]() { Core::CleanUpWiiFileSystemContents(boot_session_data); }};

no need for ()


Source/Core/Core/WiiRoot.cpp, line 342 at r3 (raw file):

(Also note: The "Wii" GC_MEMCARD_NETPLAY directory being deleted is not, as you might assume, the temp NAND -- rather it is the staging directory used for collecting saves when they're sent to the client on netplay initialization. So that part doesn't break anything.)

I'm a bit confused -- is this clearing the netplay temp NAND or the save staging area?


Source/Core/DolphinQt/NetPlay/NetPlayDialog.h, line 39 at r2 (raw file):

public:
  explicit NetPlayDialog(const GameListModel& game_list_model,
                         std::function<void(const std::string& path,

using StartGameCallback = ... to make this more readable (and avoid having to write the entire thing a second time for m_start_game_callback)?

@AdmiralCurtiss
Copy link
Contributor Author

Ah oops, yes, staging area, not temp NAND, I was confused on this myself at first and it looks like the function name is a leftover from that. Probably not the right place for this either now that I think about it, that should be somewhere in the Netplay code...

@AdmiralCurtiss
Copy link
Contributor Author

Alright, redid this a bit so the staging directory is deleted in a callback set from the Netplay code. I think this is a lot cleaner!

@leoetlino leoetlino self-requested a review November 22, 2021 01:22
Copy link
Member

@leoetlino leoetlino left a comment

Choose a reason for hiding this comment

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

Reviewed 12 of 12 files at r5, 5 of 5 files at r6, 5 of 5 files at r7, 6 of 6 files at r8, all commit messages.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on @AdmiralCurtiss)

@leoetlino leoetlino merged commit ba62019 into dolphin-emu:master Nov 23, 2021
10 checks passed
@AdmiralCurtiss AdmiralCurtiss deleted the netplay-save-sync-boot branch November 23, 2021 15:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
3 participants