Merge pull request #3489 from MarcZX100/codex/carry-equeue-level-once-3115

fix(libkernel): report level-triggered equeue events once per wait
This commit is contained in:
Adrià 2026-10-11 13:54:44 +02:00 committed by GitHub
commit eaffdc402f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 136 additions and 39 deletions

View File

@ -120,11 +120,15 @@ int KernelEqueuePrivate::GetTriggeredEvents(KernelEvent* ev, int num) {
}
TriggerExpiredTimers(MonotonicNs());
PollEvents();
return CollectTriggered(ev, num);
}
int KernelEqueuePrivate::CollectTriggered(KernelEvent* ev, int num) {
int ret = 0;
for (auto it = m_events.begin(); it != m_events.end();) {
for (auto it = m_events.begin(); it != m_events.end() && ret < num;) {
auto& e = *it;
bool erase = false;
while (e.triggered) {
while (e.triggered && ret < num) {
ev[ret++] = e.event;
if ((e.event.flags & EV_ONESHOT) != 0) {
erase = true;
@ -137,19 +141,14 @@ int KernelEqueuePrivate::GetTriggeredEvents(KernelEvent* ev, int num) {
e.event.fflags = 0;
e.event.data = 0;
}
if (!e.pendingEvents.empty()) {
e.event = e.pendingEvents.front();
e.pendingEvents.pop_front();
e.triggered = true;
}
if (ret >= num) {
if (e.pendingEvents.empty()) {
break;
}
e.event = e.pendingEvents.front();
e.pendingEvents.pop_front();
e.triggered = true;
}
it = erase ? m_events.erase(it) : std::next(it);
if (ret >= num) {
break;
}
}
return ret;
}
@ -163,34 +162,7 @@ int KernelEqueuePrivate::WaitForEvents(KernelEvent* ev, int num, uint32_t micros
for (;;) {
TriggerExpiredTimers(MonotonicNs());
const bool polling = PollEvents();
int ret = 0;
for (auto it = m_events.begin(); it != m_events.end() && ret < num;) {
auto& e = *it;
bool erase = false;
while (e.triggered) {
ev[ret++] = e.event;
if ((e.event.flags & EV_ONESHOT) != 0) {
erase = true;
break;
}
if (e.filter.resetFunc != nullptr) {
e.filter.resetFunc(&e);
} else if ((e.event.flags & EV_CLEAR) != 0) {
e.triggered = false;
e.event.fflags = 0;
e.event.data = 0;
}
if (!e.pendingEvents.empty()) {
e.event = e.pendingEvents.front();
e.pendingEvents.pop_front();
e.triggered = true;
}
if (ret >= num) {
break;
}
}
it = erase ? m_events.erase(it) : std::next(it);
}
const int ret = CollectTriggered(ev, num);
if (ret != 0) {
return ret;
}

View File

@ -78,6 +78,7 @@ public:
private:
void TriggerExpiredTimers(uint64_t nowNs);
int CollectTriggered(KernelEvent* ev, int num);
bool NextTimerWaitMicros(uint64_t nowNs, uint32_t* out) const;
bool PollEvents();
void WakePollers();

View File

@ -10,8 +10,12 @@ int APS5_VABI sceKernelCreateEqueue(KernelEqueue* eq, const char* name);
int APS5_VABI sceKernelDeleteEqueue(KernelEqueue eq);
int APS5_VABI sceKernelWaitEqueue(KernelEqueue eq, KernelEvent* ev, int num, int* out, const KernelUseconds* timo);
int APS5_VABI sceKernelAddUserEvent(KernelEqueue eq, int id);
int APS5_VABI sceKernelAddUserEventEdge(KernelEqueue eq, int id);
int APS5_VABI sceKernelTriggerUserEvent(KernelEqueue eq, int id, void* udata);
int APS5_VABI sceKernelDeleteUserEvent(KernelEqueue eq, int id);
int APS5_VABI sceKernelAddAmprEvent(KernelEqueue eq, int id, void* udata);
int APS5_VABI sceKernelDeleteAmprEvent(KernelEqueue eq, int id);
int APS5_VABI EqueueTriggerEvent_nid_postfix(KernelEqueue eq, uintptr_t ident, int16_t filter, void* triggerData);
int APS5_VABI sceKernelAddHRTimerEvent(KernelEqueue eq, int id, const KernelTimespec* ts, void* udata);
int APS5_VABI sceKernelAddTimerEvent(KernelEqueue eq, int id, KernelUseconds usec, void* udata);
int APS5_VABI sceKernelDeleteTimerEvent(KernelEqueue eq, int id);
@ -29,6 +33,7 @@ static constexpr int SCE_KERNEL_ERROR_ETIMEDOUT = static_cast<int>(0x8002003c);
static constexpr int EVFILT_TIMER = -7;
static constexpr int EVFILT_USER = -11;
static constexpr int EVFILT_HRTIMER = -15;
static constexpr int EVFILT_AMPR = -25;
static void Require(bool value) { if (!value) std::abort(); }
@ -124,8 +129,126 @@ static void VerifyPeriodicTimer() {
Require(sceKernelDeleteEqueue(eq) == SCE_OK);
}
static void VerifyUserEventReportedOncePerWait() {
KernelEqueue eq = 0;
Require(sceKernelCreateEqueue(&eq, "user") == SCE_OK);
KernelEvent events[4]{};
int count = 0;
const KernelUseconds poll = 0;
int payload = 0;
Require(sceKernelAddUserEvent(eq, 5) == SCE_OK);
Require(sceKernelWaitEqueue(eq, events, 4, &count, &poll) == SCE_KERNEL_ERROR_ETIMEDOUT);
Require(sceKernelTriggerUserEvent(eq, 5, &payload) == SCE_OK);
for (int i = 0; i < 2; ++i) {
count = 0;
Require(sceKernelWaitEqueue(eq, events, 4, &count, &poll) == SCE_OK);
Require(count == 1 && sceKernelGetEventId(&events[0]) == 5);
}
Require(sceKernelDeleteUserEvent(eq, 5) == SCE_OK);
Require(sceKernelAddUserEventEdge(eq, 6) == SCE_OK);
Require(sceKernelTriggerUserEvent(eq, 6, &payload) == SCE_OK);
Require(sceKernelAddUserEvent(eq, 8) == SCE_OK);
Require(sceKernelTriggerUserEvent(eq, 8, &payload) == SCE_OK);
Require(sceKernelWaitEqueue(eq, events, 4, &count, &poll) == SCE_OK);
Require(count == 2);
Require(sceKernelGetEventId(&events[0]) + sceKernelGetEventId(&events[1]) == 14);
Require(sceKernelGetEventId(&events[0]) != sceKernelGetEventId(&events[1]));
Require(sceKernelWaitEqueue(eq, events, 4, &count, &poll) == SCE_OK);
Require(count == 1 && sceKernelGetEventId(&events[0]) == 8);
Require(sceKernelDeleteEqueue(eq) == SCE_OK);
}
static void VerifyBlockingWaitReportsLevelEventOnce() {
KernelEqueue eq = 0;
Require(sceKernelCreateEqueue(&eq, "blocking") == SCE_OK);
KernelEvent events[4]{};
int count = 0;
const KernelUseconds wait = 1000000;
int payload = 0;
Require(sceKernelAddUserEvent(eq, 5) == SCE_OK);
Require(sceKernelTriggerUserEvent(eq, 5, &payload) == SCE_OK);
for (int i = 0; i < 2; ++i) {
count = 0;
Require(sceKernelWaitEqueue(eq, events, 4, &count, &wait) == SCE_OK);
Require(count == 1 && sceKernelGetEventId(&events[0]) == 5);
Require(sceKernelGetEventUserData(&events[0]) == &payload);
}
Require(sceKernelDeleteUserEvent(eq, 5) == SCE_OK);
Require(sceKernelAddUserEvent(eq, 6) == SCE_OK);
std::thread trigger([eq, &payload] {
SleepAtLeast(std::chrono::milliseconds(10));
Require(sceKernelTriggerUserEvent(eq, 6, &payload) == SCE_OK);
});
count = 0;
Require(sceKernelWaitEqueue(eq, events, 4, &count, &wait) == SCE_OK);
trigger.join();
Require(count == 1 && sceKernelGetEventId(&events[0]) == 6);
Require(sceKernelDeleteEqueue(eq) == SCE_OK);
}
static void VerifyEdgeUserEventReportedOnce() {
KernelEqueue eq = 0;
Require(sceKernelCreateEqueue(&eq, "edge") == SCE_OK);
KernelEvent events[4]{};
int count = 0;
const KernelUseconds poll = 0;
const KernelUseconds wait = 1000000;
int payload = 0;
Require(sceKernelAddUserEventEdge(eq, 6) == SCE_OK);
Require(sceKernelTriggerUserEvent(eq, 6, &payload) == SCE_OK);
Require(sceKernelWaitEqueue(eq, events, 4, &count, &wait) == SCE_OK);
Require(count == 1 && sceKernelGetEventId(&events[0]) == 6);
Require(sceKernelWaitEqueue(eq, events, 4, &count, &poll) == SCE_KERNEL_ERROR_ETIMEDOUT);
Require(sceKernelTriggerUserEvent(eq, 6, &payload) == SCE_OK);
Require(sceKernelWaitEqueue(eq, events, 4, &count, &poll) == SCE_OK);
Require(count == 1 && sceKernelGetEventId(&events[0]) == 6);
Require(sceKernelWaitEqueue(eq, events, 4, &count, &poll) == SCE_KERNEL_ERROR_ETIMEDOUT);
Require(sceKernelDeleteEqueue(eq) == SCE_OK);
}
static void VerifyRepeatedAmprCompletionsAreNotLost() {
KernelEqueue eq = 0;
Require(sceKernelCreateEqueue(&eq, "ampr") == SCE_OK);
KernelEvent events[4]{};
int count = 0;
const KernelUseconds poll = 0;
int udata = 0;
auto data = [](std::uintptr_t value) { return reinterpret_cast<void*>(value); };
Require(sceKernelAddAmprEvent(eq, 3, &udata) == SCE_OK);
Require(EqueueTriggerEvent_nid_postfix(eq, 3, EVFILT_AMPR, data(0x11)) == SCE_OK);
Require(EqueueTriggerEvent_nid_postfix(eq, 3, EVFILT_AMPR, data(0x22)) == SCE_OK);
Require(sceKernelWaitEqueue(eq, events, 4, &count, &poll) == SCE_OK);
Require(count == 2);
Require(sceKernelGetEventFilter(&events[0]) == EVFILT_AMPR && sceKernelGetEventFilter(&events[1]) == EVFILT_AMPR);
Require(sceKernelGetEventId(&events[0]) == 3 && sceKernelGetEventId(&events[1]) == 3);
Require(sceKernelGetEventData(&events[0]) == 0x11 && sceKernelGetEventData(&events[1]) == 0x22);
Require(sceKernelGetEventUserData(&events[0]) == &udata && sceKernelGetEventUserData(&events[1]) == &udata);
Require(sceKernelWaitEqueue(eq, events, 4, &count, &poll) == SCE_KERNEL_ERROR_ETIMEDOUT);
Require(EqueueTriggerEvent_nid_postfix(eq, 3, EVFILT_AMPR, data(0x33)) == SCE_OK);
Require(EqueueTriggerEvent_nid_postfix(eq, 3, EVFILT_AMPR, data(0x44)) == SCE_OK);
Require(EqueueTriggerEvent_nid_postfix(eq, 3, EVFILT_AMPR, data(0x55)) == SCE_OK);
for (const intptr_t expected : {0x33, 0x44, 0x55}) {
Require(sceKernelWaitEqueue(eq, events, 1, &count, &poll) == SCE_OK);
Require(count == 1 && sceKernelGetEventData(&events[0]) == expected);
}
Require(sceKernelWaitEqueue(eq, events, 4, &count, &poll) == SCE_KERNEL_ERROR_ETIMEDOUT);
Require(sceKernelDeleteAmprEvent(eq, 3) == SCE_OK);
Require(sceKernelDeleteEqueue(eq) == SCE_OK);
}
int main() {
VerifyPeriodicTimer();
VerifyUserEventReportedOncePerWait();
VerifyBlockingWaitReportsLevelEventOnce();
VerifyEdgeUserEventReportedOnce();
VerifyRepeatedAmprCompletionsAreNotLost();
KernelEqueue eq = 0;
Require(sceKernelCreateEqueue(&eq, "events") == SCE_OK);

View File

@ -288,6 +288,7 @@ Throughout the project, every function at every stage either **does exactly what
- [sceCoredumpAttachUserMemoryFile](../../core/libs/prx/libkernel/System/src/Coredump.cpp) (libkernel) - unknown signature
- [sceCoredumpAttachMemoryRegion](../../core/libs/prx/libkernel/System/src/Coredump.cpp) (libkernel) - unknown signature
- [sceKernelAddTimerEvent](../../core/libs/prx/libkernel/Equeue/Equeue.cpp) (libkernel) - `usec` is taken as the period in microseconds; re-adding an existing timer keeps its pending expiration count and restarts the period from the time of the call, where FreeBSD 12+ `filt_timertouch` clears pending expirations on `EV_ADD`; `usec == 0` fires on every wait. None of this is confirmed on a PS5 title
- [sceKernelWaitEqueue](../../core/libs/prx/libkernel/Equeue/Equeue.cpp) (libkernel) - a level-triggered user event (`sceKernelAddUserEvent`) is reported at most once per call, as in FreeBSD `kqueue_scan` (`sys/kern/kern_event.c`), which re-queues a reported knote without `EV_CLEAR` or `EV_DISPATCH` behind the scan marker, and kevent(2), where several triggers do not produce several kevents. Not measured on a console. Events are scanned in list order and the list is not rotated, so when `num` is smaller than the number of ready level events the later ones can starve, where FreeBSD moves a reported level knote to the tail. Several completions of one AMPR or VideoOut event within one call are returned as separate entries, where FreeBSD would aggregate them; the Orbis behaviour is unconfirmed
- [sceRtcFormatRFC2822, sceRtcFormatRFC2822LocalTime, sceRtcFormatRFC3339LocalTime](../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - only shadPS4 (PS4) has bodies. A null tick returns `INVALID_POINTER` like the existing `sceRtcFormatRFC3339`, where shadPS4 formats the current time instead; offsets outside ±1439 minutes return `INVALID_VALUE` like `sceRtcFormatRFC3339`; the local-time variants use the host's offset at the given instant (shadPS4 uses `sceKernelGettimezone`, the current offset)
- [snwprintf_s](../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - follows C11 K.3.9.1.3 with `RSIZE_MAX` assumed to be `SIZE_MAX >> 1`. A runtime-constraint violation only returns a negative value: `set_constraint_handler_s` is not exported and the console's default handler is unknown. An invalid multibyte `%s` argument is copied byte by byte instead of being reported as an encoding error, as in `vswprintf`
- [vsnprintf_s and vsscanf_s](../../core/libs/prx/libc/src/Formatting.cpp) (libc) - `vsnprintf_s` forwards to `vsnprintf` semantics: the C11 K.3.7.1.5 runtime constraints (null buffer, `size == 0`, `%n`, null `%s`) are not checked, so `size == 0` returns the would-be length instead of a negative value, and no constraint handler is invoked. `vsscanf_s` returns the count of items converted before an undersized `%s`/`%c`/`%[` destination (0 when it is the first), not EOF. Neither is confirmed on a PS5 title