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) {
if (!UISettings::values.favorited_ids.contains(program_id)) {
tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(),

View File

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

View File

@@ -21,7 +21,6 @@
#include <QSettings>
#include <QStandardPaths>
#include "citron/compatibility_list.h"
#include "citron/game_list.h"
#include "citron/game_list_p.h"
@@ -40,7 +39,6 @@
#include "core/file_sys/submission_package.h"
#include "core/loader/loader.h"
namespace {
// Structure to hold cached game metadata
@@ -485,6 +483,11 @@ GameListWorker::~GameListWorker() {
processing_completed.Wait();
}
void GameListWorker::Cancel() {
this->disconnect();
stop_requested.store(true);
}
void GameListWorker::ProcessEvents(GameList* game_list) {
while (true) {
std::function<void(GameList*)> func;
@@ -542,6 +545,10 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir,
}
for (const auto& [slot, game] : installed_games) {
if (stop_requested) {
break;
}
if (slot == ContentProviderUnionSlot::FrontendManual) {
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,
int& processed_files, int total_files) {
const auto callback = [this, target, parent_dir, &online_stats, &processed_files,
total_files](const std::filesystem::path& path) -> bool {
if (stop_requested)
total_files](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 auto physical_name = Common::FS::PathToUTF8String(path);
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) {
Common::FS::IterateDirEntriesRecursively(dir_path, callback,
Common::FS::DirEntryFilter::File);
Common::FS::DirEntryFilter::All);
} else {
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")
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);
if (HasSupportedFileExtension(physical_name)) {
total_files++;
@@ -755,7 +777,7 @@ void GameListWorker::run() {
if (game_dir.deep_scan) {
Common::FS::IterateDirEntriesRecursively(game_dir.path, count_callback,
Common::FS::DirEntryFilter::File);
Common::FS::DirEntryFilter::All);
} else {
Common::FS::IterateDirEntries(game_dir.path, count_callback,
Common::FS::DirEntryFilter::File);

View File

@@ -6,24 +6,26 @@
#include <atomic>
#include <deque>
#include <map> // Required for the online_stats map
#include <memory>
#include <string>
#include <map> // Required for the online_stats map
#include <utility> // Required for std::pair
#include <QList>
#include <QObject>
#include <QRunnable>
#include <QString>
#include "common/thread.h"
#include "citron/compatibility_list.h"
#include "citron/play_time_manager.h"
#include "citron/multiplayer/state.h"
#include "citron/play_time_manager.h"
#include "common/thread.h"
#include "network/announce_multiplayer_session.h"
namespace Core {
class System;
class System;
}
class GameList;
@@ -31,9 +33,9 @@ class GameListDir; // Forward declare GameListDir
class QStandardItem;
namespace FileSys {
class NCA;
class VfsFilesystem;
class ManualContentProvider;
class NCA;
class VfsFilesystem;
class ManualContentProvider;
} // namespace FileSys
/**
@@ -62,6 +64,9 @@ public:
/// Starts the processing of directory tree information.
void run() override;
/// Request the worker to stop.
void Cancel();
public:
/**
* 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
#include "common/x64/cpu_detect.h"
#endif
#include "audio_core/audio_core.h"
#include "audio_core/sink/sink.h"
#include "citron/about_dialog.h"
#include "citron/bootmanager.h"
#include "citron/compatdb.h"
@@ -2100,6 +2102,8 @@ void GMainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletP
StartGameType type) {
LOG_INFO(Frontend, "citron starting...");
game_list->CancelPopulation();
RegisterAutoloaderContents();
if (params.program_id == 0 ||
@@ -2342,9 +2346,15 @@ void GMainWindow::OnEmulationStopped() {
LOG_INFO(Frontend,
"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.
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();
#ifdef __unix__
@@ -5746,6 +5756,14 @@ void GMainWindow::UpdateVolumeUI() {
volume_button->setChecked(true);
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() {

View File

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

View File

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