From f41601e0df8e05e3e7d92aa7708f6e0f8ecfc034 Mon Sep 17 00:00:00 2001
From: Vignesh <vignesh21@gmail.com>
Date: Thu, 26 Dec 2024 15:11:09 +0530
Subject: [PATCH v3_PG14] Fix memory leak in pgoutput relation attribute map

The pgoutput module caches relation attribute map and frees it upon
invalidation.  However, this was not getting freed incase of SQL functions like
pg_logical_slot_{get,peek}_changes() which would bloat its cache memory usage.
Every pg_logical_slot_{get,peek}_changes() call for changes on partition table
creates more bloat. To address this, this commit adds a new memory context
within the logical decoding context. This ensures that the lifespan of the
relation entry attribute map aligns with that of the logical decoding context.
The context is tracked with a static variable whose state is reset with a
MemoryContext reset callback attached to PGOutputData->context, so as ABI
compatibility is preserved in stable branches.
---
 src/backend/replication/pgoutput/pgoutput.c | 20 ++++++++++++++------
 1 file changed, 14 insertions(+), 6 deletions(-)

diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 9fd879ee0c..32263daa66 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -71,6 +71,7 @@ static bool in_streaming;
  * parent context is reset via a dedicated MemoryContextCallback.
  */
 static MemoryContext pubctx = NULL;
+static MemoryContext cachectx = NULL;
 
 static List *LoadPublications(List *pubnames);
 static void publication_invalidation_cb(Datum arg, int cacheid,
@@ -260,12 +261,13 @@ parse_output_parameters(List *options, PGOutputData *data)
 }
 
 /*
- * Callback of PGOutputData->context in charge of cleaning pubctx.
+ * Callback of PGOutputData->context in charge of cleaning pubctx and cachectx.
  */
 static void
-pgoutput_pubctx_reset_callback(void *arg)
+pgoutput_ctx_reset_callback(void *arg)
 {
 	pubctx = NULL;
+	cachectx = NULL;
 }
 
 /*
@@ -289,8 +291,13 @@ pgoutput_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
 								   "logical replication publication list context",
 								   ALLOCSET_SMALL_SIZES);
 
+	Assert(cachectx == NULL);
+	cachectx = AllocSetContextCreate(ctx->context,
+									 "logical replication cache context",
+									 ALLOCSET_SMALL_SIZES);
+
 	mcallback = palloc0(sizeof(MemoryContextCallback));
-	mcallback->func = pgoutput_pubctx_reset_callback;
+	mcallback->func = pgoutput_ctx_reset_callback;
 	MemoryContextRegisterResetCallback(ctx->context, mcallback);
 
 	ctx->output_plugin_private = data;
@@ -490,7 +497,7 @@ maybe_send_schema(LogicalDecodingContext *ctx,
 		MemoryContext oldctx;
 
 		/* Map must live as long as the session does. */
-		oldctx = MemoryContextSwitchTo(CacheMemoryContext);
+		oldctx = MemoryContextSwitchTo(cachectx);
 
 		/*
 		 * Make copies of the TupleDescs that will live as long as the map
@@ -812,8 +819,8 @@ pgoutput_origin_filter(LogicalDecodingContext *ctx,
 /*
  * Shutdown the output plugin.
  *
- * Note, we don't need to clean the data->context and pubctx as they are
- * child contexts of the ctx->context so they will be cleaned up by logical
+ * Note, we don't need to clean the data->context, pubctx, and cachectx as they
+ * are child contexts of the ctx->context so they will be cleaned up by logical
  * decoding machinery.
  */
 static void
@@ -827,6 +834,7 @@ pgoutput_shutdown(LogicalDecodingContext *ctx)
 
 	/* Better safe than sorry */
 	pubctx = NULL;
+	cachectx = NULL;
 }
 
 /*
-- 
2.43.0

