aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSssnowyyy <78863999+Sssnowyyy@users.noreply.github.com>2026-11-02 11:22:23 -0500
committerSssnowyyy <78863999+Sssnowyyy@users.noreply.github.com>2026-11-02 14:53:47 -0500
commita3d67c14c86f57ec12787db0b8bb058ec8b5f73c (patch)
treeba9d1d9396927fc6d3d9bf481ffc9f24b5aa24a5
parentc83eb1d1f5c14d0842a165de431ac6367dff2909 (diff)
frontend: bound the wait for the emulation thread at shutdown
426f120ee8 dropped the 10 s wait and terminate() fallback when stopping emulation, and ShutdownGame and closeEvent now wait for the emulation thread with no limit. If the thread hangs (for example a GPU or present deadlock), stopping a game, switching games or quitting never completes. Keep the asynchronous teardown, but once the thread has been asked to stop, give it 10 s and then terminate it and finish cleanup, as before. ShutdownGame's nested event loop gets the same bound. A terminated thread is logged as an error. Assisted-by: Claude Sonnet 5.5 (implementation and commit message)
-rw-r--r--src/yuzu/main_window.cpp31
-rw-r--r--src/yuzu/main_window.h3
2 files changed, 30 insertions, 4 deletions
diff --git a/src/yuzu/main_window.cpp b/src/yuzu/main_window.cpp
index e5a8f81424..02b36cf9cf 100644
--- a/src/yuzu/main_window.cpp
+++ b/src/yuzu/main_window.cpp
@@ -320,6 +320,7 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
constexpr int default_mouse_hide_timeout = 2500;
constexpr int default_input_update_timeout = 1;
+constexpr int EmuThreadStopTimeoutMs = 10'000;
constexpr size_t CopyBufferSize = 1_MiB;
@@ -2209,6 +2210,23 @@ void MainWindow::OnShutdownBeginDialog() {
void MainWindow::OnEmulationStopTimeExpired() {
if (QtCommon::emu_thread) {
QtCommon::emu_thread->ForceStop();
+ emu_terminate_timer.setSingleShot(true);
+ emu_terminate_timer.start(EmuThreadStopTimeoutMs);
+ connect(&emu_terminate_timer, &QTimer::timeout, this, &MainWindow::OnEmulationStopTimeout,
+ Qt::UniqueConnection);
+ }
+}
+
+void MainWindow::OnEmulationStopTimeout() {
+ TerminateEmulationThread();
+ OnEmulationStopped();
+}
+
+void MainWindow::TerminateEmulationThread() {
+ if (QtCommon::emu_thread && QtCommon::emu_thread->isRunning()) {
+ LOG_ERROR(Frontend, "Emulation thread failed to stop, terminating it");
+ QtCommon::emu_thread->terminate();
+ QtCommon::emu_thread->wait();
}
}
@@ -2220,6 +2238,7 @@ void MainWindow::OnEmulationStopped() {
return;
}
shutdown_timer.stop();
+ emu_terminate_timer.stop();
if (QtCommon::emu_thread) {
QtCommon::emu_thread->disconnect();
QtCommon::emu_thread->wait();
@@ -2309,13 +2328,17 @@ void MainWindow::ShutdownGame() {
play_time_manager->Stop();
OnShutdownBegin();
OnEmulationStopTimeExpired();
- if (QtCommon::emu_thread) {
+ emu_terminate_timer.stop();
+ if (QtCommon::emu_thread && QtCommon::emu_thread->isRunning()) {
QEventLoop loop;
+ QTimer timeout;
+ timeout.setSingleShot(true);
connect(QtCommon::emu_thread.get(), &QThread::finished, &loop, &QEventLoop::quit);
- if (QtCommon::emu_thread->isRunning()) {
- loop.exec(QEventLoop::ExcludeUserInputEvents);
- }
+ connect(&timeout, &QTimer::timeout, &loop, &QEventLoop::quit);
+ timeout.start(EmuThreadStopTimeoutMs);
+ loop.exec(QEventLoop::ExcludeUserInputEvents);
}
+ TerminateEmulationThread();
OnEmulationStopped();
}
diff --git a/src/yuzu/main_window.h b/src/yuzu/main_window.h
index e2c5a0f6db..a3b030a6a6 100644
--- a/src/yuzu/main_window.h
+++ b/src/yuzu/main_window.h
@@ -279,6 +279,7 @@ private:
StartGameType with_config = StartGameType::Normal);
void BootGameFromList(const QString& filename, StartGameType with_config);
void ShutdownGame();
+ void TerminateEmulationThread();
void SetDiscordEnabled(bool state);
void LoadAmiibo(const QString& filename);
@@ -437,6 +438,7 @@ private slots:
void OnShutdownBeginDialog();
void OnEmulationStopped();
void OnEmulationStopTimeExpired();
+ void OnEmulationStopTimeout();
#ifdef ENABLE_UPDATE_CHECKER
void OnEmulatorUpdateAvailable();
@@ -506,6 +508,7 @@ private:
GameList* game_list = nullptr;
LoadingScreen* loading_screen = nullptr;
QTimer shutdown_timer;
+ QTimer emu_terminate_timer;
OverlayDialog* shutdown_dialog{};
PerformanceOverlay* perf_overlay = nullptr;
#ifdef HAS_RESHADE