pub fn run_close_finalizers(state: &mut LuaState)Expand description
Run every still-pending __gc finalizer at state close.
Mirrors C-Lua’s luaC_freeallobjects (lgc.c), which calls
separatetobefnz(g, 1) to move all remaining finalizable objects
(regardless of reachability) into the to-be-finalized list, then
callallpendingfinalizers to invoke each __gc before the objects are
freed. At lua_close, objects the program kept alive to program end —
e.g. a table held by a global — still have their finalizer run; that is
what emits messages like >>> closing state <<< from gc.lua.
Drains both queues until stable (issue #260 codex round 2, finding 3):
the loop runs while either pending_finalizers or to_be_finalized is
non-empty, so (a) leftovers parked in to_be_finalized by an earlier
bounded incremental pass (run_some_pending_finalizers_inner caps at
GC_FIN_MAX per step) still run at close even when pending is empty,
and (b) a finalizer that registers a new finalizable object during the
first pass has that object’s __gc run by a later pass. The seen set
spans all passes, so each object identity is finalized at most once per
close — a finalizer that re-registers its own object cannot loop the
drain. seen is seeded with the identities already parked in
to_be_finalized at entry — those objects are finalized by the first
run_pending_finalizers call without ever passing through pending,
and without the seed a self-re-registering queue-only finalizer would
run twice (codex round-3 finding). (C refuses close-time registrations
outright via GCSTPCLS; running fresh registrations exactly once is
the deliberate divergence here, adjudicated on the #260 review.)