Summary
The recycled arena-block pool introduced for #7438 is a fixed 64 MiB per live thread cache. Every normal general/old/large-Eden block-deallocation path offers blocks to it before actually deallocating them.
That conflicts with two current contracts:
js_gc_memory_pressure(level >= 2) documents that critical pressure runs a full cycle so idle blocks are handed back to the OS.
PERRY_GC_HEAP_LIMIT / the device-derived budget scales collector thresholds for small devices and containers.
The pool has no drain API, is not consulted by the pressure entry point, and does not scale with the heap budget. On Unix it uses MADV_FREE, which leaves mappings cached and may leave pages resident until the OS reclaims them; on non-Unix platforms there is no equivalent discard in this path. A live thread can therefore retain up to 64 MiB of pooled mappings after a critical-pressure full GC, and N live agent threads can retain up to N × 64 MiB independently.
Evidence
The last point also makes current deallocated_bytes / “freed back to OS” diagnostics overstate physical release after #7438.
Proposed direction
- Add an explicit current-thread pool drain/bypass for critical memory pressure and allocation-failure recovery.
- Scale the normal pool cap from the device heap budget, and decide whether a process-wide cap is needed for
perry/thread agents.
- Separate “removed from arena accounting”, “pooled/discarded”, and “actually deallocated/unmapped” telemetry.
Acceptance criteria
- After a synchronous level-2 pressure call, the current thread's pool is empty (or bounded by a documented small emergency reserve) and its blocks have gone through real deallocation/unmapping where the platform supports it.
- Deferred critical pressure drains the pool when the owed full collection completes.
- A small
PERRY_GC_HEAP_LIMIT cannot coexist with a larger fixed per-thread pool allowance.
- Tests seed the pool, exercise synchronous and deferred critical pressure, and assert pool bytes plus honest release telemetry.
- Add a multi-thread/process-level retention test or telemetry assertion preventing N × 64 MiB silent growth across live agents.
Summary
The recycled arena-block pool introduced for #7438 is a fixed 64 MiB per live thread cache. Every normal general/old/large-Eden block-deallocation path offers blocks to it before actually deallocating them.
That conflicts with two current contracts:
js_gc_memory_pressure(level >= 2)documents that critical pressure runs a full cycle so idle blocks are handed back to the OS.PERRY_GC_HEAP_LIMIT/ the device-derived budget scales collector thresholds for small devices and containers.The pool has no drain API, is not consulted by the pressure entry point, and does not scale with the heap budget. On Unix it uses
MADV_FREE, which leaves mappings cached and may leave pages resident until the OS reclaims them; on non-Unix platforms there is no equivalent discard in this path. A live thread can therefore retain up to 64 MiB of pooled mappings after a critical-pressure full GC, and N live agent threads can retain up to N × 64 MiB independently.Evidence
block_pool_putretained itThe last point also makes current
deallocated_bytes/ “freed back to OS” diagnostics overstate physical release after #7438.Proposed direction
perry/threadagents.Acceptance criteria
PERRY_GC_HEAP_LIMITcannot coexist with a larger fixed per-thread pool allowance.