Follow-up to #885 / #891.
PR #891 fixed the worker-exit lifetime of managed threadvars (AnsiString / TArray<>) via Goccia.ThreadCleanupRegistry, but deliberately scoped out object-reference threadvars, because FPC never auto-finalizes object refs anywhere and freeing them at thread exit risks double-free / use-after-free that needs per-site analysis.
Audit the object-reference threadvars surfaced during #885 and route any genuine leakers (or document why each is already safe):
Goccia.Builtins.GlobalSymbol — GSharedSymbolRegistry: TOrderedStringMap<...> (ref-counted; appears safe — confirm)
Goccia.Values.SymbolValue — GSymbolRegistry: THashMap<...>
Goccia.ObjectModel — GPublishedGetterHosts: TObjectList<...>
Goccia.Compiler.Statements — TList working-state threadvars
- any others found by a fresh sweep of
source/units + source/shared
Acceptance criteria
- Each object-reference threadvar is either freed on the correct teardown path(s) or has a comment explaining why it is already safe (owned/freed elsewhere).
- No double-free / use-after-free introduced (verify under the worker spawn→exit path).
Also in scope: manually-pinned per-thread prototype hosts (from #891 review)
Several value types keep a threadvar FPrototypeMethodHost: TGoccia<Type>Value that InitializePrototype assigns and manually GC-pins (PinObject) so the cached member definitions stay valid across realms on the thread — e.g. Goccia.Values.ArrayValue, Goccia.Values.BigIntValue, Goccia.Values.BooleanObjectValue, Goccia.Builtins.Performance (FPrototypeMethodHost), and similar. These are object-reference threadvars (not managed types), so #891 left them untouched. Per [docs/garbage-collector.md], built-in prototypes are normally pinned via realm slots and released on realm Destroy; these manual pins are a separate, per-thread mechanism.
Determine whether the manually-pinned host is reclaimed at worker GC shutdown or leaks per worker, and either move it to a realm-slot / true process singleton or unpin+nil it on thread teardown. Sweep for every FPrototypeMethodHost (and equivalent manually-pinned host threadvars).
Follow-up to #885 / #891.
PR #891 fixed the worker-exit lifetime of managed threadvars (AnsiString /
TArray<>) viaGoccia.ThreadCleanupRegistry, but deliberately scoped out object-reference threadvars, because FPC never auto-finalizes object refs anywhere and freeing them at thread exit risks double-free / use-after-free that needs per-site analysis.Audit the object-reference threadvars surfaced during #885 and route any genuine leakers (or document why each is already safe):
Goccia.Builtins.GlobalSymbol—GSharedSymbolRegistry: TOrderedStringMap<...>(ref-counted; appears safe — confirm)Goccia.Values.SymbolValue—GSymbolRegistry: THashMap<...>Goccia.ObjectModel—GPublishedGetterHosts: TObjectList<...>Goccia.Compiler.Statements—TListworking-state threadvarssource/units+source/sharedAcceptance criteria
Also in scope: manually-pinned per-thread prototype hosts (from #891 review)
Several value types keep a
threadvar FPrototypeMethodHost: TGoccia<Type>ValuethatInitializePrototypeassigns and manually GC-pins (PinObject) so the cached member definitions stay valid across realms on the thread — e.g.Goccia.Values.ArrayValue,Goccia.Values.BigIntValue,Goccia.Values.BooleanObjectValue,Goccia.Builtins.Performance(FPrototypeMethodHost), and similar. These are object-reference threadvars (not managed types), so #891 left them untouched. Per [docs/garbage-collector.md], built-in prototypes are normally pinned via realm slots and released on realmDestroy; these manual pins are a separate, per-thread mechanism.Determine whether the manually-pinned host is reclaimed at worker GC shutdown or leaks per worker, and either move it to a realm-slot / true process singleton or unpin+nil it on thread teardown. Sweep for every
FPrototypeMethodHost(and equivalent manually-pinned host threadvars).