[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;
}
#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) {
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<X_KTHREAD>();
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 {

View File

@@ -711,7 +711,9 @@ uint32_t XThread::start_address() {
X_STATUS XThread::Resume(uint32_t* out_suspend_count) {
auto guest_thread = guest_object<X_KTHREAD>();
uint32_t unused_host_suspend_count = 0;
#if XE_PLATFORM_WIN32
uint8_t previous_suspend_count =
reinterpret_cast<std::atomic_uint8_t*>(&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<std::mutex> 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<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,
uint64_t interval) {
int64_t timeout_ticks = interval;

View File

@@ -15,6 +15,10 @@
#include <string>
#include "xenia/base/mutex.h"
#if XE_PLATFORM_LINUX
#include <condition_variable>
#include <mutex>
#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;