Memory contexts — actionable rules
Reference doc: knowledge/idioms/memory-contexts.md.
Allocation cheat sheet
palloc(n) — allocate in CurrentMemoryContext. Never returns NULL — it
calls ereport(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 the
MaxAllocSize (≈1 GB) limit; capped at MaxAllocHugeSize (SIZE_MAX/2).
MemoryContextAlloc(ctx, n) — allocate in a specific context without
switching CurrentMemoryContext.
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 be palloc.
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 raises errmsg("invalid memory alloc request size %zu") —
switch to MemoryContextAllocHuge / palloc_extended(..., MCXT_ALLOC_HUGE),
capped at MaxAllocHugeSize = SIZE_MAX/2. Use repalloc_huge to 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/GetMemoryChunkContext will 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
CacheMemoryContext while 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
AllocSetContextCreate name — fails
StaticAssertExpr. Use MemoryContextSetIdentifier for the dynamic part.
- Calling
pfree / repalloc on a bump-context chunk — undefined.
palloc inside a critical section — the context must have
allowInCritSection = true (MemoryContextAllowInCriticalSection).
Default contexts forbid it; the assertion fires only in assert builds.
- Using a saved
MemoryContext after the context was deleted. Especially
common with PortalContext — a portal drop invalidates it.
Checklist before committing
When in doubt, cite
src/backend/executor/execMain.c — canonical MemoryContextSwitchTo pattern
around es_query_cxt.
src/backend/utils/cache/relcache.c — per-relation child contexts under
CacheMemoryContext.
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; AbortTransaction releases per-query contexts; PG_TRY / volatile rules.
.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 — MultiCallMemoryCtx for SRFs; fcinfo->flinfo->fn_mcxt.
.claude/skills/coding-style/SKILL.md — palloc vs raw malloc rule; pstrdup, psprintf conventions.
knowledge/idioms/memory-contexts.md — long-form idiom doc.
1---2name: memory-contexts3description: 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.4---56# Memory contexts — actionable rules78Reference doc: `knowledge/idioms/memory-contexts.md`.910## Allocation cheat sheet1112- `palloc(n)` — allocate in `CurrentMemoryContext`. **Never returns NULL** — it13 calls `ereport(ERROR)` on OOM. Don't test for NULL.14- `palloc0(n)` — like palloc plus zero-fill.15- `pstrdup(s)` / `pnstrdup(s, n)` / `psprintf(fmt, ...)` — string variants.16- `palloc_object(T)`, `palloc_array(T, count)`, `palloc0_array(T, count)`17 — type-safe macros. Prefer these over raw size calculations.18- `palloc_extended(n, MCXT_ALLOC_NO_OOM)` — opt out of the OOM-throws contract19 (returns NULL on failure). Use only when you specifically can recover.20- `palloc_extended(n, MCXT_ALLOC_HUGE)` / `MemoryContextAllocHuge` — past the21 `MaxAllocSize` (≈1 GB) limit; capped at `MaxAllocHugeSize` (SIZE_MAX/2).22- `MemoryContextAlloc(ctx, n)` — allocate in a specific context without23 switching `CurrentMemoryContext`.24- `repalloc(p, n)` — grow/shrink. Goes to p's original context, not current.25- `pfree(p)` — free a chunk. Goes to its original context.2627### Hard rules2829- **`pfree(NULL)` is undefined** — always check first if pointer may be NULL.30- **`repalloc(NULL, n)` is undefined** — first allocation must be `palloc`.31- **`palloc(0)` is legal** — returns a usable chunk.32- **Do not test palloc's return for NULL** unless you used `MCXT_ALLOC_NO_OOM`.33- **Single-allocation cap is `MaxAllocSize` (1 GB - 1)** for regular palloc.34 Exceeding it raises `errmsg("invalid memory alloc request size %zu")` —35 switch to `MemoryContextAllocHuge` / `palloc_extended(..., MCXT_ALLOC_HUGE)`,36 capped at `MaxAllocHugeSize = SIZE_MAX/2`. Use `repalloc_huge` to grow.37 Slab is fixed-size so N/A; Bump cannot be repalloc'd; AllocSet and38 Generation both support huge chunks (AllocSet routes any chunk ≥ 8 KB39 straight to malloc).4041## Picking the right context4243Default rule: **`CurrentMemoryContext` should be the shortest-lived context44that still outlives the data you're allocating.**4546| You need data to live until... | Allocate in / switch to |47|---|---|48| 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 |49| End of one statement | per-query context (executor sets this up; `estate->es_query_cxt` or `econtext->ecxt_per_query_memory`) or `MessageContext` |50| Across SRF calls (value-per-call) | `funcctx->multi_call_memory_ctx` from `SRF_FIRSTCALL_INIT()` |51| Across SRF calls (materialize) | `rsinfo->econtext->ecxt_per_query_memory` for the tuplestore |52| Across aggregate transitions (per group) | the aggcontext from `AggCheckCallContext(fcinfo, &aggcontext)` |53| End of current (sub)transaction | `CurTransactionContext` |54| End of top-level transaction | `TopTransactionContext` |55| Lifetime of one portal | the portal's private context (`PortalContext` when active) |56| Lifetime of a cache entry | a child of `CacheMemoryContext` you control (delete the child on invalidation; delete is recursive) |57| Backend lifetime / forever | `TopMemoryContext` — but only if truly forever |5859**Avoid making `TopMemoryContext` or `CacheMemoryContext` `CurrentMemoryContext`.**60Allocating into them by accident is the classic permanent-leak bug.6162## The switch idiom6364```c65MemoryContext oldcxt = MemoryContextSwitchTo(target_cxt);66result = build_something(); /* allocs land in target_cxt */67MemoryContextSwitchTo(oldcxt);68return result;69```7071You do NOT need to restore on error paths in normal code — transaction abort72will fix `CurrentMemoryContext`. If you use `PG_TRY`, declare `oldcxt`73`volatile` if you read it in `PG_CATCH`.7475## Creating a context7677```c78MemoryContext cxt = AllocSetContextCreate(parent,79 "my purpose", /* MUST be a literal */80 ALLOCSET_DEFAULT_SIZES);81MemoryContextSetIdentifier(cxt, dynamic_name); /* if you need a runtime label */82```8384Cache-entry pattern (per-relation child of `CacheMemoryContext`, blown85away as a unit on invalidation — `MemoryContextDelete` is recursive):8687```c88MemoryContext rulescxt = AllocSetContextCreate(CacheMemoryContext,89 "relation rules",90 ALLOCSET_SMALL_SIZES);91MemoryContextCopyAndSetIdentifier(rulescxt, RelationGetRelationName(rel));92oldcxt = MemoryContextSwitchTo(rulescxt);93/* build cache contents; everything lands in rulescxt */94MemoryContextSwitchTo(oldcxt);95rel->rd_rulescxt = rulescxt; /* invalidation: MemoryContextDelete */96```97See `source/src/backend/utils/cache/relcache.c` for the real precedent98(`rd_rulescxt`, `rd_indexcxt`, `rd_pdcxt`, …).99100Sizing presets:101- `ALLOCSET_DEFAULT_SIZES` — 0 / 8KB / 8MB. Use when the context may hold a lot.102- `ALLOCSET_SMALL_SIZES` — 0 / 1KB / 8KB. Use for many small contexts (per103 relcache entry, per query plan).104- `ALLOCSET_START_SMALL_SIZES` — small init, default max.105106Pick a non-default context type when the allocation pattern fits:107- **Slab** (`SlabContextCreate(parent, name, blockSize, chunkSize)`) — all108 chunks are the same size. Good for reorder buffer txns, fixed-shape structs.109- **Generation** (`GenerationContextCreate(parent, name, min, init, max)`) —110 FIFO-ish allocation/free pattern. Good for queue-like buffering.111- **Bump** (`BumpContextCreate(...)`) — write-once, never pfree'd. Densest112 packing. **`pfree`/`repalloc`/`GetMemoryChunkContext` will NOT work** on113 bump chunks — only context reset/delete frees them.114115## Cleanup116117- `MemoryContextReset(cxt)` — frees all chunks AND deletes all child contexts.118- `MemoryContextResetOnly(cxt)` — only frees chunks; children remain.119- `MemoryContextDelete(cxt)` — frees everything including the context itself120 and all descendants.121- `MemoryContextDeleteChildren(cxt)` — keep cxt, delete its subtree.122- `MemoryContextRegisterResetCallback(cxt, cb)` — fire a callback the next123 time cxt is reset or deleted. Use for closing file handles, releasing124 refcounts, tearing down non-PG-owned resources.125126## Common mistakes to avoid1271281. **Testing `palloc(...)` for NULL.** It cannot return NULL. Delete the test.1292. **`pfree(p)` where p might be NULL.** Guard explicitly.1303. **Allocating in `CacheMemoryContext` while building cache entries** without131 switching — permanent leak per entry. Switch in, switch out.1324. **Returning per-tuple-context memory across the boundary.** The next tuple133 cycle resets that context. Either palloc into the caller's context or134 `datumCopy` / `pstrdup` / explicit copy.1355. **Non-constant string passed as `AllocSetContextCreate` name** — fails136 `StaticAssertExpr`. Use `MemoryContextSetIdentifier` for the dynamic part.1376. **Calling `pfree` / `repalloc` on a bump-context chunk** — undefined.1387. **`palloc` inside a critical section** — the context must have139 `allowInCritSection = true` (`MemoryContextAllowInCriticalSection`).140 Default contexts forbid it; the assertion fires only in assert builds.1418. **Using a saved `MemoryContext` after the context was deleted.** Especially142 common with `PortalContext` — a portal drop invalidates it.143144## Checklist before committing145146- [ ] No `NULL` checks on `palloc`/`palloc0`/`pstrdup`/`psprintf`.147- [ ] `pfree(p)` callers ensure `p != NULL`.148- [ ] Long-lived allocations explicitly switch into the right context.149- [ ] New `AllocSetContextCreate` uses string-literal name + appropriate size150 preset.151- [ ] If you stored a pointer somewhere persistent, you allocated it in a152 context that outlives the storing struct.153- [ ] For non-PG resource attached to a context lifetime, you registered a154 reset callback (don't rely on destructors or explicit cleanup paths).155- [ ] `volatile` qualifier on any `oldcxt` / pointer used across `PG_TRY` /156 `PG_CATCH`.157- [ ] If you used Slab/Generation/Bump, you understand which ops are unsupported158 (bump in particular).159160## When in doubt, cite161162- `src/backend/executor/execMain.c` — canonical `MemoryContextSwitchTo` pattern163 around `es_query_cxt`.164- `src/backend/utils/cache/relcache.c` — per-relation child contexts under165 `CacheMemoryContext`.166- `src/backend/utils/mmgr/mcxt.c` — type-independent operations.167- `src/backend/utils/mmgr/README` — the canonical design discussion.168169## Cross-references170171- `.claude/skills/error-handling/SKILL.md` — OOM-throws-ereport contract; `AbortTransaction` releases per-query contexts; `PG_TRY` / `volatile` rules.172- `.claude/skills/debugging/SKILL.md` — `pg_backend_memory_contexts`, `pg_log_backend_memory_contexts(pid)`, `MemoryContextStats(TopMemoryContext)` from the debugger.173- `.claude/skills/executor-and-planner/SKILL.md` — `es_query_cxt`, `ExprContext`, per-tuple contexts in plan nodes.174- `.claude/skills/fmgr-and-spi/SKILL.md` — `MultiCallMemoryCtx` for SRFs; `fcinfo->flinfo->fn_mcxt`.175- `.claude/skills/coding-style/SKILL.md` — `palloc` vs raw `malloc` rule; `pstrdup`, `psprintf` conventions.176- `knowledge/idioms/memory-contexts.md` — long-form idiom doc.