mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-29 12:15:39 +00:00
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]>
89 lines
3.2 KiB
C++
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();
|
|
}
|