Bucketed page pool, memory reclaim + threat watermarks - #224
Open
TurkeyMan wants to merge 5 commits into
Open
Conversation
TurkeyMan
force-pushed
the
ow/page-pool
branch
3 times, most recently
from
August 14, 2026 16:30
8c712c1 to
6255f40
Compare
…n-thread notion Critical's Windows backing becomes SRWLOCK (zero-init by design) with the same owner/count reentrancy layer the Posix owner-spin branch uses, so zero-init is the valid initial state on every platform and a __gshared Critical needs no init() hook. Windows waiters block instead of spinning. thread gains current_thread_id/set_main_thread/is_main_thread; urt's own main() stamps the main thread during bootstrap, so is_main_thread() is trustworthy in every consumer with no setup call.
Subsystems holding reclaimable cache (freelists, caches) register a handler with a willingness value reflecting rebuild cost; on allocation failure the allocator walks handlers most-willing-first and retries once anything was freed. Handlers registered !thread_safe only run from the main thread; reentry from a handler that allocates returns 0 instead of recursing. Both delegate and function-pointer registration are supported since module-level trim handlers have no context to close over.
Fixed-size pages in a few size categories, carved from slabs. Allocation prefers the most-occupied slab (per-slab freelists, not a master list) so lightly-used slabs drain to fully-idle and trim can return them to the heap; the pool registers itself as a high-willingness reclaimer. Requests beyond the largest category get a heap-backed page with the same header so page_free is uniform; in-range requests never fall through to the heap, keeping the per-category caps meaningful under flood. Pop/push run under a Critical; slab allocation and release happen outside it.
TLSF has no internal locking and vendor C enters through the extern(C) malloc overrides from other tasks. All pool access now runs under a Critical; log calls stay outside it since they format strings. query_pool_stats walks the free-list so it takes the lock too.
Proactive counterpart to the reclaim walk: allocated bytes are accounted at the alloc/free/realloc/expand choke points (one atomic + one compare on the fast path), and when usage crosses a registered high watermark the handler fires ONCE, re-arming only after usage falls below the low watermark -- a pressure episode costs one callback, not one per alloc. Handlers run inside the allocator on whatever thread allocated, so their contract is post-an-event-and-return; the scheduled job does the real work (e.g. flushing dirty cache so its reclaimer becomes cheap). Usage counts requested bytes, excluding allocator headers and fragmentation: watermarks are a proxy for footprint, not a measure.
TurkeyMan
force-pushed
the
ow/page-pool
branch
from
August 15, 2026 05:46
6255f40 to
e58f831
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Memory-pressure toolkit for shared-heap targets, in three mechanisms plus groundwork:
Page pool (
urt.mem.pagepool): fixed-size pages in configurable size categories, carved from slabs with per-slab freelists. Allocation always pops from the most-occupied slab, so lightly-used slabs drain to fully-idle and trim can return them to the heap whole. Hard per-category caps (failure = caller drops + counter, never heap growth), low-water preallocation, stats with size histogram. Oversize requests get a heap-backed page behind the same header sopage_freeis uniform; in-range requests never fall through to the heap, keeping the caps meaningful under flood. Pop/push run under a Critical; slab alloc/release always happens outside it.Reclaimer registry (
urt.mem.reclaim): subsystems holding reclaimable cache register a handler with a willingness value (reflecting rebuild cost). On allocation failure the allocator walks handlers most-willing-first and retries;!thread_safehandlers only run from the main thread; reentry from a handler that allocates returns 0 instead of recursing. Handlers are stored as a decomposed function/context pair (delegates recomposed at the call), so function-pointer and delegate registration share one entry shape. The page pool registers its trim at high willingness.Memory-threat watermarks (same module): allocated bytes are accounted at the alloc/free/realloc/expand choke points (one atomic + one compare on the fast path); crossing a registered high watermark fires the handler once, re-arming only below the low watermark, so a pressure episode costs one callback rather than one per allocation. Handler contract is post-an-event-and-return. This is the proactive half: it buys time to convert expensive-to-reclaim state to cheap (e.g. flushing dirty history so its blocks become droppable) before the reclaim walk needs it.
Groundwork:
Criticalis now zero-init valid on every platform -- the Windows backing becomes SRWLOCK (zero-init by design, waiters block) with the same owner/count reentrancy layer the Posix owner-spin branch uses -- so__gshared Criticalneeds no init hook;urt.threadgainscurrent_thread_id/set_main_thread/is_main_thread, with urt's ownmain()stamping the main thread during bootstrap; and the Bouffalo TLSF heap is now locked (it had no locking while vendor C enters via the extern(C) malloc overrides) -- a prerequisite once anything above it takes locks.Unittests cover pool round-trip/exhaustion/packing/trim/jumbo, reclaim ordering/early-exit/reentrancy, watermark fire-once/re-arm, and thread identity. openwatt consumer: open-watt/openwatt#518.