The first handoff loop waited for the worker to set started and then slept 50 ms, but started is set before sceKernelWaitSema queues the worker, so a delayed worker could still be unqueued at the signal. The loop now waits with WaitForQueue(sem, 1), as the other cases do, and Waiter drops the started flag.
Semaphore.cpp and the handoff test declared scePthreadSelf, scePthreadGetprio, scePthreadCreate, scePthreadJoin and scePthreadSetprio locally. libkernel's Api.hpp already declares them with the same signatures, so both include it instead. The test keeps the internal semaphore header for the fixture state.
sceKernelDeleteSema sets the semaphore's deleted flag and waits for its
waiters to leave before freeing it. A sceKernelWaitSema that arrived in
that window with enough tokens took them on the fast path and returned
SCE_OK without counting as a waiter, where main returns
SCE_KERNEL_ERROR_EACCES. The deleted check now comes before the fast
path, as main orders it.
A semaphore created with attribute 0x02 inserts a waiter before the first queued waiter with a lower priority, so the signal hands tokens to higher-priority threads first and to equal ones in arrival order. guest_semaphore_handoff also checks two waiters: the skip of one that needs more than is left, and the order for both attributes.
sceKernelSignalSema only added to the count and woke the waiters, each
of which then had to take the count itself. A sceKernelPollSema or
sceKernelWaitSema that ran first took the tokens instead, so a thread
that had queued for them could time out or keep waiting. #4899 fixes
the same steal for event flags.
sceKernelSignalSema now hands its tokens to the queued waiters in the
order they queued, passing over one that needs more than is left, and
only what remains stays on the count. sceKernelCancelSema reports the
queued waiters and cancels them. shadPS4's semaphore serves its wait
list the same way when it is signalled.