Files
AnyPS5/core/libs/tests/GuestSelect.cpp
T
fdfawef324andClaude Opus 5.5 ce0679c03b fix(libkernel): use precise waits for select and poll on pipes on Windows
On Windows, select over pipes polls readiness in HostPoll and sleeps
std::this_thread::sleep_for(1 ms) between rounds, and poll with pipes
but no native sockets sleeps sleep_for(slice). winpthreads ends these
sleeps on 15.6 ms timer ticks (#1471), so a guest select or poll with a
short timeout on an idle pipe waits about 15 ms per call instead of its
timeout, and a longer wait checks the pipes only every 15.6 ms.

Both paths now sleep with PreciseSleepUntil. Each sleep requests at
most 1 ms and ends no later than the deadline; the old HostPoll sleep
could pass its deadline by a whole tick. poll on pipes also stops
spinning through the last partial millisecond before its deadline,
where the remaining time truncated to 0 ms and no sleep was made.
Infinite timeouts still sleep 1 ms per round.

The pipes are now checked about every 1.5 ms instead of every 15.6 ms:
a pipe that becomes ready is noticed sooner, and a thread blocked on an
idle pipe wakes about ten times as often. A 1 ms poll on an idle pipe
returns after about 1.5 ms instead of spinning for 1 ms.

guest_poll times 20 polls with a 2 ms timeout on an empty pipe read end
and guest_select times 20 selects with a 1 ms timeout on it; each set
must take less than 200 ms. A 1 ms poll does not reach the old sleep,
because the remaining time truncates to 0 ms and the old loop spins
until the deadline, so the poll check uses 2 ms. On Windows Server 2025
both sets took 312 ms before this change and about 50 ms and 30 ms
after it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:14:11 -04:00

177 lines
6.7 KiB
C++

#include "prx/libc/include/general/VabiMacros.hpp"
#include <array>
#include <chrono>
#include <cstdint>
#include <cstdlib>
struct GuestTimeval {
std::int64_t seconds;
std::int64_t microseconds;
};
struct GuestDescriptorSet {
std::array<std::uint64_t, 1024 / 64> words{};
void Add(int descriptor) {
words[descriptor / 64] |= (std::uint64_t{1} << (descriptor % 64));
}
bool Has(int descriptor) const {
return ((words[descriptor / 64] >> (descriptor % 64)) & 1u) != 0;
}
};
extern "C" {
int APS5_VABI select_nid_postfix(int, void*, void*, void*, const void*);
int APS5_VABI pipe_nid_postfix(int*);
int APS5_VABI close_nid_postfix(int);
int APS5_VABI socket_nid_postfix(int, int, int);
int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t);
int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*);
int APS5_VABI connect_nid_postfix(int, const void*, std::uint32_t);
std::int64_t APS5_VABI send_nid_postfix(int, const void*, std::uint64_t, int);
std::int64_t APS5_VABI recv_nid_postfix(int, void*, std::uint64_t, int);
std::int64_t APS5_VABI write_nid_postfix(int, const char*, std::int64_t);
std::int64_t APS5_VABI read_nid_postfix(int, void*, std::uint64_t);
int* APS5_VABI __error_nid_postfix();
}
static void Require(bool value) { if (!value) std::abort(); }
constexpr int GuestEbadf = 9;
constexpr int GuestEinval = 22;
constexpr int GuestDescriptorLimit = 1024;
int main() {
int descriptors[2]{-1, -1};
Require(pipe_nid_postfix(descriptors) == 0);
const int reader = descriptors[0];
const int writer = descriptors[1];
Require(reader >= 0 && writer >= 0 && reader != writer);
{
GuestDescriptorSet read{};
read.Add(reader);
GuestTimeval limit{0, 200000};
const auto start = std::chrono::steady_clock::now();
Require(select_nid_postfix(reader + 1, read.words.data(), nullptr, nullptr, &limit) == 0);
Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(150));
Require(!read.Has(reader));
}
{
const GuestTimeval limit{0, 1000};
const auto start = std::chrono::steady_clock::now();
for (int i = 0; i < 20; ++i) {
GuestDescriptorSet read{};
read.Add(reader);
Require(select_nid_postfix(reader + 1, read.words.data(), nullptr, nullptr, &limit) == 0);
Require(!read.Has(reader));
}
const auto elapsed = std::chrono::steady_clock::now() - start;
Require(elapsed >= std::chrono::milliseconds(20) && elapsed < std::chrono::milliseconds(200));
}
{
const char byte = 's';
Require(write_nid_postfix(writer, &byte, 1) == 1);
GuestDescriptorSet read{};
GuestDescriptorSet except{};
read.Add(reader);
except.Add(reader);
GuestTimeval limit{0, 200000};
Require(select_nid_postfix(reader + 1, read.words.data(), nullptr, except.words.data(), &limit) == 1);
Require(read.Has(reader));
Require(!except.Has(reader));
char buffer[1]{};
Require(read_nid_postfix(reader, buffer, sizeof(buffer)) == 1);
Require(buffer[0] == byte);
}
{
GuestDescriptorSet write{};
write.Add(writer);
GuestTimeval limit{0, 200000};
Require(select_nid_postfix(writer + 1, nullptr, write.words.data(), nullptr, &limit) == 1);
Require(write.Has(writer));
}
{
const char byte = 'e';
Require(write_nid_postfix(writer, &byte, 1) == 1);
GuestDescriptorSet read{};
GuestDescriptorSet write{};
read.Add(reader);
write.Add(writer);
const int highest = reader > writer ? reader : writer;
GuestTimeval limit{0, 200000};
Require(select_nid_postfix(highest + 1, read.words.data(), write.words.data(), nullptr, &limit) == 2);
Require(read.Has(reader) && write.Has(writer));
}
{
GuestDescriptorSet read{};
read.Add(reader);
GuestTimeval limit{0, 100000};
Require(select_nid_postfix(reader, read.words.data(), nullptr, nullptr, &limit) == 0);
Require(read.Has(reader));
}
{
GuestDescriptorSet read{};
read.Add(reader);
GuestTimeval limit{0, 50000};
Require(select_nid_postfix(GuestDescriptorLimit + 1, read.words.data(), nullptr, nullptr, &limit) == -1);
Require(*__error_nid_postfix() == GuestEinval);
}
{
int pipe[2]{-1, -1};
Require(pipe_nid_postfix(pipe) == 0);
const int receiver = socket_nid_postfix(2, 2, 0);
const int sender = socket_nid_postfix(2, 2, 0);
Require(receiver >= 0 && receiver < GuestDescriptorLimit && sender >= 0 && sender < GuestDescriptorLimit);
std::array<unsigned char, 16> address{16, 2, 0, 0, 127, 0, 0, 1};
Require(bind_nid_postfix(receiver, address.data(), address.size()) == 0);
std::uint32_t size = address.size();
Require(getsockname_nid_postfix(receiver, address.data(), &size) == 0);
Require(connect_nid_postfix(sender, address.data(), address.size()) == 0);
const int highest = receiver > pipe[0] ? receiver : pipe[0];
GuestDescriptorSet idle{};
idle.Add(receiver);
idle.Add(pipe[0]);
GuestTimeval none{0, 0};
Require(select_nid_postfix(highest + 1, idle.words.data(), nullptr, nullptr, &none) == 0);
Require(!idle.Has(receiver) && !idle.Has(pipe[0]));
const char byte = 'm';
Require(send_nid_postfix(sender, &byte, 1, 0) == 1);
Require(write_nid_postfix(pipe[1], &byte, 1) == 1);
GuestDescriptorSet read{};
GuestTimeval limit{2, 0};
const auto start = std::chrono::steady_clock::now();
int ready = 0;
while (ready < 2 && std::chrono::steady_clock::now() - start < std::chrono::seconds(2)) {
read = {};
read.Add(receiver);
read.Add(pipe[0]);
ready = select_nid_postfix(highest + 1, read.words.data(), nullptr, nullptr, &limit);
}
Require(ready == 2 && read.Has(receiver) && read.Has(pipe[0]));
char buffer[1]{};
Require(recv_nid_postfix(receiver, buffer, sizeof(buffer), 0) == 1 && buffer[0] == byte);
Require(read_nid_postfix(pipe[0], buffer, sizeof(buffer)) == 1 && buffer[0] == byte);
for (const int descriptor : {receiver, sender, pipe[0], pipe[1]}) Require(close_nid_postfix(descriptor) == 0);
}
Require(close_nid_postfix(reader) == 0);
Require(close_nid_postfix(writer) == 0);
{
GuestDescriptorSet read{};
read.Add(reader);
GuestTimeval limit{0, 50000};
Require(select_nid_postfix(reader + 1, read.words.data(), nullptr, nullptr, &limit) == -1);
Require(*__error_nid_postfix() == GuestEbadf);
}
}