diff --git a/Source/Core/Common/BlockingLoop.h b/Source/Core/Common/BlockingLoop.h index 472a84b1f6..4f01ae87a0 100644 --- a/Source/Core/Common/BlockingLoop.h +++ b/Source/Core/Common/BlockingLoop.h @@ -239,8 +239,8 @@ private: Flag m_stopped; // If this is set, Wait() shall not block. Flag m_shutdown; // If this is set, the loop shall end. - Event m_new_work_event; - Event m_done_event; + TimedEvent m_new_work_event; + TimedEvent m_done_event; enum RUNNING_TYPE { diff --git a/Source/Core/Common/Event.h b/Source/Core/Common/Event.h index 67dbd1ed3a..a9237e0960 100644 --- a/Source/Core/Common/Event.h +++ b/Source/Core/Common/Event.h @@ -11,15 +11,17 @@ #pragma once +#include #include #include #include #include "Common/Flag.h" +#include "Common/WaitableFlag.h" namespace Common { -class Event final +class TimedEvent final { public: void Set() @@ -77,4 +79,28 @@ private: std::mutex m_mutex; }; +// An auto-resetting WaitableFlag. Only sensible for one waiting thread. +class Event final +{ +public: + void Set() { m_flag.Set(); } + + void Wait() + { + m_flag.Wait(true); + + // This might run concurrently with the next Set, clearing m_flag before notification. + // "Missing" that event later is fine as long as all the data is visible *now*. + m_flag.Reset(); + // This store-load barrier prevents the Reset-store ordering after pertinent data loads. + // Without it, we could observe stale data AND miss the next event, i.e. deadlock. + std::atomic_thread_fence(std::memory_order_seq_cst); + } + + void Reset() { m_flag.Reset(); } + +private: + WaitableFlag m_flag{}; +}; + } // namespace Common diff --git a/Source/Core/Common/WaitableFlag.h b/Source/Core/Common/WaitableFlag.h index 09ab12a3bc..e6fa63e119 100644 --- a/Source/Core/Common/WaitableFlag.h +++ b/Source/Core/Common/WaitableFlag.h @@ -1,15 +1,11 @@ // Copyright 2025 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -// Wrapper around Flag that lets callers wait for the flag to change. +// class that allows threads to wait for a bool to take on a value. #pragma once -#include -#include -#include - -#include "Common/Flag.h" +#include namespace Common { @@ -18,52 +14,17 @@ class WaitableFlag final public: void Set(bool value = true) { - if (m_flag.TestAndSet(value)) - { - // Lock and immediately unlock m_mutex. - { - // Holding the lock at any time between the change of our flag and notify call - // is sufficient to prevent a race where both of these actions - // happen between the other thread's predicate test and wait call - // which would cause wait to block until the next spurious wakeup or timeout. - - // Unlocking before notification is a micro-optimization to prevent - // the notified thread from immediately blocking on the mutex. - std::lock_guard lk(m_mutex); - } - - m_condvar.notify_all(); - } + m_flag.store(value, std::memory_order_release); + m_flag.notify_all(); } - void Reset() { Set(false); } + void Wait(bool expected_value) { m_flag.wait(!expected_value, std::memory_order_acquire); } - void Wait(bool expected_value) - { - if (m_flag.IsSet() == expected_value) - return; - - std::unique_lock lk(m_mutex); - m_condvar.wait(lk, [&] { return m_flag.IsSet() == expected_value; }); - } - - template - bool WaitFor(bool expected_value, const std::chrono::duration& rel_time) - { - if (m_flag.IsSet() == expected_value) - return true; - - std::unique_lock lk(m_mutex); - bool signaled = - m_condvar.wait_for(lk, rel_time, [&] { return m_flag.IsSet() == expected_value; }); - - return signaled; - } + // Note that this does not awake Wait'ing threads. Use Set(false) if that's needed. + void Reset() { m_flag.store(false, std::memory_order_relaxed); } private: - Flag m_flag; - std::condition_variable m_condvar; - std::mutex m_mutex; + std::atomic_bool m_flag{}; }; } // namespace Common diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index 5f90f93bae..d0f6a410c0 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -121,7 +121,7 @@ struct HostJob }; static std::mutex s_host_jobs_lock; static std::queue s_host_jobs_queue; -static Common::Event s_cpu_thread_job_finished; +static Common::TimedEvent s_cpu_thread_job_finished; static thread_local bool tls_is_cpu_thread = false; static thread_local bool tls_is_gpu_thread = false; diff --git a/Source/Core/Core/HW/CPU.cpp b/Source/Core/Core/HW/CPU.cpp index 5a21e5e3c8..66c6e36a50 100644 --- a/Source/Core/Core/HW/CPU.cpp +++ b/Source/Core/Core/HW/CPU.cpp @@ -127,7 +127,7 @@ void CPUManager::Run() ExecutePendingJobs(state_lock); CPUThreadConfigCallback::CheckForConfigChanges(); - Common::Event gdb_step_sync_event; + Common::TimedEvent gdb_step_sync_event; switch (m_state) { case State::Running: @@ -272,7 +272,7 @@ void CPUManager::Reset() { } -void CPUManager::StepOpcode(Common::Event* event) +void CPUManager::StepOpcode(Common::TimedEvent* event) { std::lock_guard state_lock(m_state_change_lock); // If we're not stepping then this is pointless diff --git a/Source/Core/Core/HW/CPU.h b/Source/Core/Core/HW/CPU.h index b73471136d..5d052c31b1 100644 --- a/Source/Core/Core/HW/CPU.h +++ b/Source/Core/Core/HW/CPU.h @@ -12,7 +12,7 @@ namespace Common { -class Event; +class TimedEvent; } namespace Core { @@ -61,7 +61,7 @@ public: void Reset(); // StepOpcode (Steps one Opcode) - void StepOpcode(Common::Event* event = nullptr); + void StepOpcode(Common::TimedEvent* event = nullptr); // Enable or Disable Stepping. [Will deadlock if called from a system thread] void SetStepping(bool stepping); @@ -134,9 +134,9 @@ private: bool m_state_paused_and_locked = false; bool m_state_system_request_stepping = false; bool m_state_cpu_step_instruction = false; - Common::Event* m_state_cpu_step_instruction_sync = nullptr; + Common::TimedEvent* m_state_cpu_step_instruction_sync = nullptr; std::queue> m_pending_jobs; - Common::Event m_time_played_finish_sync; + Common::TimedEvent m_time_played_finish_sync; Core::System& m_system; }; diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.h b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.h index 5b02700a61..d110fd0f7f 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.h +++ b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.h @@ -61,7 +61,7 @@ private: std::vector m_saves; std::string m_save_directory; - Common::Event m_flush_trigger; + Common::TimedEvent m_flush_trigger; std::mutex m_write_mutex; Common::Flag m_exiting; std::thread m_flush_thread; diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.h b/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.h index 3138899667..5f206d083a 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.h +++ b/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.h @@ -41,7 +41,7 @@ private: std::unique_ptr m_flush_buffer; std::thread m_flush_thread; std::mutex m_flush_mutex; - Common::Event m_flush_trigger; + Common::TimedEvent m_flush_trigger; Common::Flag m_dirty; u32 m_memory_card_size; }; diff --git a/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h b/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h index aa59202daa..b89dd8a45f 100644 --- a/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h +++ b/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h @@ -202,7 +202,7 @@ private: std::thread m_scan_thread; Common::Flag m_scan_thread_running; Common::Flag m_populate_devices; - Common::Event m_scan_mode_changed_or_population_event; + Common::TimedEvent m_scan_mode_changed_or_population_event; std::atomic m_scan_mode{WiimoteScanMode::DO_NOT_SCAN}; }; diff --git a/Source/Core/Core/IOS/Network/KD/NetKDRequest.h b/Source/Core/Core/IOS/Network/KD/NetKDRequest.h index 848454fa71..8c415753e1 100644 --- a/Source/Core/Core/IOS/Network/KD/NetKDRequest.h +++ b/Source/Core/Core/IOS/Network/KD/NetKDRequest.h @@ -123,7 +123,7 @@ private: u32 m_download_span = 2; u32 m_mail_span = 1; bool m_handle_mail; - Common::Event m_shutdown_event; + Common::TimedEvent m_shutdown_event; std::mutex m_scheduler_lock; std::thread m_scheduler_timer_thread; }; diff --git a/Source/Core/DolphinQt/Debugger/CodeWidget.cpp b/Source/Core/DolphinQt/Debugger/CodeWidget.cpp index 526165ee5c..a801268dc4 100644 --- a/Source/Core/DolphinQt/Debugger/CodeWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/CodeWidget.cpp @@ -501,7 +501,7 @@ void CodeWidget::Step() if (!cpu.IsStepping()) return; - Common::Event sync_event; + Common::TimedEvent sync_event; auto& power_pc = m_system.GetPowerPC(); PowerPC::CoreMode old_mode = power_pc.GetMode(); diff --git a/Source/Core/DolphinQt/DiscordHandler.h b/Source/Core/DolphinQt/DiscordHandler.h index a4fc342530..7e70897537 100644 --- a/Source/Core/DolphinQt/DiscordHandler.h +++ b/Source/Core/DolphinQt/DiscordHandler.h @@ -42,7 +42,7 @@ private: void Run(); QWidget* m_parent; Common::Flag m_stop_requested; - Common::Event m_wakeup_event; + Common::TimedEvent m_wakeup_event; std::thread m_thread; std::list m_request_dialogs; std::mutex m_request_dialogs_mutex; diff --git a/Source/Core/UICommon/NetPlayIndex.h b/Source/Core/UICommon/NetPlayIndex.h index 13fb674a66..deeb2658e9 100644 --- a/Source/Core/UICommon/NetPlayIndex.h +++ b/Source/Core/UICommon/NetPlayIndex.h @@ -68,7 +68,7 @@ private: std::string m_last_error; std::thread m_session_thread; - Common::Event m_session_thread_exit_event; + Common::TimedEvent m_session_thread_exit_event; std::function m_error_callback = nullptr; };