diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 2ceb0cfa1..b951f5d60 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -416,6 +416,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:$") + 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:$") endif() if(WIN32) add_library(guest_unwind_module_info_fixture SHARED ${LIBS_INCLUDE_DIR}/tests/GuestUnwindModuleInfoFixture.cpp) diff --git a/core/libs/prx/libkernel/Time/TimedWait.cpp b/core/libs/prx/libkernel/Time/TimedWait.cpp index f5949d6da..8a8f5759f 100644 --- a/core/libs/prx/libkernel/Time/TimedWait.cpp +++ b/core/libs/prx/libkernel/Time/TimedWait.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(nanos / 100ULL); diff --git a/core/libs/tests/GuestRaiseNestedSleep.cpp b/core/libs/tests/GuestRaiseNestedSleep.cpp new file mode 100644 index 000000000..839f831ec --- /dev/null +++ b/core/libs/tests/GuestRaiseNestedSleep.cpp @@ -0,0 +1,70 @@ +#include "SceTypes.hpp" +#include +#include +#include +#include + +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 handled{0}; +static std::atomic started{false}; +static std::atomic 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 +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(&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(sleepEnd - sleepStart).count(); + Require(slept >= OuterSleep && slept < OuterSleep + 1000000); + Require(scePthreadJoin(thread, nullptr) == 0); + Require(sceKernelRemoveExceptionHandler(SIGUSR1) == 0); + return 0; +}