From 8203ea929b5981b8413c342e6bbfb7467f3c2a0d Mon Sep 17 00:00:00 2001 From: Jules Blok Date: Thu, 10 Nov 2016 17:55:21 +0100 Subject: [PATCH 1/3] BlockingLoop: Yield to UI message pump while waiting. --- Source/Android/jni/MainAndroid.cpp | 4 ++++ Source/Core/Common/BlockingLoop.h | 27 +++++++++++++++++++++++++ Source/Core/Core/Host.h | 1 + Source/Core/DolphinQt2/Host.cpp | 3 +++ Source/Core/DolphinWX/Main.cpp | 6 ++++++ Source/Core/DolphinWX/MainNoGUI.cpp | 4 ++++ Source/Core/VideoCommon/Fifo.cpp | 9 ++++++++- Source/UnitTests/TestUtils/StubHost.cpp | 3 +++ 8 files changed, 56 insertions(+), 1 deletion(-) diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index d2fdf30bb6..4cd975fae8 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -148,6 +148,10 @@ void Host_ShowVideoConfig(void*, const std::string&) { } +void Host_YieldToUI() +{ +} + static bool MsgAlert(const char* caption, const char* text, bool yes_no, int /*Style*/) { __android_log_print(ANDROID_LOG_ERROR, DOLPHIN_TAG, "%s:%s", caption, text); diff --git a/Source/Core/Common/BlockingLoop.h b/Source/Core/Common/BlockingLoop.h index da148fccd4..a1ebe7d708 100644 --- a/Source/Core/Common/BlockingLoop.h +++ b/Source/Core/Common/BlockingLoop.h @@ -66,6 +66,33 @@ public: m_may_sleep.Set(); } + // Wait for a complete payload run after the last Wakeup() call. + // This version will call a yield function every 100ms. + // If stopped, this returns immediately. + template + void WaitYield(const std::chrono::duration& rel_time, Functor yield_func) + { + // already done + if (IsDone()) + return; + + // notifying this event will only wake up one thread, so use a mutex here to + // allow only one waiting thread. And in this way, we get an event free wakeup + // but for the first thread for free + std::lock_guard lk(m_wait_lock); + + // Wait for the worker thread to finish. + while (!IsDone()) + { + if (!m_done_event.WaitFor(rel_time)) + yield_func(); + } + + // As we wanted to wait for the other thread, there is likely no work remaining. + // So there is no need for a busy loop any more. + m_may_sleep.Set(); + } + // Half start the worker. // So this object is in a running state and Wait() will block until the worker calls Run(). // This may be called from any thread and is supposed to be called at least once before Wait() is diff --git a/Source/Core/Core/Host.h b/Source/Core/Core/Host.h index c739f0dca1..2f517c4c8e 100644 --- a/Source/Core/Core/Host.h +++ b/Source/Core/Core/Host.h @@ -38,6 +38,7 @@ void Host_UpdateDisasmDialog(); void Host_UpdateMainFrame(); void Host_UpdateTitle(const std::string& title); void Host_ShowVideoConfig(void* parent, const std::string& backend_name); +void Host_YieldToUI(); // TODO (neobrain): Remove this from host! void* Host_GetRenderHandle(); diff --git a/Source/Core/DolphinQt2/Host.cpp b/Source/Core/DolphinQt2/Host.cpp index 62af45b489..0652942dfe 100644 --- a/Source/Core/DolphinQt2/Host.cpp +++ b/Source/Core/DolphinQt2/Host.cpp @@ -89,6 +89,9 @@ bool Host_RendererIsFullscreen() { return Host::GetInstance()->GetRenderFullscreen(); } +void Host_YieldToUI() +{ +} // We ignore these, and their purpose should be questioned individually. // In particular, RequestRenderWindowSize, RequestFullscreen, and diff --git a/Source/Core/DolphinWX/Main.cpp b/Source/Core/DolphinWX/Main.cpp index 871e89ad28..444b270d39 100644 --- a/Source/Core/DolphinWX/Main.cpp +++ b/Source/Core/DolphinWX/Main.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -547,3 +548,8 @@ void Host_ShowVideoConfig(void* parent, const std::string& backend_name) diag.ShowModal(); } } + +void Host_YieldToUI() +{ + wxGetApp().GetMainLoop()->YieldFor(wxEVT_CATEGORY_UI); +} diff --git a/Source/Core/DolphinWX/MainNoGUI.cpp b/Source/Core/DolphinWX/MainNoGUI.cpp index 216eb5a707..6c8a53b88d 100644 --- a/Source/Core/DolphinWX/MainNoGUI.cpp +++ b/Source/Core/DolphinWX/MainNoGUI.cpp @@ -161,6 +161,10 @@ void Host_ShowVideoConfig(void*, const std::string&) { } +void Host_YieldToUI() +{ +} + #if HAVE_X11 #include #include "DolphinWX/X11Utils.h" diff --git a/Source/Core/VideoCommon/Fifo.cpp b/Source/Core/VideoCommon/Fifo.cpp index 374ddb07f3..678dc77687 100644 --- a/Source/Core/VideoCommon/Fifo.cpp +++ b/Source/Core/VideoCommon/Fifo.cpp @@ -18,6 +18,7 @@ #include "Core/CoreTiming.h" #include "Core/HW/Memmap.h" #include "Core/HW/SystemTimers.h" +#include "Core/Host.h" #include "Core/NetPlayProto.h" #include "VideoCommon/AsyncRequests.h" @@ -94,7 +95,13 @@ void PauseAndLock(bool doLock, bool unpauseOnUnlock) { SyncGPU(SyncGPUReason::Other); EmulatorState(false); - FlushGpu(); + + const SConfig& param = SConfig::GetInstance(); + + if (!param.bCPUThread || s_use_deterministic_gpu_thread) + return; + + s_gpu_mainloop.WaitYield(std::chrono::milliseconds(100), Host_YieldToUI); } else { diff --git a/Source/UnitTests/TestUtils/StubHost.cpp b/Source/UnitTests/TestUtils/StubHost.cpp index c6d1cbb12e..e32561da90 100644 --- a/Source/UnitTests/TestUtils/StubHost.cpp +++ b/Source/UnitTests/TestUtils/StubHost.cpp @@ -63,6 +63,9 @@ void Host_SetWiiMoteConnectionState(int) void Host_ShowVideoConfig(void*, const std::string&) { } +void Host_YieldToUI() +{ +} std::unique_ptr HostGL_CreateGLInterface() { return nullptr; From 05a3f927ee60b90c4de193294b2b58e2f45b6eed Mon Sep 17 00:00:00 2001 From: Jules Blok Date: Thu, 10 Nov 2016 23:40:05 +0100 Subject: [PATCH 2/3] CPU: Fix deadlocks by periodically yielding to the UI message pump. --- Source/Core/Core/HW/CPU.cpp | 40 +++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/Source/Core/Core/HW/CPU.cpp b/Source/Core/Core/HW/CPU.cpp index 2c40689c9e..a790995f70 100644 --- a/Source/Core/Core/HW/CPU.cpp +++ b/Source/Core/Core/HW/CPU.cpp @@ -160,11 +160,15 @@ void Stop() std::unique_lock state_lock(s_state_change_lock); s_state = CPU_POWERDOWN; s_state_cpu_cvar.notify_one(); - // FIXME: MsgHandler can cause this to deadlock the GUI Thread. Remove the timeout. - bool success = s_state_cpu_idle_cvar.wait_for(state_lock, std::chrono::seconds(5), - [] { return !s_state_cpu_thread_active; }); - if (!success) - ERROR_LOG(POWERPC, "CPU Thread failed to acknowledge CPU_POWERDOWN. It may be deadlocked."); + + while (s_state_cpu_thread_active) + { + std::cv_status status = + s_state_cpu_idle_cvar.wait_for(state_lock, std::chrono::milliseconds(100)); + if (status == std::cv_status::timeout) + Host_YieldToUI(); + } + RunAdjacentSystems(false); FlushStepSyncEventLocked(); } @@ -226,12 +230,13 @@ void EnableStepping(bool stepping) { SetStateLocked(CPU_STEPPING); - // Wait for the CPU Thread to leave the run loop - // FIXME: MsgHandler can cause this to deadlock the GUI Thread. Remove the timeout. - bool success = s_state_cpu_idle_cvar.wait_for(state_lock, std::chrono::seconds(5), - [] { return !s_state_cpu_thread_active; }); - if (!success) - ERROR_LOG(POWERPC, "Abandoned waiting for CPU Thread! The Core may be deadlocked."); + while (s_state_cpu_thread_active) + { + std::cv_status status = + s_state_cpu_idle_cvar.wait_for(state_lock, std::chrono::milliseconds(100)); + if (status == std::cv_status::timeout) + Host_YieldToUI(); + } RunAdjacentSystems(false); } @@ -276,12 +281,13 @@ bool PauseAndLock(bool do_lock, bool unpause_on_unlock, bool control_adjacent) was_unpaused = s_state == CPU_RUNNING; SetStateLocked(CPU_STEPPING); - // FIXME: MsgHandler can cause this to deadlock the GUI Thread. Remove the timeout. - bool success = s_state_cpu_idle_cvar.wait_for(state_lock, std::chrono::seconds(10), - [] { return !s_state_cpu_thread_active; }); - if (!success) - PanicAlert( - "Abandoned CPU Thread synchronization in CPU::PauseAndLock! We'll probably crash now."); + while (s_state_cpu_thread_active) + { + std::cv_status status = + s_state_cpu_idle_cvar.wait_for(state_lock, std::chrono::milliseconds(100)); + if (status == std::cv_status::timeout) + Host_YieldToUI(); + } if (control_adjacent) RunAdjacentSystems(false); From 20d49ba97f14649c459f5311bf6325daebfa382a Mon Sep 17 00:00:00 2001 From: Jules Blok Date: Fri, 11 Nov 2016 13:39:50 +0100 Subject: [PATCH 3/3] DolphinQt2: Implement Host_YieldToUI(). --- Source/Core/DolphinQt2/Host.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Core/DolphinQt2/Host.cpp b/Source/Core/DolphinQt2/Host.cpp index 0652942dfe..906853bc3c 100644 --- a/Source/Core/DolphinQt2/Host.cpp +++ b/Source/Core/DolphinQt2/Host.cpp @@ -91,6 +91,7 @@ bool Host_RendererIsFullscreen() } void Host_YieldToUI() { + qApp->processEvents(QEventLoop::ExcludeUserInputEvents); } // We ignore these, and their purpose should be questioned individually.