From 1d8206dad31dd9b0e17aff0f9367c89f192cfc04 Mon Sep 17 00:00:00 2001
From: Shi Yu <shiy.fnst@fujitsu.com>
Date: Sun, 19 Feb 2023 10:23:38 +0800
Subject: [PATCH v1] Unregister syscache callback and relcache callback in
 pgoutput.

Relcache callback and syscache callbacks are registered when initializing
pgoutput, but they won't be unregistered when pgoutput shutdown. This results in
exceeding MAX_RELCACHE_CALLBACKS/MAX_SYSCACHE_CALLBACKS. Fix it by adding two
functions to unregister relcache callbacks and syscache callbacks.
---
 src/backend/replication/pgoutput/pgoutput.c | 23 ++---
 src/backend/utils/cache/inval.c             | 96 +++++++++++++++++++++
 src/include/utils/inval.h                   |  7 ++
 3 files changed, 112 insertions(+), 14 deletions(-)

diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 98377c094b..2db4e705de 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1755,6 +1755,13 @@ pgoutput_shutdown(LogicalDecodingContext *ctx)
 	{
 		hash_destroy(RelationSyncCache);
 		RelationSyncCache = NULL;
+
+		/* Unregister callbacks. */
+		CacheUnregisterSyscacheCallback(PUBLICATIONOID, publication_invalidation_cb, (Datum) 0);
+		CacheUnregisterSyscacheCallback(NAMESPACEOID, rel_sync_cache_publication_cb, (Datum) 0);
+		CacheUnregisterSyscacheCallback(PUBLICATIONRELMAP, rel_sync_cache_publication_cb, (Datum) 0);
+		CacheUnregisterSyscacheCallback(PUBLICATIONNAMESPACEMAP, rel_sync_cache_publication_cb, (Datum) 0);
+		CacheUnregisterRelcacheCallback(rel_sync_cache_relation_cb, (Datum) 0);
 	}
 }
 
