From e2e8efb6bb4b564463f2d892d45afc4cdeacabc4 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
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 <boekewurm+postgres@gmail.com>
---
 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 <limits.h>
+
+#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)

