2 Commits

Author SHA1 Message Date
chrg6
d8c046acc4 Destroy a movie decoder's system events and free their table entries when the decoder goes.
The review of the system-event change (Ev-1) found the table still filling. Entries in host memory now count as live
for good in the full-table scan, but their objects are freed: ov_movie2.c ov_dtor freed the Video with in_ev and out_ev
still bound, so each decoder left two dead entries that nothing reclaimed (AUDIT F-1-08 measured about two entries per
reload of a save, which E-4's verifier tied to the decoder pair), and their four kernel handles stayed open. A long
session would end with "kern_bind_system_event: table full" once no dead guest entry was left to take.

kernel.c gains kern_unbind_system_event(c, ev). On a guest thread it calls the SDK's nn::os::DestroySystemEvent, which
zeroes the state byte and closes both handles, and then removes the entry under g_se_lock (the last entry takes its
place). The destroy comes first, so a signal still in flight finds the event destroyed or its handle no longer naming it
and skips it, rather than finding no entry and binding again on a host thread. Off a guest thread, or when the SDK lacks
the function (logged once), only the entry goes. ov_dtor calls it for each bound event after finalize has joined the
decoder and convert threads, then frees the Video. net_offline.c frees its HostTask with a bound event in the same way;
that file is outside this change and is handed to the coordinator.

Not reproduced before the change in a game run. tests/system_event_check.c fills the table with live guest events and
the movie's two host-memory events, unbinds one: it leaves the table, its handles are closed and its state byte is 0,
the other keeps its entry, and a second unbind changes nothing; with the table full again and the last host entry
unbound, a new guest bind succeeds where it would have ended with "table full". The check passes in both modes,
movie_planes_check passes, and the compile check of every runtime file (host.h changed) passes, adding no warnings in
kernel.c or ov_movie2.c with -Wall -Wextra.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 18:41:46 -05:00
chrg6
465ffe67bb Check a system event's kept handle under the kernel lock before signalling it, and keep host-memory events in a full table.
kern_signal_system_event read the table entry and the state byte without the kernel lock, then kern_event_signal took
the lock and ended the process when the kept writable handle was no longer an event. A host thread (the vsync thread
signals every bound vsync event 60 times a second) racing the guest's DestroySystemEvent, which zeroes the byte and
then closes both handles under the kernel lock, hit that fatal; a probe built on kernel.c reached it after 437
destroy and bind rounds. A stale entry whose byte reads nonzero again ended the process at once, and since h_new hands
a closed number to the next new object, a stale entry could also signal or clear an unrelated event.

Each Event now gets a serial number, and kern_bind_system_event records the serial of the Event its writable handle
names. The new system_event_set reads the entry again under the kernel lock and sets or clears the event only while
the handle still names that same Event; a stale entry is skipped and logged once. kern_clear_system_event goes through
it as well. The lock order is G before g_se_lock; the binder never holds both.

The full-table path of kern_bind_system_event took any entry whose object mem_readable did not know, which includes
the live events host services keep in their own memory (ov_movie2.c Video.in_ev/out_ev, net_offline.c
HostTask.event). The service's next signal from its host thread then found no entry and tried to bind there, which is
fatal. Entries now record whether the object lies in guest memory, and only guest entries that were destroyed or
unmapped give their slot.

tests/system_event_check.py builds kernel.c with stubs and checks a destroyed event with its byte nonzero again, a
closed number taken by an unrelated event, two seconds of a host thread signalling and clearing against a guest
thread destroying and binding again, a full table with the movie's two host-memory events, and the "table full"
fatal with every entry live. Against the previous kernel.c it ends with "kern_event_signal: 0x1001 is not a writable
event handle". sync_wake_check and thread_end_check pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 18:41:46 -05:00