Files
AnyPS5/core/libs/tests/GuestKernelSysAio.cpp
T
LeandroLP23andClaude Opus 5.5 adef5635c8 fix(libkernel_sys): wait for AIO timeouts until one deadline, precisely on Windows
aio_suspend and aio_waitcomplete waited through std::condition_variable,
which winpthreads ends only on 15.6 ms GetTickCount64 steps (#1471). They
also restarted the full timeout after every wake that did not finish their
wait, so completions of other requests could keep them waiting past it.
Both now compute one deadline and wait on TimedWait::Condition until it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-27 08:50:54 +01:00

250 lines
9.9 KiB
C++

#include "prx/libkernel_sys/include/Api.hpp"
#include "prx/libkernel/include/Api.hpp"
#include "prx/libc/include/Api.hpp"
#include "SceTypes.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <atomic>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <limits>
#include <source_location>
#include <string>
#include <thread>
namespace {
}
extern "C" {
void AioHoldWorkerForTest_nid_no_patch(bool hold);
}
namespace {
constexpr int Ebadf = 9;
constexpr int Efault = 14;
constexpr int Einval = 22;
constexpr int Eagain = 35;
constexpr int Einprogress = 36;
constexpr int Eopnotsupp = 45;
constexpr int Ecanceled = 85;
constexpr int ORdonly = 0;
constexpr int ORdwr = 2;
constexpr int OCreat = 0x200;
constexpr int OSync = 0x80;
constexpr int ODsync = 0x1000000;
constexpr int AioCanceled = 1;
constexpr int AioAllDone = 3;
void Require(bool condition, std::source_location location = std::source_location::current()) {
if (condition) return;
std::fprintf(stderr, "check failed at line %u\n", static_cast<unsigned>(location.line()));
std::exit(1);
}
int Error() {
return *__error_nid_postfix();
}
GuestAiocb Request(int descriptor, void* buffer, std::uint64_t bytes, std::int64_t offset = 0) {
GuestAiocb request{};
request.fildes = descriptor;
request.buf = buffer;
request.nbytes = bytes;
request.offset = offset;
return request;
}
void Wait(GuestAiocb& request) {
const GuestAiocb* list[] = {&request};
Require(aio_suspend_nid_postfix(list, 1, nullptr) == 0);
}
void BeforeUse() {
GuestAiocb request{};
const GuestAiocb* list[] = {&request};
Require(aio_error_nid_postfix(&request) == Einval);
Require(aio_return_nid_postfix(&request) == -1 && Error() == Einval);
Require(aio_suspend_nid_postfix(list, 1, nullptr) == -1 && Error() == Eagain);
}
void Validation(int file, int readOnly) {
char buffer[8] = {};
Require(aio_read_nid_postfix(nullptr) == -1 && Error() == Efault);
auto bad = Request(-1, buffer, sizeof(buffer));
Require(aio_read_nid_postfix(&bad) == -1 && Error() == Ebadf);
Require(bad.status == -1 && bad.error == Ebadf && aio_error_nid_postfix(&bad) == Ebadf);
auto readOnlyWrite = Request(readOnly, buffer, sizeof(buffer));
Require(aio_write_nid_postfix(&readOnlyWrite) == -1 && Error() == Ebadf);
auto negative = Request(file, buffer, sizeof(buffer), -1);
Require(aio_read_nid_postfix(&negative) == -1 && Error() == Einval && negative.error == Einval);
auto huge = Request(file, buffer, std::uint64_t{std::numeric_limits<int>::max()} + 1);
Require(aio_read_nid_postfix(&huge) == -1 && Error() == Einval && huge.error == 0);
auto notify = Request(file, buffer, sizeof(buffer));
notify.sigevent.notify = 2;
Require(aio_read_nid_postfix(&notify) == -1 && Error() == Einval && notify.error == Einval);
notify.sigevent = {1, 0, 0, {}};
Require(aio_read_nid_postfix(&notify) == -1 && Error() == Einval);
int pipe[2] = {-1, -1};
Require(pipe_nid_postfix(pipe) == 0);
auto piped = Request(pipe[0], buffer, sizeof(buffer));
Require(aio_read_nid_postfix(&piped) == -1 && Error() == Eopnotsupp && piped.error == Eopnotsupp);
auto pipeSync = Request(pipe[1], nullptr, 0);
Require(aio_fsync_nid_postfix(OSync, &pipeSync) == -1 && Error() == Einval);
Require(close_nid_postfix(pipe[0]) == 0 && close_nid_postfix(pipe[1]) == 0);
auto sync = Request(file, nullptr, 0);
Require(aio_fsync_nid_postfix(0, &sync) == -1 && Error() == Einval);
Require(aio_fsync_nid_postfix(ODsync, &sync) == -1 && Error() == Einval);
}
void ReadAndWrite(int file) {
char text[] = "asynchronous";
auto write = Request(file, text, 12, 4);
Require(aio_write_nid_postfix(&write) == 0);
Wait(write);
Require(aio_error_nid_postfix(&write) == 0);
Require(aio_return_nid_postfix(&write) == 12 && write.status == 12 && write.error == 0);
Require(aio_return_nid_postfix(&write) == -1 && Error() == Einval);
Require(aio_error_nid_postfix(&write) == Einval);
char read[16] = {};
auto request = Request(file, read, sizeof(read), 6);
Require(aio_read_nid_postfix(&request) == 0);
Wait(request);
Require(aio_return_nid_postfix(&request) == 10 && std::memcmp(read, "ynchronous", 10) == 0);
auto empty = Request(file, read, 0, 0);
Require(aio_read_nid_postfix(&empty) == 0);
Wait(empty);
Require(aio_return_nid_postfix(&empty) == 0);
const GuestAiocb* none[] = {&request};
Require(aio_suspend_nid_postfix(none, 1, nullptr) == 0);
Require(aio_suspend_nid_postfix(none, 0, nullptr) == 0);
Require(aio_suspend_nid_postfix(none, -1, nullptr) == -1 && Error() == Einval);
Require(aio_suspend_nid_postfix(none, 257, nullptr) == -1 && Error() == Einval);
const KernelTimespec badTime{0, 1000000000};
Require(aio_suspend_nid_postfix(none, 1, &badTime) == -1 && Error() == Einval);
}
void Cancel(int file) {
char buffer[4] = {};
AioHoldWorkerForTest_nid_no_patch(true);
auto first = Request(file, buffer, sizeof(buffer));
auto second = Request(file, buffer, sizeof(buffer));
Require(aio_read_nid_postfix(&first) == 0 && aio_read_nid_postfix(&second) == 0);
Require(first.error == Einprogress && aio_error_nid_postfix(&first) == Einprogress);
const GuestAiocb* list[] = {&first};
const KernelTimespec brief{0, 5000000};
Require(aio_suspend_nid_postfix(list, 1, &brief) == -1 && Error() == Eagain);
Require(aio_cancel_nid_postfix(file, &first) == AioCanceled);
Require(aio_error_nid_postfix(&first) == Ecanceled && aio_error_nid_postfix(&second) == Einprogress);
Require(aio_suspend_nid_postfix(list, 1, &brief) == 0);
Require(aio_return_nid_postfix(&first) == -1 && Error() == Ecanceled);
Require(first.status == -1 && first.error == Ecanceled);
Require(aio_return_nid_postfix(&first) == -1 && Error() == Einval);
Require(aio_cancel_nid_postfix(file, &first) == AioAllDone);
Require(aio_cancel_nid_postfix(file, nullptr) == AioCanceled);
Require(aio_return_nid_postfix(&second) == -1 && Error() == Ecanceled);
Require(aio_cancel_nid_postfix(file, nullptr) == AioAllDone);
Require(aio_cancel_nid_postfix(-1, nullptr) == -1 && Error() == Ebadf);
AioHoldWorkerForTest_nid_no_patch(false);
}
void SyncOrder(int file) {
char text[] = "ordered";
AioHoldWorkerForTest_nid_no_patch(true);
auto write = Request(file, text, 7, 0);
auto sync = Request(file, nullptr, 0);
Require(aio_write_nid_postfix(&write) == 0 && aio_fsync_nid_postfix(OSync, &sync) == 0);
Require(aio_error_nid_postfix(&sync) == Einprogress);
AioHoldWorkerForTest_nid_no_patch(false);
GuestAiocb* done = nullptr;
Require(aio_waitcomplete_nid_postfix(&done, nullptr) == 7 && done == &write);
Require(aio_waitcomplete_nid_postfix(&done, nullptr) == 0 && done == &sync && sync.status == 0);
const KernelTimespec zero{0, 0};
Require(aio_waitcomplete_nid_postfix(&done, &zero) == -1 && Error() == Eagain && done == nullptr);
const KernelTimespec brief{0, 5000000};
Require(aio_waitcomplete_nid_postfix(&done, &brief) == -1 && Error() == Eagain);
const KernelTimespec badTime{0, -1};
Require(aio_waitcomplete_nid_postfix(&done, &badTime) == -1 && Error() == Einval);
}
void TimedWaits(int file) {
using Clock = std::chrono::steady_clock;
char buffer[4] = {};
AioHoldWorkerForTest_nid_no_patch(true);
auto held = Request(file, buffer, sizeof(buffer));
Require(aio_read_nid_postfix(&held) == 0);
const GuestAiocb* list[] = {&held};
const KernelTimespec millisecond{0, 1000000};
auto start = Clock::now();
for (int round = 0; round < 20; ++round) Require(aio_suspend_nid_postfix(list, 1, &millisecond) == -1 && Error() == Eagain);
auto elapsed = Clock::now() - start;
Require(elapsed >= std::chrono::milliseconds(20) && elapsed < std::chrono::milliseconds(200));
GuestAiocb* done = nullptr;
start = Clock::now();
for (int round = 0; round < 20; ++round) Require(aio_waitcomplete_nid_postfix(&done, &millisecond) == -1 && Error() == Eagain);
elapsed = Clock::now() - start;
Require(elapsed >= std::chrono::milliseconds(20) && elapsed < std::chrono::milliseconds(200));
std::atomic<bool> stop{false};
std::thread waker([&] {
char other[4] = {};
const auto end = Clock::now() + std::chrono::milliseconds(600);
while (!stop.load() && Clock::now() < end) {
auto request = Request(file, other, sizeof(other));
Require(aio_read_nid_postfix(&request) == 0);
Require(aio_cancel_nid_postfix(file, &request) == AioCanceled);
Require(aio_return_nid_postfix(&request) == -1 && Error() == Ecanceled);
std::this_thread::yield();
}
});
const KernelTimespec brief{0, 30000000};
start = Clock::now();
Require(aio_suspend_nid_postfix(list, 1, &brief) == -1 && Error() == Eagain);
elapsed = Clock::now() - start;
stop = true;
waker.join();
Require(elapsed >= std::chrono::milliseconds(30) && elapsed < std::chrono::milliseconds(300));
Require(aio_cancel_nid_postfix(file, &held) == AioCanceled);
Require(aio_return_nid_postfix(&held) == -1 && Error() == Ecanceled);
AioHoldWorkerForTest_nid_no_patch(false);
}
}
int main() {
const auto path = "anyps5-kernel-sys-aio-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + ".bin";
BeforeUse();
const int file = open_nid_postfix(path.c_str(), ORdwr | OCreat, 0600);
const int readOnly = open_nid_postfix(path.c_str(), ORdonly, 0);
Require(file >= 0 && readOnly >= 0);
Validation(file, readOnly);
ReadAndWrite(file);
Cancel(file);
SyncOrder(file);
TimedWaits(file);
Require(close_nid_postfix(readOnly) == 0 && close_nid_postfix(file) == 0);
Require(std::filesystem::remove(path));
std::puts("guest_kernel_sys_aio passed");
return 0;
}