[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;
|
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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user