[Linux/Threading] Self-suspend with condition variable instead of spin

This commit is contained in:
Herman S.
2025-12-25 12:08:05 +09:00
parent 9f8961c185
commit f45a254774
3 changed files with 51 additions and 21 deletions

View File

@@ -245,27 +245,17 @@ dword_result_t NtSuspendThread_entry(dword_t handle,
return X_STATUS_THREAD_IS_TERMINATING; return X_STATUS_THREAD_IS_TERMINATING;
} }
#elif XE_PLATFORM_LINUX #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<X_KTHREAD>()->terminated) { if (!thread->guest_object<X_KTHREAD>()->terminated) {
bool is_self_suspend = bool is_self_suspend =
(current_pcr->prcb_data.current_thread == thread->guest_object()); (current_pcr->prcb_data.current_thread == thread->guest_object());
if (is_self_suspend) { if (is_self_suspend) {
// Self-suspension: increment the suspend count and wait until XELOGD("Thread {:X} self-suspending", thread->handle());
// another thread resumes us (decrements count to 0) suspend_count = thread->SelfSuspend();
auto guest_thread = thread->guest_object<X_KTHREAD>();
suspend_count = guest_thread->suspend_count;
guest_thread->suspend_count++;
result = X_STATUS_SUCCESS; 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()); XELOGD("Thread {:X} resumed", thread->handle());
} else { } else {
// Normal suspension of another thread
result = thread->Suspend(&suspend_count); result = thread->Suspend(&suspend_count);
} }
} else { } else {

View File

@@ -711,7 +711,9 @@ uint32_t XThread::start_address() {
X_STATUS XThread::Resume(uint32_t* out_suspend_count) { X_STATUS XThread::Resume(uint32_t* out_suspend_count) {
auto guest_thread = guest_object<X_KTHREAD>(); auto guest_thread = guest_object<X_KTHREAD>();
uint32_t unused_host_suspend_count = 0;
#if XE_PLATFORM_WIN32
uint8_t previous_suspend_count = uint8_t previous_suspend_count =
reinterpret_cast<std::atomic_uint8_t*>(&guest_thread->suspend_count) reinterpret_cast<std::atomic_uint8_t*>(&guest_thread->suspend_count)
->fetch_sub(1); ->fetch_sub(1);
@@ -719,21 +721,30 @@ X_STATUS XThread::Resume(uint32_t* out_suspend_count) {
*out_suspend_count = previous_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)) { if (thread_->Resume(&unused_host_suspend_count)) {
return X_STATUS_SUCCESS; return X_STATUS_SUCCESS;
} else { } else {
return X_STATUS_UNSUCCESSFUL; return X_STATUS_UNSUCCESSFUL;
} }
#elif XE_PLATFORM_LINUX #elif XE_PLATFORM_LINUX
// On Linux, only resume if suspend count is 0 // Use mutex to protect suspend_count access and coordinate with SelfSuspend.
// Resume might fail if thread was self-suspended - this is expected bool should_resume_host = false;
if (guest_thread->suspend_count == 0) { {
if (!thread_->Resume(&unused_host_suspend_count)) { std::lock_guard<std::mutex> lock(suspend_mutex_);
XELOGD("Host thread resume skipped for thread {:X} (was self-suspended)", uint8_t previous = guest_thread->suspend_count;
handle()); 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; return X_STATUS_SUCCESS;
#else #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<X_KTHREAD>();
std::unique_lock<std::mutex> 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, X_STATUS XThread::Delay(uint32_t processor_mode, uint32_t alertable,
uint64_t interval) { uint64_t interval) {
int64_t timeout_ticks = interval; int64_t timeout_ticks = interval;

View File

@@ -15,6 +15,10 @@
#include <string> #include <string>
#include "xenia/base/mutex.h" #include "xenia/base/mutex.h"
#if XE_PLATFORM_LINUX
#include <condition_variable>
#include <mutex>
#endif
#include "xenia/base/threading.h" #include "xenia/base/threading.h"
#include "xenia/cpu/thread.h" #include "xenia/cpu/thread.h"
#include "xenia/cpu/thread_state.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, X_STATUS Delay(uint32_t processor_mode, uint32_t alertable,
uint64_t interval); 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(); } xe::threading::Thread* thread() { return thread_.get(); }
virtual bool Save(ByteStream* stream) override; virtual bool Save(ByteStream* stream) override;
@@ -467,6 +478,12 @@ class XThread : public XObject, public cpu::Thread {
int32_t priority_ = 0; 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 // Reentry context for setjmp/longjmp based stack unwinding
std::jmp_buf reentry_jmp_buf_; std::jmp_buf reentry_jmp_buf_;
uint32_t reentry_address_ = 0; uint32_t reentry_address_ = 0;