Files
KytyPS5/tests/EventQueueLifetimeTests.cpp
T
2026-07-27 02:27:29 +02:00

501 lines
19 KiB
C++

#include "kernel/eventQueue.h"
#include "libs/errno.h"
#include <algorithm>
#include <atomic>
#include <cstdio>
#include <cstdlib>
#include <memory>
#include <mutex>
#include <thread>
#include <vector>
namespace {
namespace EventQueue = Libs::LibKernel::EventQueue;
using Libs::LibKernel::KERNEL_ERROR_EBADF;
using Libs::LibKernel::KERNEL_ERROR_ENOENT;
void Check(bool value, const char *text) {
if (!value) {
std::fprintf(stderr, "EventQueueLifetimeTests: failed: %s\n", text);
std::abort();
}
}
void CheckConcurrentResult(int result, const char *text) {
Check(result == OK || result == KERNEL_ERROR_EBADF ||
result == KERNEL_ERROR_ENOENT,
text);
}
void CountDeletedEvent(EventQueue::KernelEqueue,
EventQueue::KernelEqueueEvent *event) {
auto *count = static_cast<std::atomic_uint32_t *>(event->filter.data);
count->fetch_add(1, std::memory_order_relaxed);
}
struct DuplicateEventOwner {
std::atomic_uint32_t delete_count{0};
};
void QueueDuplicateEvent(EventQueue::KernelEqueueEvent *event,
void *trigger_data) {
auto next = event->event;
next.data = reinterpret_cast<intptr_t>(trigger_data);
if (event->triggered) {
event->pending_events.push_back(next);
} else {
event->event = next;
event->triggered = true;
}
}
void ResetDuplicateEvent(EventQueue::KernelEqueueEvent *event) {
event->triggered = false;
event->event.data = 0;
}
void DeleteDuplicateEvent(EventQueue::KernelEqueue,
EventQueue::KernelEqueueEvent *event) {
auto *owner = static_cast<DuplicateEventOwner *>(event->filter.data);
owner->delete_count.fetch_add(1, std::memory_order_relaxed);
}
void PoisonDuplicateEvent(EventQueue::KernelEqueueEvent *, void *) {
Check(false, "duplicate add replaced trigger callback");
}
void TestDuplicateAddPreservesEventState() {
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
Check(EventQueue::KernelCreateEqueue(&queue, "duplicate-add") == OK,
"create duplicate add queue");
auto original_owner = std::make_shared<DuplicateEventOwner>();
std::weak_ptr<DuplicateEventOwner> weak_original = original_owner;
EventQueue::KernelEqueueEvent original{};
original.event.ident = 17;
original.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
original.event.udata = reinterpret_cast<void *>(0x1111);
original.filter.data = original_owner.get();
original.filter.owner = original_owner;
original.filter.trigger_func = QueueDuplicateEvent;
original.filter.reset_func = ResetDuplicateEvent;
original.filter.delete_event_func = DeleteDuplicateEvent;
Check(EventQueue::KernelAddEvent(queue, original) == OK,
"add original duplicate event");
Check(EventQueue::KernelTriggerEvent(queue, 17,
EventQueue::KERNEL_EVFILT_VIDEO_OUT,
reinterpret_cast<void *>(0x1234)) == OK,
"queue first trigger");
Check(EventQueue::KernelTriggerEvent(queue, 17,
EventQueue::KERNEL_EVFILT_VIDEO_OUT,
reinterpret_cast<void *>(0x5678)) == OK,
"queue pending trigger");
auto replacement_owner = std::make_shared<DuplicateEventOwner>();
std::weak_ptr<DuplicateEventOwner> weak_replacement = replacement_owner;
EventQueue::KernelEqueueEvent duplicate{};
duplicate.triggered = false;
duplicate.deadline_ns = 1;
duplicate.event.ident = 17;
duplicate.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
duplicate.event.data = 0x7fffffff;
duplicate.event.udata = reinterpret_cast<void *>(0x2222);
duplicate.filter.data = replacement_owner.get();
duplicate.filter.owner = replacement_owner;
duplicate.filter.trigger_func = PoisonDuplicateEvent;
Check(EventQueue::KernelAddEvent(queue, duplicate) == OK,
"update duplicate event");
duplicate.filter.owner.reset();
replacement_owner.reset();
Check(weak_replacement.expired(), "duplicate owner is not retained");
original.filter.owner.reset();
original_owner.reset();
Check(!weak_original.expired(), "original event owner remains retained");
EventQueue::KernelEvent events[2]{};
int out = 0;
Libs::LibKernel::KernelUseconds timeout = 0;
Check(EventQueue::KernelWaitEqueue(queue, events, 2, &out, &timeout) == OK,
"read queued duplicate triggers");
Check(out == 2, "duplicate add preserves pending event count");
Check(events[0].data == 0x1234 && events[1].data == 0x5678,
"duplicate add preserves current and pending event data");
Check(events[0].udata == reinterpret_cast<void *>(0x2222),
"duplicate add updates current user data");
Check(events[1].udata == reinterpret_cast<void *>(0x2222),
"duplicate add updates pending user data");
EventQueue::KernelEvent timer_event{};
Check(EventQueue::KernelWaitEqueue(queue, &timer_event, 1, &out, &timeout) ==
OK &&
out == 1,
"duplicate add updates deadline metadata");
Check(timer_event.data == 0 &&
timer_event.udata == reinterpret_cast<void *>(0x2222),
"deadline trigger retains updated duplicate metadata");
auto retained_owner = weak_original.lock();
Check(retained_owner != nullptr, "original owner alive before delete");
Check(EventQueue::KernelDeleteEvent(queue, 17,
EventQueue::KERNEL_EVFILT_VIDEO_OUT) ==
OK,
"delete duplicate event");
Check(retained_owner->delete_count.load(std::memory_order_relaxed) == 1,
"duplicate add preserves delete callback");
retained_owner.reset();
Check(weak_original.expired(), "original owner released on delete");
Check(EventQueue::KernelDeleteEqueue(queue) == OK,
"delete duplicate add queue");
}
struct SimulatedVideoOutEventState;
struct SimulatedVideoOutRegistration {
EventQueue::KernelEqueue handle = EventQueue::KERNEL_EQUEUE_INVALID;
std::shared_ptr<SimulatedVideoOutEventState> state;
uint64_t marker = 0x123456789abcdef0ull;
};
struct SimulatedVideoOutEventState {
SimulatedVideoOutEventState(std::atomic_uint32_t &stage,
std::atomic_uint32_t &destroy_count)
: stage(stage), destroy_count(destroy_count) {}
~SimulatedVideoOutEventState() {
destroy_count.fetch_add(1, std::memory_order_relaxed);
}
std::mutex mutex;
std::vector<std::shared_ptr<SimulatedVideoOutRegistration>> queues;
std::atomic_uint32_t &stage;
std::atomic_uint32_t &destroy_count;
uint64_t marker = 0xfedcba9876543210ull;
};
void DetachSimulatedVideoOutEvent(EventQueue::KernelEqueue queue,
EventQueue::KernelEqueueEvent *event) {
auto *registration =
static_cast<SimulatedVideoOutRegistration *>(event->filter.data);
Check(registration != nullptr && registration->handle == queue,
"simulated registration identity");
auto state = registration->state;
Check(state != nullptr, "simulated event owns shared state");
state->stage.store(1, std::memory_order_release);
while (state->stage.load(std::memory_order_acquire) != 2) {
std::this_thread::yield();
}
{
std::lock_guard lock(state->mutex);
const auto entry =
std::find_if(state->queues.begin(), state->queues.end(),
[registration](const auto &candidate) {
return candidate.get() == registration;
});
if (entry != state->queues.end()) {
state->queues.erase(entry);
}
}
event->filter.owner.reset();
Check(state->marker == 0xfedcba9876543210ull &&
registration->marker == 0x123456789abcdef0ull,
"callback state survives simulated port destruction");
}
void TestCallbackStateOutlivesPort() {
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
Check(EventQueue::KernelCreateEqueue(&queue, "shared-port-state") == OK,
"create shared port state queue");
std::atomic_uint32_t stage{0};
std::atomic_uint32_t destroy_count{0};
auto port_state =
std::make_shared<SimulatedVideoOutEventState>(stage, destroy_count);
std::weak_ptr<SimulatedVideoOutEventState> weak_state = port_state;
auto registration = std::make_shared<SimulatedVideoOutRegistration>();
std::weak_ptr<SimulatedVideoOutRegistration> weak_registration =
registration;
registration->handle = queue;
registration->state = port_state;
port_state->queues.push_back(registration);
EventQueue::KernelEqueueEvent event{};
event.event.ident = 8;
event.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
event.filter.data = registration.get();
event.filter.owner = registration;
event.filter.delete_event_func = DetachSimulatedVideoOutEvent;
Check(EventQueue::KernelAddEvent(queue, event) == OK,
"add shared port state event");
event.filter.owner.reset();
std::jthread close([&] {
Check(EventQueue::KernelDeleteEqueue(queue) == OK,
"delete shared port state queue");
});
while (stage.load(std::memory_order_acquire) != 1) {
std::this_thread::yield();
}
std::vector<std::shared_ptr<SimulatedVideoOutRegistration>> detached;
{
std::lock_guard lock(port_state->mutex);
detached = std::move(port_state->queues);
}
registration.reset();
detached.clear();
port_state.reset();
Check(!weak_state.expired(),
"callback state outlives simulated port object");
Check(!weak_registration.expired(),
"registration outlives simulated port object");
stage.store(2, std::memory_order_release);
close.join();
Check(weak_registration.expired(), "detached registration is released");
Check(weak_state.expired(), "detached event state is released");
Check(destroy_count.load(std::memory_order_relaxed) == 1,
"shared event state is destroyed exactly once");
}
struct OwnedCallbackPayload {
OwnedCallbackPayload(std::atomic_uint32_t &stage,
std::atomic_uint32_t &delete_count,
std::atomic_uint32_t &destroy_count)
: stage(stage), delete_count(delete_count), destroy_count(destroy_count) {}
std::atomic_uint32_t &stage;
std::atomic_uint32_t &delete_count;
std::atomic_uint32_t &destroy_count;
uint64_t marker = 0xc0dec0dec0dec0deull;
~OwnedCallbackPayload() {
destroy_count.fetch_add(1, std::memory_order_relaxed);
}
};
void DeleteOwnedEvent(EventQueue::KernelEqueue queue,
EventQueue::KernelEqueueEvent *event) {
auto *payload = static_cast<OwnedCallbackPayload *>(event->filter.data);
Check(payload != nullptr, "owned callback payload");
Check(!EventQueue::KernelPinEqueue(queue),
"owned callback runs after registry removal");
payload->delete_count.fetch_add(1, std::memory_order_relaxed);
event->filter.owner.reset();
payload->stage.store(1, std::memory_order_release);
while (payload->stage.load(std::memory_order_acquire) != 2) {
std::this_thread::yield();
}
Check(payload->marker == 0xc0dec0dec0dec0deull,
"owned callback payload remains valid");
}
void TestCallbackOwnsPayload() {
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
Check(EventQueue::KernelCreateEqueue(&queue, "owned-callback") == OK,
"create owned callback queue");
std::atomic_uint32_t stage{0};
std::atomic_uint32_t delete_count{0};
std::atomic_uint32_t destroy_count{0};
auto registration =
std::make_shared<OwnedCallbackPayload>(stage, delete_count, destroy_count);
std::weak_ptr<OwnedCallbackPayload> weak_registration = registration;
std::vector<std::shared_ptr<OwnedCallbackPayload>> port_registrations{
registration};
{
EventQueue::KernelEqueueEvent event{};
event.event.ident = 2;
event.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
event.filter.data = registration.get();
event.filter.owner = registration;
event.filter.delete_event_func = DeleteOwnedEvent;
Check(EventQueue::KernelAddEvent(queue, event) == OK,
"add owned callback event");
}
std::jthread close([&] {
Check(EventQueue::KernelDeleteEqueue(queue) == OK,
"delete owned callback queue");
});
while (stage.load(std::memory_order_acquire) != 1) {
std::this_thread::yield();
}
Check(!EventQueue::KernelPinEqueue(queue),
"owned callback queue removed while callback blocked");
port_registrations.clear();
registration.reset();
Check(!weak_registration.expired(),
"delete callback retains detached payload");
stage.store(2, std::memory_order_release);
close.join();
Check(delete_count.load(std::memory_order_relaxed) == 1,
"owned callback runs exactly once");
Check(weak_registration.expired(),
"owned callback payload released with event");
Check(destroy_count.load(std::memory_order_relaxed) == 1,
"owned callback payload destroyed exactly once");
}
void TestPinnedClose() {
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
Check(EventQueue::KernelCreateEqueue(&queue, "pinned-close") == OK,
"create pinned queue");
std::atomic_uint32_t delete_count{0};
EventQueue::KernelEqueueEvent event{};
event.event.ident = 1;
event.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
event.filter.data = &delete_count;
event.filter.delete_event_func = CountDeletedEvent;
Check(EventQueue::KernelAddEvent(queue, event) == OK, "add callback event");
auto owner = EventQueue::KernelPinEqueue(queue);
Check(owner != nullptr, "pin live queue");
Check(EventQueue::KernelDeleteEqueue(queue) == OK, "delete pinned queue");
Check(delete_count.load(std::memory_order_relaxed) == 1,
"close invokes callback once");
Check(!EventQueue::KernelPinEqueue(queue), "deleted queue leaves registry");
Check(EventQueue::KernelTriggerEvent(queue, 1,
EventQueue::KERNEL_EVFILT_VIDEO_OUT,
nullptr) == KERNEL_ERROR_EBADF,
"stale trigger rejected");
Check(EventQueue::KernelDeleteEqueue(queue) == KERNEL_ERROR_EBADF,
"second queue delete rejected");
owner.reset();
Check(delete_count.load(std::memory_order_relaxed) == 1,
"deferred destruction does not repeat callback");
}
void TestStaleHandleNeverAliasesNewQueue() {
EventQueue::KernelEqueue stale = EventQueue::KERNEL_EQUEUE_INVALID;
Check(EventQueue::KernelCreateEqueue(&stale, "stale-handle") == OK,
"create stale queue");
Check(EventQueue::KernelDeleteEqueue(stale) == OK, "delete stale queue");
EventQueue::KernelEqueue replacement = EventQueue::KERNEL_EQUEUE_INVALID;
Check(EventQueue::KernelCreateEqueue(&replacement, "replacement") == OK,
"create replacement queue");
Check(stale != replacement, "queue handles are never recycled");
Check(!EventQueue::KernelPinEqueue(stale), "stale handle does not pin");
Check(EventQueue::KernelAddUserEvent(stale, 11) == KERNEL_ERROR_EBADF,
"stale handle cannot mutate replacement");
Check(EventQueue::KernelAddUserEvent(replacement, 11) == OK,
"replacement handle remains valid");
Check(EventQueue::KernelTriggerUserEvent(stale, 11, nullptr) ==
KERNEL_ERROR_EBADF,
"stale handle cannot trigger replacement");
Check(EventQueue::KernelTriggerUserEvent(replacement, 11, nullptr) == OK,
"replacement event triggers");
Check(EventQueue::KernelDeleteEqueue(replacement) == OK,
"delete replacement queue");
}
void TestConcurrentCloseCallback() {
for (uint32_t iteration = 0; iteration < 64; iteration++) {
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
Check(EventQueue::KernelCreateEqueue(&queue, "callback-race") == OK,
"create callback race queue");
std::atomic_uint32_t delete_count{0};
EventQueue::KernelEqueueEvent callback_event{};
callback_event.event.ident = 9;
callback_event.event.filter = EventQueue::KERNEL_EVFILT_GRAPHICS;
callback_event.filter.data = &delete_count;
callback_event.filter.delete_event_func = CountDeletedEvent;
Check(EventQueue::KernelAddEvent(queue, callback_event) == OK,
"add callback race event");
std::atomic_bool start{false};
std::jthread trigger([&] {
while (!start.load(std::memory_order_acquire)) {
std::this_thread::yield();
}
for (uint32_t i = 0; i < 256; i++) {
CheckConcurrentResult(
EventQueue::KernelTriggerEvent(
queue, 9, EventQueue::KERNEL_EVFILT_GRAPHICS, nullptr),
"callback race trigger result");
}
});
start.store(true, std::memory_order_release);
Check(EventQueue::KernelDeleteEqueue(queue) == OK,
"callback race queue delete");
trigger.join();
Check(delete_count.load(std::memory_order_relaxed) == 1,
"concurrent close invokes callback exactly once");
}
}
void TestConcurrentDelete() {
for (uint32_t iteration = 0; iteration < 64; iteration++) {
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
Check(EventQueue::KernelCreateEqueue(&queue, "concurrent-delete") == OK,
"create concurrent queue");
EventQueue::KernelEqueueEvent event{};
event.event.ident = 7;
event.event.filter = EventQueue::KERNEL_EVFILT_USER;
Check(EventQueue::KernelAddEvent(queue, event) == OK,
"add concurrent event");
std::atomic_bool start{false};
std::jthread mutate([&] {
while (!start.load(std::memory_order_acquire)) {
std::this_thread::yield();
}
for (uint32_t i = 0; i < 64; i++) {
CheckConcurrentResult(EventQueue::KernelAddEvent(queue, event),
"concurrent add result");
CheckConcurrentResult(
EventQueue::KernelTriggerEvent(
queue, 7, EventQueue::KERNEL_EVFILT_USER, nullptr),
"concurrent trigger result");
CheckConcurrentResult(EventQueue::KernelDeleteEvent(
queue, 7, EventQueue::KERNEL_EVFILT_USER),
"concurrent event delete result");
}
});
std::jthread trigger([&] {
while (!start.load(std::memory_order_acquire)) {
std::this_thread::yield();
}
for (uint32_t i = 0; i < 128; i++) {
CheckConcurrentResult(
EventQueue::KernelTriggerEvent(
queue, 7, EventQueue::KERNEL_EVFILT_USER, nullptr),
"parallel trigger result");
}
});
start.store(true, std::memory_order_release);
Check(EventQueue::KernelDeleteEqueue(queue) == OK,
"concurrent queue delete");
mutate.join();
trigger.join();
Check(!EventQueue::KernelPinEqueue(queue),
"concurrent queue removed from registry");
}
}
} // namespace
int main() {
TestDuplicateAddPreservesEventState();
TestCallbackStateOutlivesPort();
TestCallbackOwnsPayload();
TestPinnedClose();
TestStaleHandleNeverAliasesNewQueue();
TestConcurrentCloseCallback();
TestConcurrentDelete();
std::printf("EventQueueLifetimeTests: all cases passed\n");
return 0;
}