Skip to content

Wait for IOP threads in WaitSema outside a thread context - #266

Open
drakolordx7 wants to merge 1 commit into
ran-j:mainfrom
drakolordx7:fix/iop-rpc-semaphore-wait
Open

drakolordx7 wants to merge 1 commit into
ran-j:mainfrom
drakolordx7:fix/iop-rpc-semaphore-wait

Conversation

@drakolordx7

Copy link
Copy Markdown

RPC server functions and module start routines run synchronously (callFunction), outside any IOP thread. WaitSema there found the count at 0, had no thread to block, and returned at once without taking the semaphore. The caller then ran its critical section while the IOP thread holding the semaphore was preempted inside its own.

Seen with a game's IRX (PFILE_R.IRX in Killzone, SCUS-97402): the read-request RPC appends to a streaming queue under a semaphore, and the streaming thread removes finished requests under the same one. An append landing between the streaming thread's next = cur->next and if (next == 0) tail = 0 was dropped, so no completion SIF command was sent and the EE waited forever (intermittent boot/loading hang). In the Killzone port's runtime, 5 of 12 headless boots hung before the equivalent change and 0 of 12 after.

Fix: waitSemaphoreOutsideThread runs the IOP scheduler (threads, due interrupts, callbacks, timers) until the semaphore count is above 0, then WaitSema takes it as usual. While such a wait is active, SignalSema keeps the count for the waiter and ends the signalling thread's slice. The wait is skipped from interrupt handlers, guest callbacks and while the scheduler is already running, and gives up after 1 s of IOP time with a warning (the old behaviour). The loop body of runCycles moves into scheduleStep(), shared with the wait.

Tests: new ps2_iop_emulator_tests case (a module start routine waits on an empty semaphore that a thread signals; fails without the change) plus a no-signaller timeout case. ps2xIOP ctest 4/4, gs_cache ctest 45/45 and ps2x_tests unchanged from main (built with -DPS2X_BUILD_TEST=ON -DPS2X_IOP_BUILD_TESTS=ON).

Behaviour change: a WaitSema outside a thread with no signaller now advances up to 1 s of IOP time before returning.

Made by drakolord and assisted with Claude Code.

RPC server functions and module start routines run synchronously
(IopRpcBridge::handleRpc / loadImage -> callFunction), outside any IOP
thread. WaitSema there found the count at 0, had no thread to block, and
returned at once without taking the semaphore. The caller then ran its
critical section while the IOP thread that holds the semaphore was
preempted inside its own, because IOP threads are cut off wherever their
slice ends.

Found in Killzone's PFILE_R.IRX: the read-request RPC appends to a
streaming queue under a semaphore, and the streaming thread removes
finished requests under the same one. An append that landed between the
streaming thread's `next = cur->next` and `if (next == 0) tail = 0` was
dropped, the completion SIF command was never sent, and the EE waited
forever (intermittent boot/loading hang). With the thread states traced
at each contended wait, the holder was preempted inside the critical
section in every hung boot. 5 of 12 headless boots hung before this
change, 0 of 12 after.

Do what the real IOP does: the waiting code blocks and the holder runs
until it signals. waitSemaphoreOutsideThread runs the IOP scheduler
(threads, due interrupts, callbacks, timers) until the semaphore count is
above 0, then WaitSema takes it as usual. While such a wait is active,
SignalSema keeps the count for the waiter and ends the signalling
thread's slice, so another thread cannot take the semaphore back first.
The wait is skipped from interrupt handlers and guest callbacks and
while the scheduler is already running, and it gives up after one second
of IOP time with a warning (the previous behaviour).

The body of runCycles' loop moves into scheduleStep(), shared with the
wait.

The new test starts a module whose start routine calls WaitSema on an
empty semaphore that a thread signals after writing a marker; it fails
without the change. A second case with no signaller checks the timeout.

Made by drakolord and assisted with Claude Code.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant