diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc index 56c0b735d..a9c508ae9 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc @@ -245,27 +245,17 @@ dword_result_t NtSuspendThread_entry(dword_t handle, return X_STATUS_THREAD_IS_TERMINATING; } #elif XE_PLATFORM_LINUX - // On Linux, we need to handle self-suspension specially to avoid deadlock + // Handle self-suspension specially to avoid deadlock. if (!thread->guest_object()->terminated) { bool is_self_suspend = (current_pcr->prcb_data.current_thread == thread->guest_object()); if (is_self_suspend) { - // Self-suspension: increment the suspend count and wait until - // another thread resumes us (decrements count to 0) - auto guest_thread = thread->guest_object(); - suspend_count = guest_thread->suspend_count; - guest_thread->suspend_count++; + XELOGD("Thread {:X} self-suspending", thread->handle()); + suspend_count = thread->SelfSuspend(); result = X_STATUS_SUCCESS; - XELOGD("Thread {:X} self-suspending (count: {}) - waiting for resume", - thread->handle(), guest_thread->suspend_count); - // Wait until suspend_count reaches 0 (resumed by another thread) - while (guest_thread->suspend_count > 0) { - xe::threading::Sleep(std::chrono::microseconds(100)); - } XELOGD("Thread {:X} resumed", thread->handle()); } else { - // Normal suspension of another thread result = thread->Suspend(&suspend_count); } } else { diff --git a/src/xenia/kernel/xthread.cc b/src/xenia/kernel/xthread.cc index 6090f9816..7588b1cb0 100644 --- a/src/xenia/kernel/xthread.cc +++ b/src/xenia/kernel/xthread.cc @@ -711,7 +711,9 @@ uint32_t XThread::start_address() { X_STATUS XThread::Resume(uint32_t* out_suspend_count) { auto guest_thread = guest_object(); + uint32_t unused_host_suspend_count = 0; +#if XE_PLATFORM_WIN32 uint8_t previous_suspend_count = reinterpret_cast(&guest_thread->suspend_count) ->fetch_sub(1); @@ -719,21 +721,30 @@ X_STATUS XThread::Resume(uint32_t* out_suspend_count) { *out_suspend_count = previous_suspend_count; } - uint32_t unused_host_suspend_count = 0; -#if XE_PLATFORM_WIN32 if (thread_->Resume(&unused_host_suspend_count)) { return X_STATUS_SUCCESS; } else { return X_STATUS_UNSUCCESSFUL; } #elif XE_PLATFORM_LINUX - // On Linux, only resume if suspend count is 0 - // Resume might fail if thread was self-suspended - this is expected - if (guest_thread->suspend_count == 0) { - if (!thread_->Resume(&unused_host_suspend_count)) { - XELOGD("Host thread resume skipped for thread {:X} (was self-suspended)", - handle()); + // Use mutex to protect suspend_count access and coordinate with SelfSuspend. + bool should_resume_host = false; + { + std::lock_guard lock(suspend_mutex_); + uint8_t previous = guest_thread->suspend_count; + if (previous > 0) { + guest_thread->suspend_count--; } + if (out_suspend_count) { + *out_suspend_count = previous; + } + should_resume_host = (guest_thread->suspend_count == 0); + suspend_cv_.notify_all(); + } + + // Try to resume host thread if fully resumed (for non-self-suspended case). + if (should_resume_host) { + thread_->Resume(&unused_host_suspend_count); } return X_STATUS_SUCCESS; #else @@ -769,6 +780,18 @@ X_STATUS XThread::Suspend(uint32_t* out_suspend_count) { } } +#if XE_PLATFORM_LINUX +uint32_t XThread::SelfSuspend() { + auto guest_thread = guest_object(); + std::unique_lock lock(suspend_mutex_); + uint32_t previous = guest_thread->suspend_count; + guest_thread->suspend_count++; + suspend_cv_.wait( + lock, [guest_thread]() { return guest_thread->suspend_count == 0; }); + return previous; +} +#endif + X_STATUS XThread::Delay(uint32_t processor_mode, uint32_t alertable, uint64_t interval) { int64_t timeout_ticks = interval; diff --git a/src/xenia/kernel/xthread.h b/src/xenia/kernel/xthread.h index 5e42a570e..23d5e5346 100644 --- a/src/xenia/kernel/xthread.h +++ b/src/xenia/kernel/xthread.h @@ -15,6 +15,10 @@ #include #include "xenia/base/mutex.h" +#if XE_PLATFORM_LINUX +#include +#include +#endif #include "xenia/base/threading.h" #include "xenia/cpu/thread.h" #include "xenia/cpu/thread_state.h" @@ -426,6 +430,13 @@ class XThread : public XObject, public cpu::Thread { X_STATUS Delay(uint32_t processor_mode, uint32_t alertable, uint64_t interval); +#if XE_PLATFORM_LINUX + // Performs self-suspension: increments suspend_count and blocks until + // another thread calls Resume() and suspend_count reaches 0. + // Returns the previous suspend_count value. + uint32_t SelfSuspend(); +#endif + xe::threading::Thread* thread() { return thread_.get(); } virtual bool Save(ByteStream* stream) override; @@ -467,6 +478,12 @@ class XThread : public XObject, public cpu::Thread { int32_t priority_ = 0; +#if XE_PLATFORM_LINUX + // Condition variable for thread self-suspension. + std::mutex suspend_mutex_; + std::condition_variable suspend_cv_; +#endif + // Reentry context for setjmp/longjmp based stack unwinding std::jmp_buf reentry_jmp_buf_; uint32_t reentry_address_ = 0;