fix(kernel): drain raised exceptions when leaving an alertable wait

A raise that suspends the thread after its wait returned but before the
wait count drops queued an APC that only ran at the next alertable wait.
Pending APCs are now run right after the count drops. The 0 ms poll in
Condition::waitSignalUntil runs under queueLock and is no longer
alertable, and a target that exits during the raise returns ESRCH.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
MerijnandClaude Opus 5.5 committed 2026-10-07 22:11:29 +02:00
1 parent e5b44c4536
commit 2654e3a4ca
3 files changed
+60 -7

No files matched your search

@@ -186,22 +186,33 @@ void CALLBACK WaitingEntry(ULONG_PTR parameter) {
static_assert(HomeArea + 8 == 40, "Aps5RedirectedEntryStub finds the delivery 40 bytes above its stack pointer");
static_assert(offsetof(Delivery, context) == 16 && offsetof(CONTEXT, Rax) == 0x78 && offsetof(CONTEXT, Rbp) == 0xa0 && offsetof(CONTEXT, R15) == 0xf0, "Aps5RedirectedEntryStub stores the live registers into the delivery's context");
void RaiseOn(Pthread thread, GuestExceptionHandler handler, int signum) {
bool Exited(HANDLE native) {
return WaitForSingleObject(native, 0) == WAIT_OBJECT_0;
}
bool RaiseOn(Pthread thread, GuestExceptionHandler handler, int signum) {
if (thread == scePthreadSelf()) {
CONTEXT context{};
RtlCaptureContext(&context);
Deliver(handler, signum, context);
return;
return true;
}
const auto native = static_cast<HANDLE>(thread->nativeHandle);
auto queued = std::make_unique<Delivery>(Delivery{handler, signum, {}});
if (SuspendThread(native) == static_cast<DWORD>(-1)) throw std::runtime_error("sceKernelRaiseException: cannot suspend the target thread");
if (SuspendThread(native) == static_cast<DWORD>(-1)) {
if (Exited(native)) return false;
throw std::runtime_error("sceKernelRaiseException: cannot suspend the target thread");
}
if (Exited(native)) {
ResumeThread(native);
return false;
}
if (thread->waitCount.load(std::memory_order_seq_cst) > 0) {
const bool accepted = QueueUserAPC(WaitingEntry, native, reinterpret_cast<ULONG_PTR>(queued.get())) != 0;
ResumeThread(native);
if (!accepted) throw std::runtime_error("sceKernelRaiseException: cannot queue delivery to the waiting thread");
queued.release();
return;
return true;
}
alignas(16) Delivery delivery{handler, signum, {}};
delivery.context.ContextFlags = CONTEXT_FULL | CONTEXT_FLOATING_POINT;
@@ -223,6 +234,7 @@ void RaiseOn(Pthread thread, GuestExceptionHandler handler, int signum) {
throw std::runtime_error("sceKernelRaiseException: cannot redirect the target thread");
}
ResumeThread(native);
return true;
}
#endif
@@ -277,8 +289,7 @@ int APS5_VABI sceKernelRaiseException(Pthread thread, int signum) {
const auto handler = Handler(signum);
if (handler == nullptr) throw std::runtime_error("sceKernelRaiseException: no handler installed for the signal");
#ifdef _WIN32
RaiseOn(thread, handler, signum);
return 0;
return RaiseOn(thread, handler, signum) ? 0 : SCE_KERNEL_ERROR_ESRCH;
#else
NotImplemented_nid_no_patch(__func__);
return 0;
+11 -1
View File
@@ -22,6 +22,14 @@ thread_local std::atomic<int>* waitState = &ownWaitState;
#ifdef _WIN32
using NtTestAlertFunction = LONG(NTAPI*)();
void DrainApcs() {
static const auto testAlert = reinterpret_cast<NtTestAlertFunction>(reinterpret_cast<void*>(GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtTestAlert")));
if (testAlert) testAlert();
else SleepEx(0, TRUE);
}
DWORD AlertableWait(DWORD count, const HANDLE* handles, DWORD milliseconds) {
waitState->fetch_add(1, std::memory_order_seq_cst);
DWORD result;
@@ -29,6 +37,7 @@ DWORD AlertableWait(DWORD count, const HANDLE* handles, DWORD milliseconds) {
result = WaitForMultipleObjectsEx(count, handles, FALSE, milliseconds, TRUE);
} while (result == WAIT_IO_COMPLETION);
waitState->fetch_sub(1, std::memory_order_seq_cst);
DrainApcs();
return result;
}
@@ -36,6 +45,7 @@ void AlertableSleep(DWORD milliseconds) {
waitState->fetch_add(1, std::memory_order_seq_cst);
SleepEx(milliseconds, TRUE);
waitState->fetch_sub(1, std::memory_order_seq_cst);
DrainApcs();
}
void WINAPI DestroyWaiter(void* value);
@@ -169,7 +179,7 @@ bool Condition::waitSignalUntil(Waiter* waiter, std::uint64_t deadlineNanos) {
unlink(waiter);
return false;
}
EventSet(waiter->event, 0);
WaitForSingleObject(waiter->event, 0);
return true;
}
+32
View File
@@ -85,6 +85,21 @@ static void* APS5_VABI HostBlocked(void* arg) {
return nullptr;
}
static constexpr int LeavingRounds = 200;
static std::atomic<int> leavingRound{0};
static void* APS5_VABI Leaving(void* arg) {
auto& worker = *static_cast<Worker*>(arg);
worker.id = std::this_thread::get_id();
for (int round = 0; round < LeavingRounds; ++round) {
worker.started.store(true);
Require(sceKernelWaitSema(worker.sem, 1, nullptr) == 0);
volatile std::uint64_t spins = 0;
while (leavingRound.load() == round) spins = spins + 1;
}
return nullptr;
}
static std::atomic<bool> finishedReturned{false};
static void* APS5_VABI Finished(void*) {
@@ -156,6 +171,23 @@ int main() {
hostLock.unlock();
Require(scePthreadJoin(blockedThread, nullptr) == 0);
Worker leaving;
Require(sceKernelCreateSema(&leaving.sem, "leaving", 0, 0, 1, nullptr) == 0);
Pthread leavingThread = nullptr;
Require(scePthreadCreate(&leavingThread, nullptr, Leaving, &leaving, "leaving") == 0);
for (int round = 0; round < LeavingRounds; ++round) {
while (!leaving.started.load()) std::this_thread::yield();
leaving.started.store(false);
std::this_thread::sleep_for(std::chrono::microseconds(200));
Require(sceKernelSignalSema(leaving.sem, 1) == 0);
for (int spin = 0; spin < round % 16 * 64; ++spin) std::this_thread::yield();
Require(sceKernelRaiseException(leavingThread, SIGUSR1) == 0);
ExpectDelivery(1 + 2 * Repeats + HostRounds + round, leaving.id);
leavingRound.store(round + 1);
}
Require(scePthreadJoin(leavingThread, nullptr) == 0);
Require(sceKernelDeleteSema(leaving.sem) == 0);
Pthread finishedThread = nullptr;
Require(scePthreadCreate(&finishedThread, nullptr, Finished, nullptr, "finished") == 0);
while (!finishedReturned.load()) std::this_thread::yield();