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

Don't call RunAsCPUThread in config callbacks #12117

Merged
merged 5 commits into from Aug 26, 2023

Conversation

JosJuice
Copy link
Member

In theory, our config system supports calling Set from any thread. But because we have config callbacks that call RunAsCPUThread, it's a lot more restricted in practice. Calling Set from any thread other than the host thread or the CPU thread is formally thread unsafe, and calling Set on the host thread while the CPU thread is showing a panic alert causes a deadlock. This is especially a problem because 04072f0 made the "Ignore for this session" button in panic alerts call Set.

Because so many of our config callbacks want their code to run on the CPU thread, I thought it would make sense to have a centralized way to move execution to the CPU thread for config callbacks. To solve the deadlock problem, this new way is non-blocking. This means that threads other than the CPU thread might continue executing before the CPU thread is informed of the new config, but I don't think there's any problem with that.

Intends to fix https://bugs.dolphin-emu.org/issues/13108.

@JosJuice JosJuice force-pushed the config-callback-cpu branch 3 times, most recently from de1bb23 to f6ce897 Compare August 16, 2023 21:32
@JosJuice
Copy link
Member Author

@iwubcode FreelookConfig.cpp has a config changed callback that uses RunAsCPUThread in master, but as far as I can tell, this class initialized on the host thread and then accessed on the GPU thread and the hotkey scheduler thread, so it doesn't really have anything to do with the CPU thread. Do you know why this code is using RunAsCPUThread? And also, why FreeLookConfig.cpp is using the callback system at all instead of fetching values from the config system when needed?

@iwubcode
Copy link
Contributor

@JosJuice - that code followed the same design principles as VideoConfig because the two are very similar in nature (though I don't recall the comment about VerifyValidity). That is why it is like that.

As for why it does that at all, there was a time when config access was deemed bad practice in the realtime path. I know you made changes to that but I still out of habit will avoid accessing config when possible. To be fair, this code was written way before that change was made.

@JosJuice
Copy link
Member Author

I think I'll just allow threads other than the CPU thread to register callbacks. (See the last commit.)

@AdmiralCurtiss Do you have any opinion on this PR in general? I know you were working on addressing some of the other thread safety issues of config callbacks.

@@ -37,7 +39,7 @@ void Config::Refresh()
{
if (!s_has_registered_callback)
{
::Config::AddConfigChangedCallback([] { Core::RunAsCPUThread([] { s_config.Refresh(); }); });
CPUThreadConfigCallback::AddConfigChangedCallback([] { s_config.Refresh(); });
Copy link
Contributor

@iwubcode iwubcode Aug 17, 2023

Choose a reason for hiding this comment

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

Just to be safe, this likely needs the same complexity involved as in the VideoConfig as that is what the FreeLookConfig was patterned after. FreeLook is this weird blend of inputs (the controller) and graphics (the "camera") and the config is used by both systems.

Copy link
Member Author

@JosJuice JosJuice Aug 17, 2023

Choose a reason for hiding this comment

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

The reason why it was added for VideoConfig was to stop the GPU thread from reading the config before VerifyValidity ran. There's nothing like VerifyValidity here, so we don't have the same problem.

I suppose that if we are to standards lawyer this, this is technically unsafe because there could be tearing, as there's no synchronization between the CPU thread writing and the GPU thread reading. But since every value involved is 32-bit at most, we're safe on every CPU we have first-class support for. If we are to do something about it, I think it would be better addressed by something simpler like a mutex, and this is far from the only config callback in the code base with this problem anyway.

In theory, our config system supports calling Set from any thread. But
because we have config callbacks that call RunAsCPUThread, it's a lot
more restricted in practice. Calling Set from any thread other than the
host thread or the CPU thread is formally thread unsafe, and calling Set
on the host thread while the CPU thread is showing a panic alert causes
a deadlock. This is especially a problem because 04072f0 made the
"Ignore for this session" button in panic alerts call Set.

Because so many of our config callbacks want their code to run on the
CPU thread, I thought it would make sense to have a centralized way to
move execution to the CPU thread for config callbacks. To solve the
deadlock problem, this new way is non-blocking. This means that threads
other than the CPU thread might continue executing before the CPU thread
is informed of the new config, but I don't think there's any problem
with that.

Intends to fix https://bugs.dolphin-emu.org/issues/13108.
This fixes a problem that started happening in CoreTimingTest after the
previous commit. CPUThreadConfigCallback registers a Config callback
only once per run of the process, but CoreTimingTest calls
Config::Shutdown after each test, and Config::Shutdown was clearing all
callbacks, preventing the callback from running after that.
This fixes a bunch of DEBUG_ASSERTs in the unit tests.
This way you can't mix up regular config callback IDs and CPU thread
config callback IDs. (It would be rather bad if you did!)
Turns out that we have two subsystems that want to register CPU thread
callbacks from a different thread than the CPU thread: FreeLookConfig
and VideoConfig. Both seem to happen from the host thread before the CPU
thread starts, so there's no thread safety issue. But ideally, if we
want to allow registering callbacks from threads other than the CPU
thread, we should make registering callbacks actually thread-safe. This
is an unsolved problem for the regular Config system, so I would like to
leave it outside the scope of this PR.
@AdmiralCurtiss
Copy link
Contributor

I'm a bit conflicted, this is adding complexity and threading is usually not improved by adding complexity. However, we definitely have a problem with the callbacks on master, so if this resolves some of them, I guess that's okay.

@AdmiralCurtiss
Copy link
Contributor

AdmiralCurtiss commented Aug 22, 2023

Okay, after thinking about this a bit more, I think this is probably an improvement, though I have one question: If emulation is paused, should the updates apply immediately or should they still be delayed until the first CoreTiming::Advance()? This might be confusing if you're debugging a game and your config changes get delayed until an unknown point in the future.

e: Wait, the checks in CPUManager::Run() take care of that, don't they? Yeah okay, I think this is good then.

@JosJuice
Copy link
Member Author

Wait, the checks in CPUManager::Run() take care of that, don't they?

Yeah, that was my intent with those checks.

@AdmiralCurtiss
Copy link
Contributor

Launched a few games and messed with settings, seems to work as expected.

@AdmiralCurtiss AdmiralCurtiss merged commit 2502e41 into dolphin-emu:master Aug 26, 2023
11 checks passed
@JosJuice JosJuice deleted the config-callback-cpu branch August 26, 2023 14:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
3 participants