core: give owner_stores its whole-process reset verb; shutdown sweeps the registry (#53)
A3a from the core stabilization audit (#49). shutdown() was a ~108-line hand-written cascade -- one clear per store, extended by hand for every capability slice, and silently keeping stale state on the ones where that was forgotten. Three shipped fixes are that exact shape (98cf483/8a06b4a publishes registries, e40492d NEXT_SUB_ID, 7e62119 cookie cache). owner_stores already solved this for the per-plugin verbs; it just never got the whole-process one. OwnerScopedStore gains reset: Box<dyn Fn()>; register() takes it (all 22 call sites updated here -- the signature change lands with its callers); sweep_reset() runs every store's reset in registration order. 23 statics' worth of clears in shutdown() collapse to one call. A store is torn down because it is REGISTERED, not because someone remembered. reset clears CONTENTS ONLY, no engine-op follow-up: remove_by_owner calls event_unsubscribe because the engine must stop delivering to a departing plugin while the server runs on, but shutdown is tearing the host down and the cascade this replaces never issued those calls either. ORDERING: the sweep is placed BEFORE HOST.take(). The old cascade cleared CONCOMMANDS/TOPMENU_ITEMS before the isolate drop (commented as required -- they hold Global<Function>) but the ~20 mux stores AFTER it, even though the muxes are EventMux<v8::Global<v8::Function>> and hold the same thing. That only held because unload_all() runs first and normally empties them per plugin; a subscriber owned by no live plugin was residue dropped after its isolate was gone. Nothing now runs later than before -- only earlier, the safe direction for handle release. Verified mechanically that no clear was lost (that being the bug class this ends): statics touched by shutdown() on origin/main = 58; still named in the new shutdown = 35; moved into reset closures = 23; lost = none. cookies::reset and breadcrumb::clear_plugins confirmed still present. The ~35 statics left hand-cleared are the NOT owner-scoped ones and are A3b; they need a PHASE-TAGGED registry because they fall on both sides of HOST.take(). They must not join owner_stores -- registering a host-global admin/ban cache owner-scoped would wipe shared state on an unrelated plugin's unload (audit 4(d)6). cargo test -p s2script-core: 466 passed. CI=1 make ci: green.
G
Gabriel Hirakawa committed
7ab89e43573c5bba21e71ba5cb0639674954c9f2
Parent: 7234919
Committed by GitHub <noreply@github.com>
on 8/1/2026, 5:55:51 AM