From e2e8efb6bb4b564463f2d892d45afc4cdeacabc4 Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Thu, 26 Feb 2026 15:24:03 -0500 Subject: [PATCH v1 4/5] MemCTX: Add minimal proxy context type that just defers to malloc Co-authored-by: Matthias van de Meent --- src/backend/nodes/gen_node_support.pl | 2 +- src/backend/utils/adt/mcxtfuncs.c | 3 + src/backend/utils/mmgr/Makefile | 1 + src/backend/utils/mmgr/mcxt.c | 14 +- src/backend/utils/mmgr/meson.build | 1 + src/backend/utils/mmgr/proxy.c | 412 ++++++++++++++++++++++++++ src/include/nodes/memnodes.h | 3 +- src/include/utils/memutils.h | 4 + src/include/utils/memutils_internal.h | 18 +- src/tools/pgindent/typedefs.list | 2 + 10 files changed, 456 insertions(+), 4 deletions(-) create mode 100644 src/backend/utils/mmgr/proxy.c diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl index 0b766272018..d3bb9877a62 100644 --- a/src/backend/nodes/gen_node_support.pl +++ b/src/backend/nodes/gen_node_support.pl @@ -136,7 +136,7 @@ my @abstract_types = qw(Node); # they otherwise don't participate in node support. my @extra_tags = qw( IntList OidList XidList - AllocSetContext GenerationContext SlabContext BumpContext + AllocSetContext GenerationContext SlabContext BumpContext ProxyContext TIDBitmap WindowObjectData ); diff --git a/src/backend/utils/adt/mcxtfuncs.c b/src/backend/utils/adt/mcxtfuncs.c index 1a4dbbeb8db..85a93de331e 100644 --- a/src/backend/utils/adt/mcxtfuncs.c +++ b/src/backend/utils/adt/mcxtfuncs.c @@ -160,6 +160,9 @@ PutMemoryContextsStatsTupleStore(Tuplestorestate *tupstore, case T_BumpContext: type = "Bump"; break; + case T_ProxyContext: + type = "Proxy"; + break; default: type = "???"; break; diff --git a/src/backend/utils/mmgr/Makefile b/src/backend/utils/mmgr/Makefile index 01a1fb85270..64d70bfcdf0 100644 --- a/src/backend/utils/mmgr/Makefile +++ b/src/backend/utils/mmgr/Makefile @@ -22,6 +22,7 @@ OBJS = \ mcxt.o \ memdebug.o \ portalmem.o \ + proxy.o \ slab.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c index 594c7a93bba..1cc6e603c9b 100644 --- a/src/backend/utils/mmgr/mcxt.c +++ b/src/backend/utils/mmgr/mcxt.c @@ -132,6 +132,19 @@ static const MemoryContextMethods mcxt_methods[] = { [MCTX_BUMP_ID].check = BumpCheck, #endif + /* proxy.c */ + [MCTX_PROXY_ID].alloc = ProxyAlloc, + [MCTX_PROXY_ID].free_p = ProxyFree, + [MCTX_PROXY_ID].realloc = ProxyRealloc, + [MCTX_PROXY_ID].reset = ProxyReset, + [MCTX_PROXY_ID].delete_context = ProxyDelete, + [MCTX_PROXY_ID].get_chunk_context = ProxyGetChunkContext, + [MCTX_PROXY_ID].get_chunk_space = ProxyGetChunkSpace, + [MCTX_PROXY_ID].is_empty = ProxyIsEmpty, + [MCTX_PROXY_ID].stats = ProxyStats, +#ifdef MEMORY_CONTEXT_CHECKING + [MCTX_PROXY_ID].check = ProxyCheck, +#endif /* * Reserved and unused IDs should have dummy entries here. This allows us @@ -141,7 +154,6 @@ static const MemoryContextMethods mcxt_methods[] = { */ BOGUS_MCTX(MCTX_1_RESERVED_GLIBC_ID), BOGUS_MCTX(MCTX_2_RESERVED_GLIBC_ID), - BOGUS_MCTX(MCTX_8_UNUSED_ID), BOGUS_MCTX(MCTX_9_UNUSED_ID), BOGUS_MCTX(MCTX_10_UNUSED_ID), BOGUS_MCTX(MCTX_11_UNUSED_ID), diff --git a/src/backend/utils/mmgr/meson.build b/src/backend/utils/mmgr/meson.build index 61837e9f91b..6eb5e068864 100644 --- a/src/backend/utils/mmgr/meson.build +++ b/src/backend/utils/mmgr/meson.build @@ -10,5 +10,6 @@ backend_sources += files( 'mcxt.c', 'memdebug.c', 'portalmem.c', + 'proxy.c', 'slab.c', ) diff --git a/src/backend/utils/mmgr/proxy.c b/src/backend/utils/mmgr/proxy.c new file mode 100644 index 00000000000..8ef48f0c130 --- /dev/null +++ b/src/backend/utils/mmgr/proxy.c @@ -0,0 +1,412 @@ +/*------------------------------------------------------------------------- +* + * proxy.c + * Proxy allocator definitions. + * + * Proxy is a MemoryContext implementation designed for memory usages which + * require their own memory context, but which generally have few allocations + * that generally have a very long lifetime. Compared to ASet, every + * allocation of a Proxy memory context gets an External chunk. + * + * Portions Copyright (c) 2024-2026, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/mmgr/proxy.c + * + * + * Proxy is best suited to cases which require a small number of small + * long-lived allocations, where the size overhead of a full aset context + * are significant. + * + * Allocations are MAXALIGNed. + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include + +#include "lib/ilist.h" +#include "port/pg_bitutils.h" +#include "utils/memdebug.h" +#include "utils/memutils.h" +#include "utils/memutils_memorychunk.h" +#include "utils/memutils_internal.h" + +typedef struct ProxyContext +{ + MemoryContextData header; /* Standard memory-context fields */ + + dlist_head allocations; +#ifdef MEMORY_CONTEXT_CHECKING + int chunks_allocated; +#endif +} ProxyContext; + +typedef struct ProxyChunk +{ + dlist_node node; + size_t sz; + ProxyContext *context; +} ProxyChunk; + + +#define ExternalChunkGetBlock(chunk) \ + (ProxyChunk *) ((char *) chunk - MAXALIGN(sizeof(ProxyChunk))) + + +/* + * ProxyContextCreate + * Create a Proxy memory context + */ +MemoryContext +ProxyContextCreate(MemoryContext parent, const char *name) +{ + Size allocSize = sizeof(ProxyContext); + ProxyContext *ctx; + + /* + * Allocate the initial block. Unlike other proxy.c blocks, it starts + * with the context header and its block header follows that. + */ + ctx = (ProxyContext *) malloc(allocSize); + if (ctx == NULL) + { + MemoryContextStats(TopMemoryContext); + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Failed while creating memory context \"%s\".", + name))); + } + + VALGRIND_CREATE_MEMPOOL(ctx, 0, false); + VALGRIND_MEMPOOL_ALLOC(ctx, ctx, allocSize); + + + dlist_init(&ctx->allocations); +#ifdef MEMORY_CONTEXT_CHECKING + ctx->chunks_allocated = 0; +#endif + + /* Finally, do the type-independent part of context creation */ + MemoryContextCreate((MemoryContext) ctx, T_ProxyContext, MCTX_PROXY_ID, + parent, name); + + ((MemoryContext) ctx)->mem_allocated = allocSize; + + return (MemoryContext) ctx; +} + +/* + * ProxyAlloc + * Alloc memory into the Proxy memory context. + */ +void * +ProxyAlloc(MemoryContext context, Size size, int flags) +{ + ProxyContext *ctx = (ProxyContext *) context; + size_t sz; + ProxyChunk *proxy_chunk; + MemoryChunk *chunk; + + /* validate 'size' is within the limits for the given 'flags' */ + MemoryContextCheckSize(context, size, flags); + + sz = MAXALIGN(sizeof(ProxyChunk)) + sizeof(MemoryChunk) + MAXALIGN(size); + + proxy_chunk = (ProxyChunk *) malloc(sz); + if (proxy_chunk == NULL) + return MemoryContextAllocationFailure(context, size, flags); + + VALGRIND_MEMPOOL_ALLOC(ctx, proxy_chunk, size); + + proxy_chunk->sz = sz; + context->mem_allocated += sz; + + proxy_chunk->context = ctx; + + dlist_push_tail(&ctx->allocations, &proxy_chunk->node); + + chunk = (MemoryChunk *) (((char *) proxy_chunk) + MAXALIGN(sizeof(ProxyChunk))); + + MemoryChunkSetHdrMaskExternal(chunk, MCTX_PROXY_ID); +#ifdef MEMORY_CONTEXT_CHECKING + ctx->chunks_allocated++; +#endif + + return MemoryChunkGetPointer(chunk); +} + +/* + * ProxyAlloc + * Free memory from the Proxy memory context. + */ +void +ProxyFree(void *pointer) +{ + MemoryChunk *chunk = PointerGetMemoryChunk(pointer); + ProxyContext *ctx; + ProxyChunk *proxy_chunk; + + VALGRIND_MAKE_MEM_DEFINED(chunk, sizeof(MemoryChunk)); + + Assert(MemoryChunkIsExternal(chunk)); + + proxy_chunk = ExternalChunkGetBlock(chunk); + VALGRIND_MAKE_MEM_DEFINED(proxy_chunk, sizeof(ProxyChunk)); + + ctx = proxy_chunk->context; + + dlist_delete_from(&ctx->allocations, &proxy_chunk->node); + + ctx->header.mem_allocated -= proxy_chunk->sz; + + VALGRIND_MEMPOOL_FREE(ctx, proxy_chunk); +#ifdef MEMORY_CONTEXT_CHECKING + ctx->chunks_allocated--; +#endif + + free(proxy_chunk); +} + +/* + * ProxyAlloc + * Realloc memory in the Proxy memory context. + */ +void * +ProxyRealloc(void *pointer, Size size, int flags) +{ + MemoryChunk *old_chunk = PointerGetMemoryChunk(pointer); + ProxyChunk *proxy_chunk, + *old_proxy_chunk; + MemoryChunk *chunk; + ProxyContext *ctx; + size_t sz; + + VALGRIND_MAKE_MEM_DEFINED(old_chunk, sizeof(MemoryChunk)); + + Assert(MemoryChunkIsExternal(old_chunk)); + + sz = MAXALIGN(sizeof(ProxyChunk)) + sizeof(MemoryChunk) + MAXALIGN(size); + + old_proxy_chunk = ExternalChunkGetBlock(old_chunk); + + VALGRIND_MAKE_MEM_DEFINED(old_proxy_chunk, sizeof(ProxyChunk)); + + ctx = old_proxy_chunk->context; + + proxy_chunk = malloc(sz); + if (proxy_chunk == NULL) + return MemoryContextAllocationFailure((MemoryContext) ctx, size, flags); + + VALGRIND_MEMPOOL_ALLOC(ctx, proxy_chunk, size); + + dlist_delete_from(&ctx->allocations, &old_proxy_chunk->node); + ctx->header.mem_allocated -= old_proxy_chunk->sz; + + chunk = (MemoryChunk *) (((char *) proxy_chunk) + MAXALIGN(sizeof(ProxyChunk))); + + proxy_chunk->context = ctx; + proxy_chunk->sz = sz; + dlist_push_tail(&ctx->allocations, &proxy_chunk->node); + ctx->header.mem_allocated += sz; + + MemoryChunkSetHdrMaskExternal(chunk, MCTX_PROXY_ID); + + memcpy(chunk, old_chunk, Min(proxy_chunk->sz, old_proxy_chunk->sz)); + + VALGRIND_MEMPOOL_FREE(ctx, old_proxy_chunk); + free(old_proxy_chunk); + + return MemoryChunkGetPointer(chunk); +} + +/* + * ProxyReset + * Reset the Proxy memory context. + */ +void +ProxyReset(MemoryContext context) +{ + ProxyContext *ctx = (ProxyContext *) context; + + while (!dlist_is_empty(&ctx->allocations)) + { + ProxyChunk *proxy_chunk = + dlist_container(ProxyChunk, node, + dlist_pop_head_node(&ctx->allocations)); + + VALGRIND_MEMPOOL_FREE(ctx, proxy_chunk); + free(proxy_chunk); + } +#ifdef MEMORY_CONTEXT_CHECKING + ctx->chunks_allocated = 0; + ctx->header.mem_allocated = sizeof(ProxyContext); +#endif +} + +/* + * ProxyAlloc + * Delete this Proxy memory context. + */ +void +ProxyDelete(MemoryContext context) +{ + ProxyContext *ctx = (ProxyContext *) context; + + ProxyReset(context); + + VALGRIND_DESTROY_MEMPOOL(context); + + free(ctx); +} + +/* + * ProxyGetChunkContext + * Return the MemoryContext that 'pointer' belongs to. + */ +MemoryContext +ProxyGetChunkContext(void *pointer) +{ + MemoryChunk *chunk = PointerGetMemoryChunk(pointer); + ProxyChunk *proxy_chunk; + + VALGRIND_MAKE_MEM_DEFINED(chunk, sizeof(MemoryChunk)); + + Assert(MemoryChunkIsExternal(chunk)); + proxy_chunk = ExternalChunkGetBlock(chunk); + + VALGRIND_MAKE_MEM_DEFINED(proxy_chunk, sizeof(ProxyChunk)); + + return (MemoryContext) proxy_chunk->context; +} + +/* + * ProxyGetChunkSpace +* Given a palloc'd chunk, determine the total space + * it occupies (including all memory-allocation overhead). + */ +Size +ProxyGetChunkSpace(void *pointer) +{ + MemoryChunk *chunk = PointerGetMemoryChunk(pointer); + ProxyChunk *proxy_chunk; + + VALGRIND_MAKE_MEM_DEFINED(chunk, sizeof(MemoryChunk)); + + Assert(MemoryChunkIsExternal(chunk)); + proxy_chunk = ExternalChunkGetBlock(chunk); + + VALGRIND_MAKE_MEM_DEFINED(proxy_chunk, sizeof(ProxyChunk)); + + return proxy_chunk->sz; +} + +/* + * ProxyIsEmpty +* Is the ProxyContext empty of any allocated space? + */ +bool +ProxyIsEmpty(MemoryContext context) +{ + ProxyContext *ctx = (ProxyContext *) context; + + return dlist_is_empty(&ctx->allocations); +} + +/* + * ProxyStats + * Compute stats about memory consumption of a Proxy context. + * + * printfunc: if not NULL, pass a human-readable stats string to this. + * passthru: pass this pointer through to printfunc. + * totals: if not NULL, add stats about this context into *totals. + * print_to_stderr: print stats to stderr if true, elog otherwise. + */ +void +ProxyStats(MemoryContext context, MemoryStatsPrintFunc printfunc, + void *passthru, MemoryContextCounters *totals, + bool print_to_stderr) +{ + ProxyContext *ctx = (ProxyContext *) context; + dlist_iter iter; + Size totalspace; + Size nchunks = 0; + + totalspace = MAXALIGN(sizeof(ProxyContext)); + + dlist_foreach(iter, &ctx->allocations) + { + ProxyChunk *proxy_chunk = dlist_container(ProxyChunk, node, iter.cur); + + nchunks++; + totalspace += proxy_chunk->sz; + } + + + if (printfunc) + { + char stats_string[200]; + + snprintf(stats_string, sizeof(stats_string), + "%zu total in %zu chunks;", + totalspace, nchunks); + printfunc(context, passthru, stats_string, print_to_stderr); + } + + if (totals) + { + totals->nblocks += nchunks; + totals->totalspace += totalspace; + } +} + +#ifdef MEMORY_CONTEXT_CHECKING +/* + * ProxyCheck + * Walk through chunks and check consistency of memory. + * + * NOTE: report errors as WARNING, *not* ERROR or FATAL. Otherwise you'll + * find yourself in an infinite loop when trouble occurs, because this + * routine will be entered again when elog cleanup tries to release memory! + */ +void +ProxyCheck(MemoryContext context) +{ + ProxyContext *ctx = (ProxyContext *) context; + const char *name = context->name; + dlist_iter iter; + Size total_allocated = sizeof(ProxyContext); + Size chunks_allocated = 0; + + /* walk all blocks in this context */ + dlist_foreach(iter, &ctx->allocations) + { + ProxyChunk *chunk = dlist_container(ProxyChunk, node, iter.cur); + + total_allocated += chunk->sz; + chunks_allocated++; + } + + if (chunks_allocated != ctx->chunks_allocated) + { + elog(WARNING, "problem in Proxy %s: number of allocated chunks %d does not match header %d", + name, (int) chunks_allocated, ctx->chunks_allocated); + } + + if (chunks_allocated >= INT_MAX) + { + elog(WARNING, "problem in Proxy %s: too many chunks allocated in one context", + name); + } + + if (total_allocated != ctx->header.mem_allocated) + { + elog(WARNING, "problem in Proxy %s: amount of memory allocated %d does not match header %d", + name, (int) total_allocated, ctx->chunks_allocated); + } + Assert(total_allocated == context->mem_allocated); + Assert(chunks_allocated == ctx->chunks_allocated); +} +#endif diff --git a/src/include/nodes/memnodes.h b/src/include/nodes/memnodes.h index 4b24778fe1e..a9a4e06d86b 100644 --- a/src/include/nodes/memnodes.h +++ b/src/include/nodes/memnodes.h @@ -147,6 +147,7 @@ typedef struct MemoryContextData (IsA((context), AllocSetContext) || \ IsA((context), SlabContext) || \ IsA((context), GenerationContext) || \ - IsA((context), BumpContext))) + IsA((context), BumpContext) || \ + IsA((context), ProxyContext))) #endif /* MEMNODES_H */ diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h index 38036c7c703..ff531742c8f 100644 --- a/src/include/utils/memutils.h +++ b/src/include/utils/memutils.h @@ -150,6 +150,10 @@ extern MemoryContext BumpContextCreate(MemoryContext parent, Size initBlockSize, Size maxBlockSize); +/* proxy.c */ +extern MemoryContext ProxyContextCreate(MemoryContext parent, + const char *name); + /* * Recommended default alloc parameters, suitable for "ordinary" contexts * that might hold quite a lot of data. diff --git a/src/include/utils/memutils_internal.h b/src/include/utils/memutils_internal.h index 475e91b336b..55af20c2e0c 100644 --- a/src/include/utils/memutils_internal.h +++ b/src/include/utils/memutils_internal.h @@ -93,6 +93,22 @@ extern void BumpStats(MemoryContext context, MemoryStatsPrintFunc printfunc, bool print_to_stderr); #ifdef MEMORY_CONTEXT_CHECKING extern void BumpCheck(MemoryContext context); +#endif + + /* These functions implement the MemoryContext API for the Proxy context. */ +extern void *ProxyAlloc(MemoryContext context, Size size, int flags); +extern void ProxyFree(void *pointer); +extern void *ProxyRealloc(void *pointer, Size size, int flags); +extern void ProxyReset(MemoryContext context); +extern void ProxyDelete(MemoryContext context); +extern MemoryContext ProxyGetChunkContext(void *pointer); +extern Size ProxyGetChunkSpace(void *pointer); +extern bool ProxyIsEmpty(MemoryContext context); +extern void ProxyStats(MemoryContext context, MemoryStatsPrintFunc printfunc, + void *passthru, MemoryContextCounters *totals, + bool print_to_stderr); +#ifdef MEMORY_CONTEXT_CHECKING +extern void ProxyCheck(MemoryContext context); #endif /* @@ -128,7 +144,7 @@ typedef enum MemoryContextMethodID MCTX_SLAB_ID, MCTX_ALIGNED_REDIRECT_ID, MCTX_BUMP_ID, - MCTX_8_UNUSED_ID, + MCTX_PROXY_ID, MCTX_9_UNUSED_ID, MCTX_10_UNUSED_ID, MCTX_11_UNUSED_ID, diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index c546b3d6375..b59184fcd84 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2455,6 +2455,8 @@ PropGraphLabelAndProperties PropGraphProperties PropGraphVertex ProtocolVersion +ProxyContext +ProxyChunk PrsStorage PruneFreezeParams PruneFreezeResult -- 2.50.1 (Apple Git-155)