fix(libkernel): give a sleep nested in an APC its own timer on Windows

SleepUntil, PollSleepUntil and SleepNanosCoarse each wait on one
thread_local high-resolution timer. A sceKernelRaiseException handler
that runs as an APC inside a sleeping thread's wait and sleeps itself
re-armed that timer and consumed its signal, so the interrupted sleep
never returned. A nested TimerWait now waits on its own timer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
popcoder565 2026-10-08 07:01:09 -04:00
parent 4a14093ac9
commit d80d5ff8c6
3 changed files with 87 additions and 0 deletions

View File

@ -303,6 +303,13 @@ if(BUILD_TESTING)
add_test(NAME guest_raise_exception COMMAND guest_raise_exception_tests)
set_tests_properties(guest_raise_exception PROPERTIES TIMEOUT 30 ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
add_test_executable(guest_raise_nested_sleep_tests ${LIBS_INCLUDE_DIR}/tests/GuestRaiseNestedSleep.cpp)
target_include_directories(guest_raise_nested_sleep_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_raise_nested_sleep_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_raise_nested_sleep_tests)
add_test(NAME guest_raise_nested_sleep COMMAND guest_raise_nested_sleep_tests)
set_tests_properties(guest_raise_nested_sleep PROPERTIES TIMEOUT 30 ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
if(WIN32)
add_library(guest_unwind_module_info_fixture SHARED ${LIBS_INCLUDE_DIR}/tests/GuestUnwindModuleInfoFixture.cpp)

View File

@ -60,6 +60,16 @@ HANDLE CreateHighResolutionTimer() {
}
bool TimerWait(HANDLE timer, std::uint64_t nanos) {
thread_local int depth = 0;
struct Nesting {
HANDLE own;
explicit Nesting(bool nested) : own(nested ? CreateHighResolutionTimer() : nullptr) { ++depth; }
~Nesting() {
--depth;
if (own) CloseHandle(own);
}
} nesting(depth != 0);
if (depth > 1) timer = nesting.own;
if (!timer) return false;
LARGE_INTEGER due{};
due.QuadPart = -static_cast<LONGLONG>(nanos / 100ULL);

View File

@ -0,0 +1,70 @@
#include "SceTypes.hpp"
#include <atomic>
#include <chrono>
#include <cstdlib>
#include <thread>
extern "C" {
int APS5_VABI sceKernelInstallExceptionHandler(int signum, void* handler);
int APS5_VABI sceKernelRemoveExceptionHandler(int signum);
int APS5_VABI sceKernelRaiseException(Pthread thread, int signum);
int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name);
int APS5_VABI scePthreadJoin(Pthread thread, void** retval);
int APS5_VABI sceKernelUsleep_nid_postfix(KernelUseconds microseconds);
}
static constexpr int SIGUSR1 = 30;
static constexpr int SCE_OK = 0;
static constexpr KernelUseconds OuterSleep = 1000000;
static constexpr KernelUseconds HandlerSleep = 20000;
using Clock = std::chrono::steady_clock;
static void Require(bool value) { if (!value) std::abort(); }
static std::atomic<int> handled{0};
static std::atomic<bool> started{false};
static std::atomic<bool> returned{false};
static Clock::time_point sleepStart;
static Clock::time_point sleepEnd;
static void APS5_VABI Handler(int signum, void*) {
Require(signum == SIGUSR1);
Require(sceKernelUsleep_nid_postfix(HandlerSleep) == SCE_OK);
handled.fetch_add(1);
}
static void* APS5_VABI Sleeper(void*) {
sleepStart = Clock::now();
started.store(true);
Require(sceKernelUsleep_nid_postfix(OuterSleep) == SCE_OK);
sleepEnd = Clock::now();
returned.store(true);
return nullptr;
}
template <class TCondition>
static bool WaitFor(TCondition condition, std::chrono::milliseconds limit) {
const auto deadline = Clock::now() + limit;
while (!condition()) {
if (Clock::now() >= deadline) return false;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
return true;
}
int main() {
Require(sceKernelInstallExceptionHandler(SIGUSR1, reinterpret_cast<void*>(&Handler)) == 0);
Pthread thread = nullptr;
Require(scePthreadCreate(&thread, nullptr, Sleeper, nullptr, "sleeper") == 0);
Require(WaitFor([] { return started.load(); }, std::chrono::milliseconds(5000)));
std::this_thread::sleep_for(std::chrono::milliseconds(50));
Require(sceKernelRaiseException(thread, SIGUSR1) == 0);
Require(WaitFor([] { return handled.load() == 1; }, std::chrono::milliseconds(5000)));
Require(WaitFor([] { return returned.load(); }, std::chrono::milliseconds(5000)));
const auto slept = std::chrono::duration_cast<std::chrono::microseconds>(sleepEnd - sleepStart).count();
Require(slept >= OuterSleep && slept < OuterSleep + 1000000);
Require(scePthreadJoin(thread, nullptr) == 0);
Require(sceKernelRemoveExceptionHandler(SIGUSR1) == 0);
return 0;
}