Files
AnyPS5/core/libs/tests/GuestRaiseShortSleep.cpp
T
Oscar45397andClaude Opus 5.5 03722a1570 fix(libkernel): deliver raises at the spin that ends a sleep on Windows
On Windows a sleep shorter than TimedWait's 0.5 ms lead, and the last 0.5 ms of a longer one, is a YieldProcessor spin in host code with no wait counted. A raise that found the target there could only be delivered through the return-address breakpoint or sampling, so sceKernelRaiseException at a thread looping on such sleeps was lost: a thread looping on sceKernelUsleep(200) lost the first raise in 5 of 5 runs.

The spins in SleepUntil and SleepNanosUnlessChanged now run under a DeliveryDeferral and drain APCs afterwards, as BeginPark and Unpark already do. A raise that finds the target spinning is queued as an APC and runs when the sleep ends.

GuestRaiseShortSleep raises 100 times each at a thread sleeping 100, 200 and 400 us in a loop, with sleep wakes on (SleepNanosUnlessChanged) and with APS5_NO_SLEEP_WAKE (SleepUntil). Both fail on main at the first or second raise.

AI-assisted (Claude Code).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 13:26:59 +01:00

65 lines
2.3 KiB
C++

#include "prx/libkernel/include/Api.hpp"
#include "prx/libkernel/Time/include/Time.hpp"
#include "SceTypes.hpp"
#include <atomic>
#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <thread>
static constexpr int SigUsr1 = 30;
static constexpr int Raises = 100;
static void Require(bool value) { if (!value) std::abort(); }
static std::atomic<int> deliveries{0};
static std::atomic<bool> stop{false};
static std::atomic<unsigned long long> naps{0};
static std::atomic<KernelUseconds> napMicros{0};
static void APS5_VABI Handler(int, void*) { deliveries.fetch_add(1); }
static void* APS5_VABI Napper(void*) {
while (!stop.load()) {
sceKernelUsleep_nid_postfix(napMicros.load());
naps.fetch_add(1);
}
return nullptr;
}
static void RaiseAtNapper(KernelUseconds micros) {
napMicros.store(micros);
stop.store(false);
naps.store(0);
Pthread napper = nullptr;
Require(scePthreadCreate(&napper, nullptr, Napper, nullptr, "napper") == 0);
while (naps.load() == 0) std::this_thread::yield();
for (int raise = 0; raise < Raises; ++raise) {
const int before = deliveries.load();
Require(sceKernelRaiseException(napper, SigUsr1) == 0);
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(3);
while (deliveries.load() == before) {
if (std::chrono::steady_clock::now() >= deadline) {
std::fprintf(stderr, "raise %d at a thread sleeping %u us in a loop was not delivered within 3 s (%llu sleeps since)\n", raise, micros, naps.load());
std::abort();
}
std::this_thread::yield();
}
}
stop.store(true);
Require(scePthreadJoin(napper, nullptr) == 0);
}
int main() {
std::atomic<bool> done{false};
std::thread watchdog([&done] {
for (int i = 0; i < 20000 && !done.load(); ++i) std::this_thread::sleep_for(std::chrono::milliseconds(1));
if (!done.load()) std::abort();
});
Require(sceKernelInstallExceptionHandler(SigUsr1, reinterpret_cast<void*>(&Handler)) == 0);
for (const KernelUseconds micros : {100u, 200u, 400u}) RaiseAtNapper(micros);
Require(sceKernelRemoveExceptionHandler(SigUsr1) == 0);
std::printf("%d raises delivered\n", deliveries.load());
done.store(true);
watchdog.join();
}