The reader held the pipe for a fixed 100 ms after the main thread flagged
the raise, so a main thread descheduled before sceKernelRaiseException let
the write finish first and the test passed without the system-call path.
The reader now waits until the writer is suspended (NtQueryInformationThread
ThreadSuspendCount), which only the raise does while the write is blocked,
then holds 100 ms.
The handler checked that the write had returned through a flag the writer
sets after fwrite, so a correct delivery between the two failed. It now
checks that the interrupted rip lies in the test's own image.
Two more registrations run the same test with a 200 ms pause before the
raise and with a guest-code spin between fwrite and the flag.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The writer used to publish its block number just before fwrite, so a
delayed raise could land before the call and pass without exercising the
system-call path. The stream is now unbuffered, so each block is one 16 KiB
WriteFile into the 4 KiB pipe. The reader counts the bytes, signals when the
first byte of a block arrives (the writer is then inside WriteFile with more
than a pipe's worth left), and stops reading until the main thread has
started the raise, then 100 ms more. It checks the write has not returned,
and the handler checks the delivery came after it did.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On a busy Windows runner the writer could stay inside NtWriteFile for
over a second, because each 64 KiB block waited on 16 reader wake-ups.
The raise then hit its 1 s retry limit and redirected the writer inside
the call with the stream lock held, so the test deadlocked until ctest
killed it at 30 s.
Each raise now targets one 8 KiB write that blocks for about 100 ms on a
single reader wake-up, and the writer spins in its own code until the
raise is delivered. The watchdog uses a steady_clock deadline, so a hang
aborts at 25 s instead of outliving the ctest timeout.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Since #8512 a raise redirects a target running host code once 50 ms have
passed since the raise began. A target inside a system call that is not a
wait, such as the NtWriteFile of an fwrite that holds its stream lock, can
stay there longer; the redirect then ran the handler with the lock held.
SONIC SUPERSTARS froze at frame 67 again on main this way, with its GC
raise delivered at ntdll!ZwWriteFile+20.
A target stopped right after a syscall instruction in ntdll now restarts
the 50 ms host-code window, so the raise keeps sampling until the call
returns and the target leaves host code, bounded by the 1 s retry limit.
guest_raise_in_system_call holds a writer in NtWriteFile on a slowly
drained pipe; it times out on main and passes with this change.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>