Files
AnyPS5/core/libs/tests/GuestRaiseInSystemCall.cpp
T
LeandroLP23andClaude Opus 5.5 493aac4e6c test(libkernel): raise only once the writer is blocked inside its write
The writer used to publish its block number just before fwrite, so a
delayed raise could land before the call and pass without exercising the
system-call path. The stream is now unbuffered, so each block is one 16 KiB
WriteFile into the 4 KiB pipe. The reader counts the bytes, signals when the
first byte of a block arrives (the writer is then inside WriteFile with more
than a pipe's worth left), and stops reading until the main thread has
started the raise, then 100 ms more. It checks the write has not returned,
and the handler checks the delivery came after it did.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-25 09:55:10 +01:00

89 lines
3.2 KiB
C++

#include "prx/libkernel/include/Api.hpp"
#include "SceTypes.hpp"
#include <atomic>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <fcntl.h>
#include <io.h>
#include <string>
#include <thread>
#include <windows.h>
static constexpr int SigUsr1 = 30;
static constexpr int Raises = 10;
static constexpr DWORD PipeBytes = 4096;
static constexpr std::uint64_t BlockBytes = 4u * PipeBytes;
static void Require(bool value) { if (!value) std::abort(); }
static std::FILE* shared = nullptr;
static std::atomic<int> deliveries{0};
static std::atomic<int> released{0};
static std::atomic<int> written{0};
static std::atomic<int> blocked{0};
static std::atomic<int> raising{0};
static void APS5_VABI Handler(int, void*) {
const auto delivery = deliveries.fetch_add(1) + 1;
Require(written.load() == delivery);
while (released.load() < delivery) std::this_thread::yield();
}
static void* APS5_VABI Writer(void*) {
const std::string block(BlockBytes, 'x');
for (int raise = 1; raise <= Raises; ++raise) {
Require(std::fwrite(block.data(), 1, block.size(), shared) == block.size());
written.store(raise);
while (deliveries.load() < raise) {
}
}
return nullptr;
}
int main() {
std::atomic<bool> done{false};
std::thread watchdog([&done] {
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(25);
while (!done.load() && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::milliseconds(10));
if (!done.load()) std::abort();
});
HANDLE read = nullptr;
HANDLE write = nullptr;
Require(CreatePipe(&read, &write, nullptr, PipeBytes) != 0);
shared = _fdopen(_open_osfhandle(reinterpret_cast<intptr_t>(write), _O_WRONLY | _O_BINARY), "wb");
Require(shared != nullptr && std::setvbuf(shared, nullptr, _IONBF, 0) == 0);
std::thread reader([&] {
char chunk[PipeBytes];
DWORD got = 0;
std::uint64_t total = 0;
int block = 0;
while (ReadFile(read, chunk, sizeof(chunk), &got, nullptr) != 0 && got != 0) {
total += got;
if (block == Raises || total <= static_cast<std::uint64_t>(block) * (BlockBytes + 1)) continue;
blocked.store(++block);
while (raising.load() < block) std::this_thread::yield();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
Require(written.load() < block);
}
});
Require(sceKernelInstallExceptionHandler(SigUsr1, reinterpret_cast<void*>(&Handler)) == 0);
Pthread writer = nullptr;
Require(scePthreadCreate(&writer, nullptr, Writer, nullptr, "pipe writer") == 0);
for (int raise = 1; raise <= Raises; ++raise) {
while (blocked.load() < raise) std::this_thread::yield();
raising.store(raise);
Require(sceKernelRaiseException(writer, SigUsr1) == 0);
while (deliveries.load() < raise) std::this_thread::yield();
Require(std::fputc('y', shared) == 'y');
released.store(raise);
}
Require(scePthreadJoin(writer, nullptr) == 0);
Require(sceKernelRemoveExceptionHandler(SigUsr1) == 0);
std::fclose(shared);
reader.join();
CloseHandle(read);
done.store(true);
watchdog.join();
}