Memory contexts
Skill matejformanek/postgres-claude/.claude/skills/memory-contexts
Turn Claude Code into a long-term collaborator on PostgreSQL internals — cited knowledge corpus, agent skills, slash commands, and task-shaped scenarios for backend hacking.
npx -y skills add matejformanek/postgres-claude --skill memory-contextsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Allocate memory in PostgreSQL backend C — pick the right MemoryContext and use palloc / palloc0 / pstrdup / psprintf correctly. Covers CurrentMemoryContext / TopMemoryContext / per-query / per-tuple / ExecutorState context choice, MemoryContextSwitchTo discipline, the OOM-throws-ereport contract (no NULL checks), pfree vs MemoryContextReset vs MemoryContextDelete, the AllocSet vs Slab vs Generation vs Bump context-type cheat sheet, and leak-scoping in long-running backends. Use whenever a PG patch or extension calls palloc / palloc0 / MemoryContextAlloc, creates or switches a MemoryContext, picks AllocSet vs Slab vs Generation vs Bump, or debugs a context-shaped leak. Skip for plain malloc / free / jemalloc / mimalloc / tcmalloc, JVM / Go / .NET GC tuning, Rust Box / Rc / Arc / lifetimes, shared_buffers / work_mem production tuning, valgrind / heaptrack on non-PG programs, and C++ smart pointers.
SKILL.md
10.2 KB, ~2.4k tokens by cl100k_base, as published. Nobody here has run it
Memory contexts — actionable rules
Reference doc: knowledge/idioms/memory-contexts.md.
Allocation cheat sheet
palloc(n)— allocate inCurrentMemoryContext. Never returns NULL — it callsereport(ERROR)on OOM. Don't test for NULL.palloc0(n)— like palloc plus zero-fill.pstrdup(s)/pnstrdup(s, n)/psprintf(fmt, ...)— string variants.palloc_object(T),palloc_array(T, count),palloc0_array(T, count)— type-safe macros. Prefer these over raw size calculations.palloc_extended(n, MCXT_ALLOC_NO_OOM)— opt out of the OOM-throws contract (returns NULL on failure). Use only when you specifically can recover.palloc_extended(n, MCXT_ALLOC_HUGE)/MemoryContextAllocHuge— past theMaxAllocSize(≈1 GB) limit; capped atMaxAllocHugeSize(SIZE_MAX/2).MemoryContextAlloc(ctx, n)— allocate in a specific context without switchingCurrentMemoryContext.repalloc(p, n)— grow/shrink. Goes to p's original context, not current.pfree(p)— free a chunk. Goes to its original context.
Hard rules
pfree(NULL)is undefined — always check first if pointer may be NULL.repalloc(NULL, n)is undefined — first allocation must bepalloc.palloc(0)is legal — returns a usable chunk.- Do not test palloc's return for NULL unless you used
MCXT_ALLOC_NO_OOM. - Single-allocation cap is
MaxAllocSize(1 GB - 1) for regular palloc. Exceeding it raiseserrmsg("invalid memory alloc request size %zu")— switch toMemoryContextAllocHuge/palloc_extended(..., MCXT_ALLOC_HUGE), capped atMaxAllocHugeSize = SIZE_MAX/2. Userepalloc_hugeto grow. Slab is fixed-size so N/A; Bump cannot be repalloc'd; AllocSet and Generation both support huge chunks (AllocSet routes any chunk ≥ 8 KB straight to malloc).
Picking the right context
Default rule: CurrentMemoryContext should be the shortest-lived context
that still outlives the data you're allocating.
| You need data to live until... | Allocate in / switch to |
|---|---|
| End of one tuple cycle | the executor's per-tuple ExprContext (usually already CurrentMemoryContext in expression eval); reset at the start of the next cycle |
| End of one statement | per-query context (executor sets this up; estate->es_query_cxt or econtext->ecxt_per_query_memory) or MessageContext |
| Across SRF calls (value-per-call) | funcctx->multi_call_memory_ctx from SRF_FIRSTCALL_INIT() |
| Across SRF calls (materialize) | rsinfo->econtext->ecxt_per_query_memory for the tuplestore |
| Across aggregate transitions (per group) | the aggcontext from AggCheckCallContext(fcinfo, &aggcontext) |
| End of current (sub)transaction | CurTransactionContext |
| End of top-level transaction | TopTransactionContext |
| Lifetime of one portal | the portal's private context (PortalContext when active) |
| Lifetime of a cache entry | a child of CacheMemoryContext you control (delete the child on invalidation; delete is recursive) |
| Backend lifetime / forever | TopMemoryContext — but only if truly forever |
Avoid making TopMemoryContext or CacheMemoryContext CurrentMemoryContext.
Allocating into them by accident is the classic permanent-leak bug.
The switch idiom
MemoryContext oldcxt = MemoryContextSwitchTo(target_cxt);
result = build_something(); /* allocs land in target_cxt */
MemoryContextSwitchTo(oldcxt);
return result;
You do NOT need to restore on error paths in normal code — transaction abort
will fix CurrentMemoryContext. If you use PG_TRY, declare oldcxt
volatile if you read it in PG_CATCH.
Creating a context
MemoryContext cxt = AllocSetContextCreate(parent,
"my purpose", /* MUST be a literal */
ALLOCSET_DEFAULT_SIZES);
MemoryContextSetIdentifier(cxt, dynamic_name); /* if you need a runtime label */
Cache-entry pattern (per-relation child of CacheMemoryContext, blown
away as a unit on invalidation — MemoryContextDelete is recursive):
MemoryContext rulescxt = AllocSetContextCreate(CacheMemoryContext,
"relation rules",
ALLOCSET_SMALL_SIZES);
MemoryContextCopyAndSetIdentifier(rulescxt, RelationGetRelationName(rel));
oldcxt = MemoryContextSwitchTo(rulescxt);
/* build cache contents; everything lands in rulescxt */
MemoryContextSwitchTo(oldcxt);
rel->rd_rulescxt = rulescxt; /* invalidation: MemoryContextDelete */
See source/src/backend/utils/cache/relcache.c for the real precedent
(rd_rulescxt, rd_indexcxt, rd_pdcxt, …).
Sizing presets:
ALLOCSET_DEFAULT_SIZES— 0 / 8KB / 8MB. Use when the context may hold a lot.ALLOCSET_SMALL_SIZES— 0 / 1KB / 8KB. Use for many small contexts (per relcache entry, per query plan).ALLOCSET_START_SMALL_SIZES— small init, default max.
Pick a non-default context type when the allocation pattern fits:
- Slab (
SlabContextCreate(parent, name, blockSize, chunkSize)) — all chunks are the same size. Good for reorder buffer txns, fixed-shape structs. - Generation (
GenerationContextCreate(parent, name, min, init, max)) — FIFO-ish allocation/free pattern. Good for queue-like buffering. - Bump (
BumpContextCreate(...)) — write-once, never pfree'd. Densest packing.pfree/repalloc/GetMemoryChunkContextwill NOT work on bump chunks — only context reset/delete frees them.
Cleanup
MemoryContextReset(cxt)— frees all chunks AND deletes all child contexts.MemoryContextResetOnly(cxt)— only frees chunks; children remain.MemoryContextDelete(cxt)— frees everything including the context itself and all descendants.MemoryContextDeleteChildren(cxt)— keep cxt, delete its subtree.MemoryContextRegisterResetCallback(cxt, cb)— fire a callback the next time cxt is reset or deleted. Use for closing file handles, releasing refcounts, tearing down non-PG-owned resources.
Common mistakes to avoid
- Testing
palloc(...)for NULL. It cannot return NULL. Delete the test. pfree(p)where p might be NULL. Guard explicitly.- Allocating in
CacheMemoryContextwhile building cache entries without switching — permanent leak per entry. Switch in, switch out. - Returning per-tuple-context memory across the boundary. The next tuple
cycle resets that context. Either palloc into the caller's context or
datumCopy/pstrdup/ explicit copy. - Non-constant string passed as
AllocSetContextCreatename — failsStaticAssertExpr. UseMemoryContextSetIdentifierfor the dynamic part. - Calling
pfree/repallocon a bump-context chunk — undefined. pallocinside a critical section — the context must haveallowInCritSection = true(MemoryContextAllowInCriticalSection). Default contexts forbid it; the assertion fires only in assert builds.- Using a saved
MemoryContextafter the context was deleted. Especially common withPortalContext— a portal drop invalidates it.
Checklist before committing
- No
NULLchecks onpalloc/palloc0/pstrdup/psprintf. -
pfree(p)callers ensurep != NULL. - Long-lived allocations explicitly switch into the right context.
- New
AllocSetContextCreateuses string-literal name + appropriate size preset. - If you stored a pointer somewhere persistent, you allocated it in a context that outlives the storing struct.
- For non-PG resource attached to a context lifetime, you registered a reset callback (don't rely on destructors or explicit cleanup paths).
-
volatilequalifier on anyoldcxt/ pointer used acrossPG_TRY/PG_CATCH. - If you used Slab/Generation/Bump, you understand which ops are unsupported (bump in particular).
When in doubt, cite
src/backend/executor/execMain.c— canonicalMemoryContextSwitchTopattern aroundes_query_cxt.src/backend/utils/cache/relcache.c— per-relation child contexts underCacheMemoryContext.src/backend/utils/mmgr/mcxt.c— type-independent operations.src/backend/utils/mmgr/README— the canonical design discussion.
Cross-references
.claude/skills/error-handling/SKILL.md— OOM-throws-ereport contract;AbortTransactionreleases per-query contexts;PG_TRY/volatilerules..claude/skills/debugging/SKILL.md—pg_backend_memory_contexts,pg_log_backend_memory_contexts(pid),MemoryContextStats(TopMemoryContext)from the debugger..claude/skills/executor-and-planner/SKILL.md—es_query_cxt,ExprContext, per-tuple contexts in plan nodes..claude/skills/fmgr-and-spi/SKILL.md—MultiCallMemoryCtxfor SRFs;fcinfo->flinfo->fn_mcxt..claude/skills/coding-style/SKILL.md—pallocvs rawmallocrule;pstrdup,psprintfconventions.knowledge/idioms/memory-contexts.md— long-form idiom doc.