476. The handler ran

2026-09-09 - directed, continuing 475

Built the pending-signal slot, in the place D651 measured rather than the place D650 guessed.

Three pieces

A slot per thread, lock-free to read. SignalSlot { pending, parked } behind an Arc, cached in a thread-local by become_thread. The Arc is the whole trick: the pending flag is read inside a condvar predicate under the queue lock, and reading it from the thread table there would invert the lock order against sceKernelRaiseException and deadlock.

A loop in the wait. The predicate becomes tokens == 0 && !signal_pending(). A thread woken for a signal has not had its word written, so it runs the handler and goes back to sleep - returning Woken would hand the guest a satisfied wait whose condition is still false. The lock is dropped before the handler runs, because the handler is guest code and may wait on the same queue.

Hooks, not a call upward. sync already takes its word-read as a closure so it can be tested against a word it owns; running a guest handler is further outside that boundary, so it is inverted down as a SignalDelivery { pending, deliver } pair (D652).

What it did

before:  ran to the time limit          (silent for 19.7s of 20)
after:   the title's own modules+0x170021b
         read of 0xf8    rdi=0x1e  rsi=0x0  r14=0x480001700210

The handler ran. The signal number arrived in rdi, rsp sits in the reentrant stack call_guest reserves, and the handler executed inc dword [rip+...], cmp edi, 0x1e, fell through the jne, and then mov rax, [rsi+0xf8] - where rsi is the exception context and orbistoun passes null.

The fault is the design working. The context layout is unpublished, so a plausible block would have carried the guest past this instruction on invented fields and buried the question. Null puts it exactly where the missing knowledge is. Filed as REQ-20260909T1720Z-4e17.

Two runs agree on the fault site.

Surprises

Next