[Linux/Threading] Self-suspend with condition variable instead of spin
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user