RetroAchievements: Fix potential deadlock on shutdown.

This commit is contained in:
Jordan Woyak
2025-05-14 00:15:30 -05:00
parent 059282df6f
commit 826f04d06c
4 changed files with 43 additions and 24 deletions

View File

@ -58,13 +58,14 @@ AchievementManager& AchievementManager::GetInstance()
return s_instance; return s_instance;
} }
void AchievementManager::Init(void* hwnd) void AchievementManager::Init(void* hwnd, AsyncCallbackHandler async_callback_handler)
{ {
LoadDefaultBadges(); LoadDefaultBadges();
if (!m_client && Config::Get(Config::RA_ENABLED)) if (!m_client && Config::Get(Config::RA_ENABLED))
{ {
{ {
std::lock_guard lg{m_lock}; std::lock_guard lg{m_lock};
m_async_callback_handler = std::move(async_callback_handler);
m_client = rc_client_create(MemoryVerifier, Request); m_client = rc_client_create(MemoryVerifier, Request);
} }
std::string host_url = Config::Get(Config::RA_HOST_URL); std::string host_url = Config::Get(Config::RA_HOST_URL);
@ -1269,8 +1270,7 @@ void AchievementManager::Request(const rc_api_request_t* request,
std::string url = request->url; std::string url = request->url;
std::string post_data = request->post_data; std::string post_data = request->post_data;
AchievementManager::GetInstance().m_queue.Push( AchievementManager::GetInstance().m_queue.Push(
[url = std::move(url), post_data = std::move(post_data), callback = std::move(callback), [url = std::move(url), post_data = std::move(post_data), callback, callback_data] {
callback_data = std::move(callback_data)] {
Common::HttpRequest http_request; Common::HttpRequest http_request;
Common::HttpRequest::Response http_response; Common::HttpRequest::Response http_response;
if (!post_data.empty()) if (!post_data.empty())
@ -1284,22 +1284,28 @@ void AchievementManager::Request(const rc_api_request_t* request,
Common::HttpRequest::AllowedReturnCodes::All); Common::HttpRequest::AllowedReturnCodes::All);
} }
rc_api_server_response_t server_response; const auto response_code = http_request.GetLastResponseCode();
if (http_response.has_value() && http_response->size() > 0)
{
server_response.body = reinterpret_cast<const char*>(http_response->data());
server_response.body_length = http_response->size();
server_response.http_status_code = http_request.GetLastResponseCode();
}
else
{
static constexpr char error_message[] = "Failed HTTP request.";
server_response.body = error_message;
server_response.body_length = sizeof(error_message);
server_response.http_status_code = RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR;
}
callback(&server_response, callback_data); // The callback needs to be invoked inside our async callback handler
// as it may trigger a shutdown which will wait on this very job to complete.
AchievementManager::GetInstance().m_async_callback_handler(
[callback, callback_data, http_response = std::move(http_response), response_code] {
rc_api_server_response_t server_response;
if (http_response.has_value() && http_response->size() > 0)
{
server_response.body = reinterpret_cast<const char*>(http_response->data());
server_response.body_length = http_response->size();
server_response.http_status_code = response_code;
}
else
{
static constexpr char error_message[] = "Failed HTTP request.";
server_response.body = error_message;
server_response.body_length = sizeof(error_message);
server_response.http_status_code = RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR;
}
callback(&server_response, callback_data);
});
}); });
} }

View File

