Merge pull request 'fix: Race Condition w/ Shutdown Logic' (#130) from fix/sigsegv into main

Reviewed-on: https://git.citron-emu.org/Citron/Emulator/pulls/130
This commit is contained in:
Collecting
2026-02-09 01:39:11 +01:00
7 changed files with 99 additions and 33 deletions

View File

@@ -2144,6 +2144,13 @@ void GameList::RefreshGameDirectory() {
} }
} }
void GameList::CancelPopulation() {
if (current_worker) {
current_worker->Cancel();
}
current_worker.reset();
}
void GameList::ToggleFavorite(u64 program_id) { void GameList::ToggleFavorite(u64 program_id) {
if (!UISettings::values.favorited_ids.contains(program_id)) { if (!UISettings::values.favorited_ids.contains(program_id)) {
tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(), tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(),

View File

@@ -27,7 +27,6 @@
#include <QVector> #include <QVector>
#include <QWidget> #include <QWidget>
#include "citron/compatibility_list.h" #include "citron/compatibility_list.h"
#include "citron/multiplayer/state.h" #include "citron/multiplayer/state.h"
#include "citron/play_time_manager.h" #include "citron/play_time_manager.h"
@@ -35,7 +34,6 @@
#include "core/core.h" #include "core/core.h"
#include "uisettings.h" #include "uisettings.h"
class ControllerNavigation; class ControllerNavigation;
class GameListWorker; class GameListWorker;
class GameListSearchField; class GameListSearchField;
@@ -107,6 +105,7 @@ public:
void LoadCompatibilityList(); void LoadCompatibilityList();
void PopulateAsync(QVector<UISettings::GameDir>& game_dirs); void PopulateAsync(QVector<UISettings::GameDir>& game_dirs);
void CancelPopulation();
void SaveInterfaceLayout(); void SaveInterfaceLayout();
void LoadInterfaceLayout(); void LoadInterfaceLayout();

View File

@@ -21,7 +21,6 @@
#include <QSettings> #include <QSettings>
#include <QStandardPaths> #include <QStandardPaths>
#include "citron/compatibility_list.h" #include "citron/compatibility_list.h"
#include "citron/game_list.h" #include "citron/game_list.h"
#include "citron/game_list_p.h" #include "citron/game_list_p.h"
@@ -40,7 +39,6 @@
#include "core/file_sys/submission_package.h" #include "core/file_sys/submission_package.h"
#include "core/loader/loader.h" #include "core/loader/loader.h"
namespace { namespace {
// Structure to hold cached game metadata // Structure to hold cached game metadata
@@ -485,6 +483,11 @@ GameListWorker::~GameListWorker() {
processing_completed.Wait(); processing_completed.Wait();
} }
void GameListWorker::Cancel() {
this->disconnect();
stop_requested.store(true);
}
void GameListWorker::ProcessEvents(GameList* game_list) { void GameListWorker::ProcessEvents(GameList* game_list) {
while (true) { while (true) {
std::function<void(GameList*)> func; std::function<void(GameList*)> func;
@@ -542,6 +545,10 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir,
} }
for (const auto& [slot, game] : installed_games) { for (const auto& [slot, game] : installed_games) {
if (stop_requested) {
break;
}
if (slot == ContentProviderUnionSlot::FrontendManual) { if (slot == ContentProviderUnionSlot::FrontendManual) {
continue; continue;
} }
@@ -580,10 +587,16 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
const std::map<u64, std::pair<int, int>>& online_stats, const std::map<u64, std::pair<int, int>>& online_stats,
int& processed_files, int total_files) { int& processed_files, int total_files) {
const auto callback = [this, target, parent_dir, &online_stats, &processed_files, const auto callback = [this, target, parent_dir, &online_stats, &processed_files,
total_files](const std::filesystem::path& path) -> bool { total_files](const std::filesystem::directory_entry& dir_entry) -> bool {
if (stop_requested) if (stop_requested) {
return false; return false;
}
if (dir_entry.is_directory()) {
return true;
}
const auto& path = dir_entry.path();
const auto physical_name = Common::FS::PathToUTF8String(path); const auto physical_name = Common::FS::PathToUTF8String(path);
if (physical_name.find("/nand/") != std::string::npos || if (physical_name.find("/nand/") != std::string::npos ||
@@ -714,7 +727,7 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
if (deep_scan) { if (deep_scan) {
Common::FS::IterateDirEntriesRecursively(dir_path, callback, Common::FS::IterateDirEntriesRecursively(dir_path, callback,
Common::FS::DirEntryFilter::File); Common::FS::DirEntryFilter::All);
} else { } else {
Common::FS::IterateDirEntries(dir_path, callback, Common::FS::DirEntryFilter::File); Common::FS::IterateDirEntries(dir_path, callback, Common::FS::DirEntryFilter::File);
} }
@@ -745,7 +758,16 @@ void GameListWorker::run() {
if (game_dir.path == "SDMC" || game_dir.path == "UserNAND" || game_dir.path == "SysNAND") if (game_dir.path == "SDMC" || game_dir.path == "UserNAND" || game_dir.path == "SysNAND")
continue; continue;
auto count_callback = [&](const std::filesystem::path& path) -> bool { auto count_callback = [&](const std::filesystem::directory_entry& dir_entry) -> bool {
if (stop_requested) {
return false;
}
if (dir_entry.is_directory()) {
return true;
}
const auto& path = dir_entry.path();
const std::string physical_name = Common::FS::PathToUTF8String(path); const std::string physical_name = Common::FS::PathToUTF8String(path);
if (HasSupportedFileExtension(physical_name)) { if (HasSupportedFileExtension(physical_name)) {
total_files++; total_files++;
@@ -755,7 +777,7 @@ void GameListWorker::run() {
if (game_dir.deep_scan) { if (game_dir.deep_scan) {
Common::FS::IterateDirEntriesRecursively(game_dir.path, count_callback, Common::FS::IterateDirEntriesRecursively(game_dir.path, count_callback,
Common::FS::DirEntryFilter::File); Common::FS::DirEntryFilter::All);
} else { } else {
Common::FS::IterateDirEntries(game_dir.path, count_callback, Common::FS::IterateDirEntries(game_dir.path, count_callback,
Common::FS::DirEntryFilter::File); Common::FS::DirEntryFilter::File);

View File

@@ -6,24 +6,26 @@
#include <atomic> #include <atomic>
#include <deque> #include <deque>
#include <map> // Required for the online_stats map
#include <memory> #include <memory>
#include <string> #include <string>
#include <map> // Required for the online_stats map
#include <utility> // Required for std::pair #include <utility> // Required for std::pair
#include <QList> #include <QList>
#include <QObject> #include <QObject>
#include <QRunnable> #include <QRunnable>
#include <QString> #include <QString>
#include "common/thread.h"
#include "citron/compatibility_list.h" #include "citron/compatibility_list.h"
#include "citron/play_time_manager.h"
#include "citron/multiplayer/state.h" #include "citron/multiplayer/state.h"
#include "citron/play_time_manager.h"
#include "common/thread.h"
#include "network/announce_multiplayer_session.h" #include "network/announce_multiplayer_session.h"
namespace Core { namespace Core {
class System; class System;
} }
class GameList; class GameList;
@@ -31,9 +33,9 @@ class GameListDir; // Forward declare GameListDir
class QStandardItem; class QStandardItem;
namespace FileSys { namespace FileSys {
class NCA; class NCA;
class VfsFilesystem; class VfsFilesystem;
class ManualContentProvider; class ManualContentProvider;
} // namespace FileSys } // namespace FileSys
/** /**
@@ -62,6 +64,9 @@ public:
/// Starts the processing of directory tree information. /// Starts the processing of directory tree information.
void run() override; void run() override;
/// Request the worker to stop.
void Cancel();
public: public:
/** /**
* Synchronously processes any events queued by the worker. * Synchronously processes any events queued by the worker.

View File

@@ -123,6 +123,8 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#ifdef ARCHITECTURE_x86_64 #ifdef ARCHITECTURE_x86_64
#include "common/x64/cpu_detect.h" #include "common/x64/cpu_detect.h"
#endif #endif
#include "audio_core/audio_core.h"
#include "audio_core/sink/sink.h"
#include "citron/about_dialog.h" #include "citron/about_dialog.h"
#include "citron/bootmanager.h" #include "citron/bootmanager.h"
#include "citron/compatdb.h" #include "citron/compatdb.h"
@@ -2100,6 +2102,8 @@ void GMainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletP
StartGameType type) { StartGameType type) {
LOG_INFO(Frontend, "citron starting..."); LOG_INFO(Frontend, "citron starting...");
game_list->CancelPopulation();
RegisterAutoloaderContents(); RegisterAutoloaderContents();
if (params.program_id == 0 || if (params.program_id == 0 ||
@@ -2342,9 +2346,15 @@ void GMainWindow::OnEmulationStopped() {
LOG_INFO(Frontend, LOG_INFO(Frontend,
"Mirroring: Emulation stopped. Re-arming startup sync for next game list refresh."); "Mirroring: Emulation stopped. Re-arming startup sync for next game list refresh.");
// This is necessary to stop the game list worker from accessing the filesystem.
game_list->CancelPopulation();
// This is necessary to reset the in-memory state for the next launch. // This is necessary to reset the in-memory state for the next launch.
system->GetFileSystemController().CreateFactories(*vfs, true); system->GetFileSystemController().CreateFactories(*vfs, true);
// Refresh the game list now that the filesystem is valid again.
game_list->PopulateAsync(UISettings::values.game_dirs);
discord_rpc->Update(); discord_rpc->Update();
#ifdef __unix__ #ifdef __unix__
@@ -5746,6 +5756,14 @@ void GMainWindow::UpdateVolumeUI() {
volume_button->setChecked(true); volume_button->setChecked(true);
volume_button->setText(tr("VOLUME: %1%", "Volume percentage (e.g. 50%)").arg(volume_value)); volume_button->setText(tr("VOLUME: %1%", "Volume percentage (e.g. 50%)").arg(volume_value));
} }
float volume_scale = static_cast<float>(volume_value) / 100.0f;
if (Settings::values.audio_muted) {
volume_scale = 0.0f;
}
if (system && system->IsPoweredOn()) {
system->AudioCore().GetOutputSink().SetSystemVolume(volume_scale);
}
} }
void GMainWindow::UpdateStatusButtons() { void GMainWindow::UpdateStatusButtons() {

View File

@@ -437,18 +437,17 @@ void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable
std::error_code status_ec; std::error_code status_ec;
const auto st = entry.status(status_ec); const auto st = entry.status(status_ec);
if (status_ec) continue; if (status_ec)
continue;
if (True(filter & DirEntryFilter::File) && if (True(filter & DirEntryFilter::File) && st.type() == fs::file_type::regular) {
st.type() == fs::file_type::regular) {
if (!callback(entry)) { if (!callback(entry)) {
callback_error = true; callback_error = true;
break; break;
} }
} }
if (True(filter & DirEntryFilter::Directory) && if (True(filter & DirEntryFilter::Directory) && st.type() == fs::file_type::directory) {
st.type() == fs::file_type::directory) {
if (!callback(entry)) { if (!callback(entry)) {
callback_error = true; callback_error = true;
break; break;
@@ -467,14 +466,16 @@ void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable
PathToUTF8String(path)); PathToUTF8String(path));
} }
void IterateDirEntriesRecursivelyInternal(const std::filesystem::path& path, bool IterateDirEntriesRecursivelyInternal(const std::filesystem::path& path,
const DirEntryCallable& callback, DirEntryFilter filter, const DirEntryCallable& callback, DirEntryFilter filter,
int depth) { int depth) {
if (depth > 12) return; if (depth > 12)
return true;
std::error_code ec; std::error_code ec;
auto it = fs::directory_iterator(path, ec); auto it = fs::directory_iterator(path, ec);
if (ec) return; if (ec)
return true;
while (it != fs::directory_iterator() && !ec) { while (it != fs::directory_iterator() && !ec) {
const auto& entry = *it; const auto& entry = *it;
@@ -483,8 +484,8 @@ void IterateDirEntriesRecursivelyInternal(const std::filesystem::path& path,
const std::string filename = entry.path().filename().string(); const std::string filename = entry.path().filename().string();
if (filename[0] == '$' || filename == "Windows" || filename == "Program Files" || if (filename[0] == '$' || filename == "Windows" || filename == "Program Files" ||
filename == "Program Files (x86)" || filename == "System Volume Information" || filename == "Program Files (x86)" || filename == "System Volume Information" ||
filename == "ProgramData" || filename == "Application Data" || filename == "ProgramData" || filename == "Application Data" || filename == "Users" ||
filename == "Users" || filename == "SteamLibrary") { filename == "SteamLibrary") {
it.increment(ec); it.increment(ec);
continue; continue;
} }
@@ -492,15 +493,29 @@ void IterateDirEntriesRecursivelyInternal(const std::filesystem::path& path,
std::error_code status_ec; std::error_code status_ec;
if (entry.is_directory(status_ec)) { if (entry.is_directory(status_ec)) {
if (True(filter & DirEntryFilter::Directory)) { if (!callback(entry)) break; } if (True(filter & DirEntryFilter::Directory)) {
IterateDirEntriesRecursivelyInternal(entry.path(), callback, filter, depth + 1); if (!callback(entry)) {
return false;
}
}
if (!IterateDirEntriesRecursivelyInternal(entry.path(), callback, filter, depth + 1)) {
return false;
}
} else { } else {
if (True(filter & DirEntryFilter::File)) { if (!callback(entry)) break; } if (True(filter & DirEntryFilter::File)) {
if (!callback(entry)) {
return false;
}
}
} }
it.increment(ec); it.increment(ec);
if (ec) { ec.clear(); break; } if (ec) {
ec.clear();
break;
}
} }
return true;
} }
void IterateDirEntriesRecursively(const std::filesystem::path& path, void IterateDirEntriesRecursively(const std::filesystem::path& path,

View File

@@ -400,9 +400,9 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path,
const DirEntryCallable& callback, const DirEntryCallable& callback,
DirEntryFilter filter = DirEntryFilter::All); DirEntryFilter filter = DirEntryFilter::All);
void IterateDirEntriesRecursivelyInternal(const std::filesystem::path& path, bool IterateDirEntriesRecursivelyInternal(const std::filesystem::path& path,
const DirEntryCallable& callback, const DirEntryCallable& callback, DirEntryFilter filter,
DirEntryFilter filter, int depth); int depth);
#ifdef _WIN32 #ifdef _WIN32
template <typename Path> template <typename Path>