sceSaveDataRegisterEventCallback read g_initializations before it took
any lock. A registration that read a nonzero count could then store its
callback after the final sceSaveDataTerminate had set the count to zero
and cleared the callback, return OK with no delivery thread, and have
the next Initialize3 deliver events to a callback the terminate had
dropped.
Registration now checks the count and stores the callback under
g_slots_mutex, the lock the final terminate holds while it reaches zero
and detaches delivery. It takes the calling lock first, the order the
delivery thread already uses when a callback calls back into SaveData,
so a terminate from inside the callback is still BUSY and the terminate
joins the delivery thread outside both locks.
The new case races registration against the final terminate on two
guest threads for 200 iterations, holding the delivery thread in a
callback on every other one so a registration that passed its check
waits across the whole terminate. Each must end NOT_INITIALIZED, or OK
with the callback cleared, and a backup after Initialize3 must not reach
the raced callback.
sceSaveDataRegisterEventCallback and sceSaveDataUnregisterEventCallback
were parameterless declarations that threw. They now follow shadPS4
#5226: registering before initialization is NOT_INITIALIZED, a null
callback is PARAMETER, a second registration replaces the callback and
its userdata, and unregistering needs no initialization.
Every backup completion that queues an event for
sceSaveDataGetEventResult, type 2 from sceSaveDataBackup and type 1 from
sceSaveDataUmountWithBackup, is queued for polling first and then handed
to the callback registered at that moment. A library thread delivers it:
sceSaveDataInitialize3 starts it with scePthreadCreate, as
libSceUsbStorage starts its monitor, so the callback runs on a guest
thread, and the last sceSaveDataTerminate joins it. Unregistering waits
for a running callback and drops completions not yet delivered, and a
terminate from inside the callback returns BUSY instead of joining its
own thread, as shadPS4 does.
guest_savedata_event_callback covers the errors, both event types, the
delivering thread, unregistration, replacement, a terminate from inside
the callback and an idle terminate followed by a restart.