@ -29,6 +29,7 @@
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/Config/Config.h" #include "Common/Config/Config.h"
#include "Common/Event.h" #include "Common/Event.h"
#include "Common/Functional.h"
#include "Common/HttpRequest.h" #include "Common/HttpRequest.h"
#include "Common/JsonUtil.h" #include "Common/JsonUtil.h"
#include "Common/Lazy.h" #include "Common/Lazy.h"
@ -121,8 +122,12 @@ public:
}; };
using UpdateCallback = std::function<void(const UpdatedItems&)>; using UpdateCallback = std::function<void(const UpdatedItems&)>;
using AsyncCallback = Common::MoveOnlyFunction<void()>;
using AsyncCallbackHandler = Common::MoveOnlyFunction<void(AsyncCallback)>;
static AchievementManager& GetInstance(); static AchievementManager& GetInstance();
void Init(void* hwnd);
void Init(void* hwnd, AsyncCallbackHandler async_callback_handler);
void SetUpdateCallback(UpdateCallback callback); void SetUpdateCallback(UpdateCallback callback);
void Login(const std::string& password); void Login(const std::string& password);
bool HasAPIToken() const; bool HasAPIToken() const;
@ -308,6 +313,8 @@ private:
Common::AsyncWorkThread m_image_queue; Common::AsyncWorkThread m_image_queue;
mutable std::recursive_mutex m_lock; mutable std::recursive_mutex m_lock;
std::recursive_mutex m_filereader_lock; std::recursive_mutex m_filereader_lock;
AsyncCallbackHandler m_async_callback_handler;
}; // class AchievementManager }; // class AchievementManager
#else // USE_RETRO_ACHIEVEMENTS #else // USE_RETRO_ACHIEVEMENTS

View File

@ -6,13 +6,12 @@
#include <QLabel> #include <QLabel>
#include <QLineEdit> #include <QLineEdit>
#include <QPushButton>
#include <QString> #include <QString>
#include <QVBoxLayout> #include <QVBoxLayout>
#include "Core/AchievementManager.h" #include "Core/AchievementManager.h"
#include "Core/Config/AchievementSettings.h" #include "Core/Config/AchievementSettings.h"
#include "Core/Config/FreeLookSettings.h"
#include "Core/Config/MainSettings.h"
#include "Core/Config/UISettings.h" #include "Core/Config/UISettings.h"
#include "Core/Core.h" #include "Core/Core.h"
#include "Core/Movie.h" #include "Core/Movie.h"
@ -22,7 +21,7 @@
#include "DolphinQt/Config/ControllerInterface/ControllerInterfaceWindow.h" #include "DolphinQt/Config/ControllerInterface/ControllerInterfaceWindow.h"
#include "DolphinQt/Config/ToolTipControls/ToolTipCheckBox.h" #include "DolphinQt/Config/ToolTipControls/ToolTipCheckBox.h"
#include "DolphinQt/QtUtils/ModalMessageBox.h" #include "DolphinQt/QtUtils/ModalMessageBox.h"
#include "DolphinQt/QtUtils/NonDefaultQPushButton.h" #include "DolphinQt/QtUtils/QueueOnObject.h"
#include "DolphinQt/QtUtils/SignalBlocking.h" #include "DolphinQt/QtUtils/SignalBlocking.h"
#include "DolphinQt/Settings.h" #include "DolphinQt/Settings.h"
@ -252,9 +251,14 @@ void AchievementSettingsWidget::ToggleRAIntegration()
auto& instance = AchievementManager::GetInstance(); auto& instance = AchievementManager::GetInstance();
if (Config::Get(Config::RA_ENABLED)) if (Config::Get(Config::RA_ENABLED))
instance.Init(reinterpret_cast<void*>(winId())); {
instance.Init(reinterpret_cast<void*>(winId()),
[this](auto func) { QueueOnObject(this, std::move(func)); });
}
else else
{
instance.Shutdown(); instance.Shutdown();
}
} }
void AchievementSettingsWidget::Login() void AchievementSettingsWidget::Login()

View File

@ -274,7 +274,9 @@ MainWindow::MainWindow(Core::System& system, std::unique_ptr<BootParameters> boo
NetPlayInit(); NetPlayInit();
#ifdef USE_RETRO_ACHIEVEMENTS #ifdef USE_RETRO_ACHIEVEMENTS
AchievementManager::GetInstance().Init(reinterpret_cast<void*>(winId())); AchievementManager::GetInstance().Init(reinterpret_cast<void*>(winId()), [this](auto func) {
QueueOnObject(this, std::move(func));
});
if (AchievementManager::GetInstance().IsHardcoreModeActive()) if (AchievementManager::GetInstance().IsHardcoreModeActive())
Settings::Instance().SetDebugModeEnabled(false); Settings::Instance().SetDebugModeEnabled(false);
// This needs to trigger on both RA_HARDCORE_ENABLED and RA_ENABLED // This needs to trigger on both RA_HARDCORE_ENABLED and RA_ENABLED