2 Commits

Author SHA1 Message Date
chrg6
e9becc12f3 Free closed IPC domain objects and give their ids out again, and keep a domain alive while its objects are.
A domain's objects[32] only grew: CloseVirtualHandle returned without freeing anything, so a client that opens and
closes sub-objects (the applet proxy getters, about 17 at boot) would get IPC_R_UNKNOWN_COMMAND for its 33rd, which the
SDK aborts on. The handlers took the new object's id from nobjects, the table had no lock (two threads could race on
nobjects), and a sub-object reached through its own handle (CopyFromCurrentDomain) kept a root pointer that the
domain's last CloseHandle had freed.

The domain table now has a lock in ipc.c (g_domains), taken only for sessions in a domain and never across a handler
or a close callback. ipc_domain_add gives out the first free slot and returns the id the handlers write back; the
table holds one reference on each object and each object one on its domain (held_root), counted in the domain's subs.
CloseVirtualHandle clears the slot and drops the table's reference; it never closes the domain's own id. A request
takes a reference on its target object under the lock for its length, and a cleared id is answered with
IPC_R_BAD_HANDLE instead of being dereferenced; CopyFromCurrentDomain answers the same. When a domain's remaining
references are all its objects', its client has closed it: the table lets the objects go, each object nothing else
holds is freed and lets go of the domain, and the domain is freed with the last one. Domain objects now count in
ipc_sessions_live from the moment they are added, since their last reference counts them out.

Not reproduced before the change: no log here has TRACE=ipc, so the getters per return to the title are not counted.
ipc_session_check gains a domain section: appletOE converted to a domain, 100 rounds of a getter and its
CloseVirtualHandle each get id 3 with the table at 3 slots, a closed id gets a bad-handle result, the domain's own id
survives a CloseVirtualHandle, a proxy with a handle of its own keeps the closed domain until that handle closes and
then both are freed, and closing a domain frees its proxy and five getters. The compile check of every runtime file
passes with no new warnings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 18:41:46 -05:00
chrg6
63228e7ca8 Hold a reference on an IPC session for the length of a request, and copy the transfer memory under the kernel lock.
SendSyncRequest looked the session up with kern_handle_get, which holds the kernel lock only for the lookup, and then
dispatched to the handler with no reference. A CloseHandle of the session's last handle on another thread meanwhile
reached ipc_handle_closed, which ran the close callback and freed the session while the handler still used it; for
the audio renderer, renderer_close joins the render thread and frees the renderer a RequestUpdate is reading.
OpenAudioRenderer read the transfer memory the same way, and a CloseHandle frees that object at once.

kernel.c gains kern_handle_hold, which runs a hold callback under the kernel lock after the lookup (handle_close
clears the slot under that lock before it calls ipc_handle_closed, so a close either comes first and the lookup
fails, or comes after and finds the extra reference), and kern_handle_copy, which copies an object under the lock.
ipc_svc takes the session with kern_handle_hold and drops the request's reference through ipc_handle_closed after the
answer, so the close callback runs on whichever thread drops the last reference; the request thread holds no service
lock by then. audren.c copies the transfer memory into a local IpcTransferMemory. Domain sub-objects are not freed
yet (IpcSession.objects), so their lookups are left for the domain object change.

Not reproduced before the change in a game run (no log shows a renderer closed during play). tests/ipc_session_check.py
builds ipc.c with kernel.c linked: a request whose handler blocks while another thread closes the session's only handle
must leave the close callback until the handler returns, and a closed transfer memory handle must copy nothing.
Against the previous ipc.c it fails with the close callback run while the handler was blocked. system_event_check,
sync_wake_check and the compile check of every runtime file pass.

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