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:
parent
4a14093ac9
commit
d80d5ff8c6
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
Loading…
Reference in New Issue