@@ -2314,13 +2321,7 @@ rel_sync_cache_relation_cb(Datum arg, Oid relid)
 {
 	RelationSyncEntry *entry;
 
-	/*
-	 * We can get here if the plugin was used in SQL interface as the
-	 * RelSchemaSyncCache is destroyed when the decoding finishes, but there
-	 * is no way to unregister the relcache invalidation callback.
-	 */
-	if (RelationSyncCache == NULL)
-		return;
+	Assert(RelationSyncCache != NULL);
 
 	/*
 	 * Nobody keeps pointers to entries in this hash table around outside
@@ -2366,13 +2367,7 @@ rel_sync_cache_publication_cb(Datum arg, int cacheid, uint32 hashvalue)
 	HASH_SEQ_STATUS status;
 	RelationSyncEntry *entry;
 
-	/*
-	 * We can get here if the plugin was used in SQL interface as the
-	 * RelSchemaSyncCache is destroyed when the decoding finishes, but there
-	 * is no way to unregister the invalidation callbacks.
-	 */
-	if (RelationSyncCache == NULL)
-		return;
+	Assert(RelationSyncCache != NULL);
 
 	/*
 	 * We have no easy way to identify which cache entries this invalidation
diff --git a/src/backend/utils/cache/inval.c b/src/backend/utils/cache/inval.c
index 0008826f67..7e5c564a9c 100644
--- a/src/backend/utils/cache/inval.c
+++ b/src/backend/utils/cache/inval.c
@@ -1548,6 +1548,76 @@ CacheRegisterSyscacheCallback(int cacheid,
 	++syscache_callback_count;
 }
 
+/*
+ * CacheUnregisterSyscacheCallback
+ *		Unregister a syscache callback.
+ */
+void
+CacheUnregisterSyscacheCallback(int cacheid,
+								SyscacheCallbackFunction func,
+								Datum arg)
+{
+	int			i;
+	int			pre = -1;
+	int			pre_cacheid;
+	struct SYSCACHECALLBACK *ccitem;
+
+	if (cacheid < 0 || cacheid >= SysCacheSize)
+		elog(ERROR, "invalid cache ID: %d", cacheid);
+
+	/* Find the target callback and remove it in the linked list. */
+	i = syscache_callback_links[cacheid] - 1;
+	while (i >= 0)
+	{
+		ccitem = syscache_callback_list + i;
+
+		Assert(ccitem->id == cacheid);
+		if (ccitem->function == func && ccitem->arg == arg)
+		{
+			if (pre == -1)
+				syscache_callback_links[cacheid] = ccitem->link;
+			else
+				syscache_callback_list[pre].link = ccitem->link;
+			break;
+		}
+		pre = i;
+		i = ccitem->link - 1;
+	}
+
+	/* Fail to find the target callback. */
+	if (i == -1)
+		return;
+
+	syscache_callback_count--;
+
+	/*
+	 * If the one removed was not the last one in the array, move the last one
+	 * to the vacant place.
+	 */
+	if (i == syscache_callback_count)
+		return;
+	syscache_callback_list[i] = syscache_callback_list[syscache_callback_count];
+	pre_cacheid = syscache_callback_list[i].id;
+	if (syscache_callback_links[pre_cacheid] == syscache_callback_count + 1)
+		syscache_callback_links[pre_cacheid] = i + 1;
+	else
+	{
+		int			j = syscache_callback_links[pre_cacheid] - 1;
+		while (syscache_callback_list[j].link > 0)
+		{
+			if (syscache_callback_list[j].link == syscache_callback_count + 1)
+			{
+				syscache_callback_list[j].link = i + 1;
+				break;
+			}
+			j = syscache_callback_list[j].link - 1;
+		}
+
+		if (syscache_callback_list[j].link == 0)
+			elog(ERROR, "fail to find the Syscache Callback in the linked list.");
+	}
+}
+
 /*
  * CacheRegisterRelcacheCallback
  *		Register the specified function to be called for all future
@@ -1570,6 +1640,32 @@ CacheRegisterRelcacheCallback(RelcacheCallbackFunction func,
 	++relcache_callback_count;
 }
 
+/*
+ * CacheUnRegisterRelcacheCallback
+ *		Unregister a relcache callback.
+ */
+void
+CacheUnregisterRelcacheCallback(RelcacheCallbackFunction func,
+								Datum arg)
+{
+	int			i;
+	for (i = 0; i < relcache_callback_count; i++)
+	{
+		struct RELCACHECALLBACK *ccitem = relcache_callback_list + i;
+
+		if (ccitem->function == func && ccitem->arg == arg)
+			break;
+	}
+
+	/* Fail to find the target callback. */
+	if (i == relcache_callback_count)
+		return;
+
+	relcache_callback_count--;
+	for (; i < relcache_callback_count; i++)
+		relcache_callback_list[i] = relcache_callback_list[i + 1];
+}
+
 /*
  * CallSyscacheCallbacks
  *
diff --git a/src/include/utils/inval.h b/src/include/utils/inval.h
index 14b4eac063..eda2fe197b 100644
--- a/src/include/utils/inval.h
+++ b/src/include/utils/inval.h
@@ -56,9 +56,16 @@ extern void CacheRegisterSyscacheCallback(int cacheid,
 										  SyscacheCallbackFunction func,
 										  Datum arg);
 
+extern void CacheUnregisterSyscacheCallback(int cacheid,
+											SyscacheCallbackFunction func,
+											Datum arg);
+
 extern void CacheRegisterRelcacheCallback(RelcacheCallbackFunction func,
 										  Datum arg);
 
+extern void CacheUnregisterRelcacheCallback(RelcacheCallbackFunction func,
+											Datum arg);
+
 extern void CallSyscacheCallbacks(int cacheid, uint32 hashvalue);
 
 extern void InvalidateSystemCaches(void);
-- 
2.31.1

