Merge pull request #8763 from JosJuice/panic-alert-deadlock-gpu

DolphinQt: Fix the panic alert deadlock, dual core edition
This commit is contained in:
Admiral H. Curtiss 2022-05-16 02:21:14 +02:00 committed by GitHub
commit b10808d815
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 83 additions and 25 deletions

View File

@ -127,6 +127,7 @@ static std::queue<HostJob> s_host_jobs_queue;
static Common::Event s_cpu_thread_job_finished; static Common::Event s_cpu_thread_job_finished;
static thread_local bool tls_is_cpu_thread = false; static thread_local bool tls_is_cpu_thread = false;
static thread_local bool tls_is_gpu_thread = false;
static void EmuThread(std::unique_ptr<BootParameters> boot, WindowSystemInfo wsi); static void EmuThread(std::unique_ptr<BootParameters> boot, WindowSystemInfo wsi);
@ -203,14 +204,7 @@ bool IsCPUThread()
bool IsGPUThread() bool IsGPUThread()
{ {
if (Core::System::GetInstance().IsDualCoreMode()) return tls_is_gpu_thread;
{
return (s_emu_thread.joinable() && (s_emu_thread.get_id() == std::this_thread::get_id()));
}
else
{
return IsCPUThread();
}
} }
bool WantsDeterminism() bool WantsDeterminism()
@ -313,6 +307,16 @@ void UndeclareAsCPUThread()
tls_is_cpu_thread = false; tls_is_cpu_thread = false;
} }
void DeclareAsGPUThread()
{
tls_is_gpu_thread = true;
}
void UndeclareAsGPUThread()
{
tls_is_gpu_thread = false;
}
// For the CPU Thread only. // For the CPU Thread only.
static void CPUSetInitialExecutionState(bool force_paused = false) static void CPUSetInitialExecutionState(bool force_paused = false)
{ {
@ -459,6 +463,8 @@ static void EmuThread(std::unique_ptr<BootParameters> boot, WindowSystemInfo wsi
Common::SetCurrentThreadName("Emuthread - Starting"); Common::SetCurrentThreadName("Emuthread - Starting");
DeclareAsGPUThread();
// For a time this acts as the CPU thread... // For a time this acts as the CPU thread...
DeclareAsCPUThread(); DeclareAsCPUThread();
s_frame_step = false; s_frame_step = false;

View File

@ -98,6 +98,8 @@ void Shutdown();
void DeclareAsCPUThread(); void DeclareAsCPUThread();
void UndeclareAsCPUThread(); void UndeclareAsCPUThread();
void DeclareAsGPUThread();
void UndeclareAsGPUThread();
std::string StopMessage(bool main_thread, std::string_view message); std::string StopMessage(bool main_thread, std::string_view message);

View File

@ -3,6 +3,8 @@
#include "DolphinQt/Host.h" #include "DolphinQt/Host.h"
#include <functional>
#include <QAbstractEventDispatcher> #include <QAbstractEventDispatcher>
#include <QApplication> #include <QApplication>
#include <QLocale> #include <QLocale>
@ -34,6 +36,7 @@
#include "UICommon/DiscordPresence.h" #include "UICommon/DiscordPresence.h"
#include "VideoCommon/Fifo.cpp"
#include "VideoCommon/RenderBase.h" #include "VideoCommon/RenderBase.h"
#include "VideoCommon/VideoConfig.h" #include "VideoCommon/VideoConfig.h"
@ -85,6 +88,44 @@ void Host::SetMainWindowHandle(void* handle)
m_main_window_handle = handle; m_main_window_handle = handle;
} }
static void RunWithGPUThreadInactive(std::function<void()> f)
{
// Potentially any thread which shows panic alerts can be blocked on this returning.
// This means that, in order to avoid deadlocks, we need to be careful with how we
// synchronize with other threads. Note that the panic alert handler temporarily declares
// us as the CPU and/or GPU thread if the panic alert was requested by that thread.
// TODO: What about the unlikely case where the GPU thread calls the panic alert handler
// while the panic alert handler is processing a panic alert from the CPU thread?
if (Core::IsGPUThread())
{
// If we are the GPU thread, we can't call Core::PauseAndLock without getting a deadlock,
// since it would try to pause the GPU thread while that thread is waiting for us.
// However, since we know that the GPU thread is inactive, we can just run f directly.
f();
}
else if (Core::IsCPUThread())
{
// If we are the CPU thread in dual core mode, we can't call Core::PauseAndLock, for the
// same reason as above. Instead, we use Fifo::PauseAndLock to pause the GPU thread only.
// (Note that this case cannot be reached in single core mode, because in single core mode,
// the CPU and GPU threads are the same thread, and we already checked for the GPU thread.)
const bool was_running = Core::GetState() == Core::State::Running;
Fifo::PauseAndLock(true, was_running);
f();
Fifo::PauseAndLock(false, was_running);
}
else
{
// If we reach here, we can call Core::PauseAndLock (which we do using RunAsCPUThread).
Core::RunAsCPUThread(std::move(f));
}
}
bool Host::GetRenderFocus() bool Host::GetRenderFocus()
{ {
#ifdef _WIN32 #ifdef _WIN32
@ -107,11 +148,13 @@ void Host::SetRenderFocus(bool focus)
{ {
m_render_focus = focus; m_render_focus = focus;
if (g_renderer && m_render_fullscreen && g_ActiveConfig.ExclusiveFullscreenEnabled()) if (g_renderer && m_render_fullscreen && g_ActiveConfig.ExclusiveFullscreenEnabled())
Core::RunAsCPUThread([focus] { {
RunWithGPUThreadInactive([focus] {
if (!Config::Get(Config::MAIN_RENDER_TO_MAIN)) if (!Config::Get(Config::MAIN_RENDER_TO_MAIN))
g_renderer->SetFullscreen(focus); g_renderer->SetFullscreen(focus);
}); });
} }
}
void Host::SetRenderFullFocus(bool focus) void Host::SetRenderFullFocus(bool focus)
{ {
@ -138,7 +181,9 @@ void Host::SetRenderFullscreen(bool fullscreen)
if (g_renderer && g_renderer->IsFullscreen() != fullscreen && if (g_renderer && g_renderer->IsFullscreen() != fullscreen &&
g_ActiveConfig.ExclusiveFullscreenEnabled()) g_ActiveConfig.ExclusiveFullscreenEnabled())
Core::RunAsCPUThread([fullscreen] { g_renderer->SetFullscreen(fullscreen); }); {
RunWithGPUThreadInactive([fullscreen] { g_renderer->SetFullscreen(fullscreen); });
}
} }
void Host::ResizeSurface(int new_width, int new_height) void Host::ResizeSurface(int new_width, int new_height)

View File

@ -41,21 +41,26 @@ static bool QtMsgAlertHandler(const char* caption, const char* text, bool yes_no
Common::MsgType style) Common::MsgType style)
{ {
const bool called_from_cpu_thread = Core::IsCPUThread(); const bool called_from_cpu_thread = Core::IsCPUThread();
const bool called_from_gpu_thread = Core::IsGPUThread();
std::optional<bool> r = RunOnObject(QApplication::instance(), [&] { std::optional<bool> r = RunOnObject(QApplication::instance(), [&] {
Common::ScopeGuard scope_guard(&Core::UndeclareAsCPUThread); // If we were called from the CPU/GPU thread, set us as the CPU/GPU thread.
// This information is used in order to avoid deadlocks when calling e.g.
// Host::SetRenderFocus or Core::RunAsCPUThread. (Host::SetRenderFocus
// can get called automatically when a dialog steals the focus.)
Common::ScopeGuard cpu_scope_guard(&Core::UndeclareAsCPUThread);
Common::ScopeGuard gpu_scope_guard(&Core::UndeclareAsGPUThread);
if (!called_from_cpu_thread)
cpu_scope_guard.Dismiss();
if (!called_from_gpu_thread)
gpu_scope_guard.Dismiss();
if (called_from_cpu_thread) if (called_from_cpu_thread)
{
// Temporarily declare this as the CPU thread to avoid getting a deadlock if any DolphinQt
// code calls RunAsCPUThread while the CPU thread is blocked on this function returning.
// Notably, if the panic alert steals focus from RenderWidget, Host::SetRenderFocus gets
// called, which can attempt to use RunAsCPUThread to get us out of exclusive fullscreen.
Core::DeclareAsCPUThread(); Core::DeclareAsCPUThread();
} if (called_from_gpu_thread)
else Core::DeclareAsGPUThread();
{
scope_guard.Dismiss();
}
ModalMessageBox message_box(QApplication::activeWindow(), Qt::ApplicationModal); ModalMessageBox message_box(QApplication::activeWindow(), Qt::ApplicationModal);
message_box.setWindowTitle(QString::fromUtf8(caption)); message_box.setWindowTitle(QString::fromUtf8(caption));

View File

@ -421,10 +421,10 @@ bool RenderWidget::event(QEvent* event)
if (Config::Get(Config::MAIN_PAUSE_ON_FOCUS_LOST) && Core::GetState() == Core::State::Running) if (Config::Get(Config::MAIN_PAUSE_ON_FOCUS_LOST) && Core::GetState() == Core::State::Running)
{ {
// If we are declared as the CPU thread, it means that the real CPU thread is waiting // If we are declared as the CPU or GPU thread, it means that the real CPU or GPU thread
// for us to finish showing a panic alert (with that panic alert likely being the cause // is waiting for us to finish showing a panic alert (with that panic alert likely being
// of this event), so trying to pause the real CPU thread would cause a deadlock // the cause of this event), so trying to pause the core would cause a deadlock
if (!Core::IsCPUThread()) if (!Core::IsCPUThread() && !Core::IsGPUThread())
Core::SetState(Core::State::Paused); Core::SetState(Core::State::Paused);
} }