You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Instance graphs are never reclaimed: one task graph per unpublished spawn, one SharedError per panic — sustained panics exhaust the registry's entries table #34
Three merged strands converge on one missing design, each recorded in a PR body and never filed. Result slots now recycle (#16 / PR #31); the graphs whose addresses ride in those slots still accumulate until family teardown, and the registry's entries table is the first hard wall.
The evidence, from the sessions that hit it
PR feat(parallel): give result slots back once their handle has taken the answer #31 (slot recycling), reproduced on unmodified main: "sustained panics still exhaust the substrate's 256-entry registry (entries table full) — one persisted SharedError graph per panic." A panic-storm workload dies on ArenaException::registryTableFull long before memory is a problem.
Two things stop the obvious "drop the graph when the slot record is released":
Result aliasing. A task's result may reference members of the task's own graph (a task returning $this, or a sub-object it persisted). dropInstance() decrements member shares and removes zero-share objects from the registry — arena memory survives (nothing is freed), but a process that attaches the address afterwards gets "not registered". So the drop moment must be provably after every reader is done, which is exactly the capture-then-release discipline feat(parallel): give result slots back once their handle has taken the answer #31 built for slots — the question is whether the slot's release moment is also late enough for the graph, given adopters in other processes.
Task graphs second, and possibly opt-in (spawnParallel(..., reclaim: true) or a directory policy): the runtime cannot see whether a result aliases its task's graph, so either it proves non-aliasing (result is scalar/string), or the caller declares it.
A panic storm (say 1000 sequential panics on a default-sized registry) completes without registryTableFull, each waiter still reading its own class/message/trace.
A long spawn-heavy run of scalar-returning unpublished tasks plateaus in entries as it already does in slots.
A task whose result references its own graph is not silently broken by reclamation — asserted by a test that awaits, then attaches the result address from another process.
Three merged strands converge on one missing design, each recorded in a PR body and never filed. Result slots now recycle (#16 / PR #31); the graphs whose addresses ride in those slots still accumulate until family teardown, and the registry's entries table is the first hard wall.
The evidence, from the sessions that hit it
entriestable full) — one persistedSharedErrorgraph per panic." A panic-storm workload dies onArenaException::registryTableFulllong before memory is a problem.spawnParallel()of an unpublished task persists an instance graph, every panic persists aSharedErrorinstance graph, and each consumes an entries-table slot plus arena memory until teardown. Correct for the arena's leak-until-teardown model; unbounded for long-running spawn-heavy or panic-heavy processes.dropInstance($address)is the substrate hook for reclaiming a slot's error object with it" — ready when slots become reclaimable. They now are, and nothing calls it:git grep dropInstancein this repo'ssrc/comes back empty.Why it is a design, not a call
Two things stop the obvious "drop the graph when the slot record is released":
$this, or a sub-object it persisted).dropInstance()decrements member shares and removes zero-share objects from the registry — arena memory survives (nothing is freed), but a process that attaches the address afterwards gets "not registered". So the drop moment must be provably after every reader is done, which is exactly the capture-then-release discipline feat(parallel): give result slots back once their handle has taken the answer #31 built for slots — the question is whether the slot's release moment is also late enough for the graph, given adopters in other processes.Registry::assertRegistryRoom()refuses any insert oncenNumUsed == nTableSize, tombstones or not — so today, dropping entries returns registry hygiene but no capacity. The substrate half is filed as The registry room guard refuses inserts a tombstoned table could absorb, so dropping entries never returns capacity in arena mode php-shared-data-extension#28 (room-guard compact-awareness, mirroring the engine's in-place-rehash branch); this issue is blocked on it for the capacity payoff, not for the design.Suggested shape
SharedErrorgraphs first: their aliasing story is simple (the waiter copies class/message/trace into the exception atsettle(), per feat(parallel): give result slots back once their handle has taken the answer #31 — "capture, then release"), so the slot-release moment is already safe for them.spawnParallel(..., reclaim: true)or a directory policy): the runtime cannot see whether a result aliases its task's graph, so either it proves non-aliasing (result is scalar/string), or the caller declares it.entriesseries like feat(parallel): give result slots back once their handle has taken the answer #31'sslotsseries, with--leak-entriesself-test — a detector nobody has seen fail is a detector nobody knows works.Acceptance criteria
registryTableFull, each waiter still reading its own class/message/trace.