Subject: Re: ResourceOwner refactoring

From: Heikki Linnakangas <[email-masked]>
Date: Tue, 21 Feb 2023 13:40:57 +0200

On 01/11/2022 17:42, Andres Freund wrote:
> On 2022-11-01 12:39:39 +0200, Heikki Linnakangas wrote:
>> If we do need to worry about release order, perhaps add a "priority" or
>> "phase" to each resource kind, and release them in priority order. We
>> already have before- and after-locks as phases, but we could generalize
>> that.
>>
>> However, I feel that trying to enforce a particular order moves the
>> goalposts. If we need that, let's add it as a separate patch later.
> 
> Like Robert, I think that the patch is moving the goalpost...

Ok, I added a release-priority mechanism to this. New patchset attached. 
I added it as a separate patch on top of the previous patches, as 
v13-0003-Release-resources-in-priority-order.patch, to make it easier to 
see what changed after the previous patchset version. But it should be 
squashed with patch 0002 before committing.

In this implementation, when you call ResourceOwnerRelease(), the 
array/hash table is sorted by phase and priority, and the callbacks are 
called in that order. To make that feasible, I had to add one limitation:

After you call ResourceOwnerRelease(RESOURCE_RELEASE_BEFORE_LOCKS), you 
cannot continue using the resource owner. You now get an error if you 
try to call ResourceOwnerRemember() or ResourceOwnerForget() after 
ResourceOwnerRelease(). Previously, it was allowed, but if you 
remembered any more before-locks resources after calling 
ResourceOwnerRelease(RESOURCE_RELEASE_BEFORE_LOCKS), you had to be 
careful to release them manually, or you hit an assertion failure when 
deleting the ResourceOwner.

We did that sometimes with relcache references in AbortSubTransaction(), 
in AtEOSubXact_Inval(). Regression tests caught that case. I worked 
around that in AbortSubTransaction() by switching to the parent 
subxact's resource owner between the phases. Other end-of-transaction 
routines also perform some tasks between the phases, but AFAICS they 
don't try to remember any extra resources. It's a bit hard to tell, 
though, there's a lot going on in AtEOXact_Inval().

A more general fix would be to have special ResourceOwner for use at 
end-of-transaction, similar to the TransactionAbortContext memory 
context. But in general, end-of-transaction activities should be kept 
simple, especially between the release phases, so I feel that having to 
remember extra resources there is a bad code smell and we shouldn't 
encourage that. Perhaps there is a better fix or workaround for the 
known case in AbortSubTransaction(), too, that would avoid having to 
remember any resources there.

I added a test module in src/test/modules/test_resowner to test those cases.

Also, I changed the ReleaseResource callback's contract so that the 
callback no longer needs to call ResourceOwnerForget. That required some 
changes in bufmgr.c and some other files, to avoid calling 
ResourceOwnerForget when the resource is released by the callback.

There are no changes to the performance-critical Remember/Forget 
codepaths, the performance is the same as before.

- Heikki
From 40b863ffeb0bbb776dc1b1ee013ff8f08b2e3b6a Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email-masked]>
Date: Mon, 31 Oct 2022 10:33:36 +0100
Subject: [PATCH v13 1/5] Move a few ResourceOwnerEnlarge() calls for safety
 and clarity.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

These are functions where quite a lot of things happen between the
ResourceOwnerEnlarge and ResourceOwnerRemember calls. It's important that
there are no unrelated ResourceOwnerRemember() calls in the code in
between, otherwise the entry reserved by the ResourceOwnerEnlarge() call
might be used up by the intervening ResourceOwnerRemember() and not be
available at the intended ResourceOwnerRemember() call anymore. The longer
the code path between them is, the harder it is to verify that.

In bufmgr.c, there is a function similar to ResourceOwnerEnlarge(), to
ensure that the private refcount array has enough space. The
ReservePrivateRefCountEntry() calls, analogous to ResourceOwnerEnlarge(),
were made at different places than the ResourceOwnerEnlarge() calls. Move
the ResourceOwnerEnlarge() calls together with the
ReservePrivateRefCountEntry() calls for consistency.

Reviewed-by: Aleksander Alekseev, Michael Paquier, Julien Rouhaud, Kyotaro Horiguchi, Hayato Kuroda, Álvaro Herrera, Zhihong Yu
Discussion: https://www.postgresql.org/message-id/cbfabeb0-cd3c-e951-a572-19b365ed314d%40iki.fi
---
 src/backend/storage/buffer/bufmgr.c   | 39 +++++++++++----------------
 src/backend/storage/buffer/localbuf.c |  3 +++
 src/backend/utils/cache/catcache.c    | 13 ++++++---
 3 files changed, 28 insertions(+), 27 deletions(-)

diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 98904a7c05a..a7a829fad46 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -822,9 +822,6 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 
 	*hit = false;
 
-	/* Make sure we will have room to remember the buffer pin */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
-
 	isExtend = (blockNum == P_NEW);
 
 	TRACE_POSTGRESQL_BUFFER_READ_START(forkNum, blockNum,
@@ -1218,9 +1215,11 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 	{
 		/*
 		 * Ensure, while the spinlock's not yet held, that there's a free
-		 * refcount entry.
+		 * refcount entry and that the resource owner has room to remember the
+		 * pin.
 		 */
 		ReservePrivateRefCountEntry();
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
 
 		/*
 		 * Select a victim buffer.  The buffer is returned with its header
@@ -1743,8 +1742,6 @@ ReleaseAndReadBuffer(Buffer buffer,
  * taking the buffer header lock; instead update the state variable in loop of
  * CAS operations. Hopefully it's just a single CAS.
  *
- * Note that ResourceOwnerEnlargeBuffers must have been done already.
- *
  * Returns true if buffer is BM_VALID, else false.  This provision allows
  * some callers to avoid an extra spinlock cycle.
  */
@@ -1755,6 +1752,8 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
 	bool		result;
 	PrivateRefCountEntry *ref;
 
+	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+
 	ref = GetPrivateRefCountEntry(b, true);
 
 	if (ref == NULL)
@@ -1835,7 +1834,8 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
  * The spinlock is released before return.
  *
  * As this function is called with the spinlock held, the caller has to
- * previously call ReservePrivateRefCountEntry().
+ * previously call ReservePrivateRefCountEntry() and
+ * ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
  *
  * Currently, no callers of this function want to modify the buffer's
  * usage_count at all, so there's no need for a strategy parameter.
@@ -2008,9 +2008,6 @@ BufferSync(int flags)
 	int			mask = BM_DIRTY;
 	WritebackContext wb_context;
 
-	/* Make sure we can handle the pin inside SyncOneBuffer */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
-
 	/*
 	 * Unless this is a shutdown checkpoint or we have been explicitly told,
 	 * we write only permanent, dirty buffers.  But at shutdown or end of
@@ -2484,9 +2481,6 @@ BgBufferSync(WritebackContext *wb_context)
 	 * requirements, or hit the bgwriter_lru_maxpages limit.
 	 */
 
-	/* Make sure we can handle the pin inside SyncOneBuffer */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
-
 	num_to_scan = bufs_to_lap;
 	num_written = 0;
 	reusable_buffers = reusable_buffers_est;
@@ -2568,8 +2562,6 @@ BgBufferSync(WritebackContext *wb_context)
  *
  * (BUF_WRITTEN could be set in error if FlushBuffer finds the buffer clean
  * after locking it, but we don't care all that much.)
- *
- * Note: caller must have done ResourceOwnerEnlargeBuffers.
  */
 static int
 SyncOneBuffer(int buf_id, bool skip_recently_used, WritebackContext *wb_context)
@@ -2579,7 +2571,9 @@ SyncOneBuffer(int buf_id, bool skip_recently_used, WritebackContext *wb_context)
 	uint32		buf_state;
 	BufferTag	tag;
 
+	/* Make sure we can handle the pin */
 	ReservePrivateRefCountEntry();
+	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
 
 	/*
 	 * Check whether buffer needs writing.
@@ -3635,9 +3629,6 @@ FlushRelationBuffers(Relation rel)
 		return;
 	}
 
-	/* Make sure we can handle the pin inside the loop */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
-
 	for (i = 0; i < NBuffers; i++)
 	{
 		uint32		buf_state;
@@ -3651,7 +3642,9 @@ FlushRelationBuffers(Relation rel)
 		if (!BufTagMatchesRelFileLocator(&bufHdr->tag, &rel->rd_locator))
 			continue;
 
+		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (BufTagMatchesRelFileLocator(&bufHdr->tag, &rel->rd_locator) &&
@@ -3708,9 +3701,6 @@ FlushRelationsAllBuffers(SMgrRelation *smgrs, int nrels)
 	if (use_bsearch)
 		pg_qsort(srels, nrels, sizeof(SMgrSortArray), rlocator_comparator);
 
-	/* Make sure we can handle the pin inside the loop */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
-
 	for (i = 0; i < NBuffers; i++)
 	{
 		SMgrSortArray *srelent = NULL;
@@ -3749,7 +3739,9 @@ FlushRelationsAllBuffers(SMgrRelation *smgrs, int nrels)
 		if (srelent == NULL)
 			continue;
 
+		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (BufTagMatchesRelFileLocator(&bufHdr->tag, &srelent->rlocator) &&
@@ -3943,9 +3935,6 @@ FlushDatabaseBuffers(Oid dbid)
 	int			i;
 	BufferDesc *bufHdr;
 
-	/* Make sure we can handle the pin inside the loop */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
-
 	for (i = 0; i < NBuffers; i++)
 	{
 		uint32		buf_state;
@@ -3959,7 +3948,9 @@ FlushDatabaseBuffers(Oid dbid)
 		if (bufHdr->tag.dbOid != dbid)
 			continue;
 
+		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (bufHdr->tag.dbOid == dbid &&
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index 5325ddb663d..bba6640aa68 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -124,6 +124,9 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
 	if (LocalBufHash == NULL)
 		InitLocalBuffers();
 
+	/* Make sure we will have room to remember the buffer pin */
+	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+
 	/* See if the desired buffer already exists */
 	hresult = (LocalBufferLookupEnt *)
 		hash_search(LocalBufHash, &newTag, HASH_FIND, NULL);
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 4510031fe69..9ef8b16a023 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1602,8 +1602,6 @@ SearchCatCacheList(CatCache *cache,
 	 * block to ensure we can undo those refcounts if we get an error before
 	 * we finish constructing the CatCList.
 	 */
-	ResourceOwnerEnlargeCatCacheListRefs(CurrentResourceOwner);
-
 	ctlist = NIL;
 
 	PG_TRY();
@@ -1691,13 +1689,22 @@ SearchCatCacheList(CatCache *cache,
 
 		table_close(relation, AccessShareLock);
 
+		/* Make sure the resource owner has room to remember this entry. */
+		ResourceOwnerEnlargeCatCacheListRefs(CurrentResourceOwner);
+
 		/* Now we can build the CatCList entry. */
 		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
 		nmembers = list_length(ctlist);
 		cl = (CatCList *)
 			palloc(offsetof(CatCList, members) + nmembers * sizeof(CatCTup *));
 
-		/* Extract key values */
+		/*
+		 * Extract key values.
+		 *
+		 * XXX: If we run out of memory while copying the key values, we will
+		 * leak any allocations we had already made in the CacheMemoryContext.
+		 * That is unlikely enough that we just accept the risk.
+		 */
 		CatCacheCopyKeys(cache->cc_tupdesc, nkeys, cache->cc_keyno,
 						 arguments, cl->keys);
 		MemoryContextSwitchTo(oldcxt);
-- 
2.30.2

From a2d3409bbc84ffff6be9f366cf1b39488bb76b22 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email-masked]>
Date: Mon, 31 Oct 2022 10:48:57 +0100
Subject: [PATCH v13 2/5] Make resowners more easily extensible.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Instead of having a separate array/hash for each resource kind, use a
single array and hash to hold all kinds of resources. This makes it
possible to introduce new resource "kinds" without having to modify the
ResourceOwnerData struct. In particular, this makes it possible for
extensions to register custom resource kinds.

The old approach was to have a small array of resources of each kind, and
if it fills up, switch to a hash table. The new approach also uses an
array and a hash, but now the array and a hash are used at the same time.
The array is used to hold the recently added resources, and when it fills
up, they are moved to the hash. This keeps the access to recent entries
fast, even when there are a lot of long-held resources.

All the resource-specific ResourceOwnerEnlarge*(), ResourceOwnerRemember*(),
and ResourceOwnerForget*() functions have been replaced with three generic
functions that take resource kind as argument. For convenience, we still
define resource-specific wrapper macros around the generic functions, with
the same old names, but they are now defined in the source files that use
those resource kinds.

Reviewed-by: Aleksander Alekseev, Michael Paquier, Julien Rouhaud, Kyotaro Horiguchi, Hayato Kuroda, Álvaro Herrera, Zhihong Yu
Discussion: https://www.postgresql.org/message-id/cbfabeb0-cd3c-e951-a572-19b365ed314d%40iki.fi
---
 src/backend/access/common/tupdesc.c   |   42 +-
 src/backend/jit/jit.c                 |    2 -
 src/backend/jit/llvm/llvmjit.c        |   45 +-
 src/backend/storage/buffer/bufmgr.c   |   47 +-
 src/backend/storage/buffer/localbuf.c |    4 +-
 src/backend/storage/file/fd.c         |   44 +-
 src/backend/storage/ipc/dsm.c         |   44 +-
 src/backend/storage/lmgr/lock.c       |    2 +-
 src/backend/utils/cache/catcache.c    |   74 +-
 src/backend/utils/cache/plancache.c   |   45 +-
 src/backend/utils/cache/relcache.c    |   41 +-
 src/backend/utils/resowner/README     |   18 +-
 src/backend/utils/resowner/resowner.c | 1327 +++++++------------------
 src/backend/utils/time/snapmgr.c      |   39 +-
 src/common/cryptohash_openssl.c       |   48 +-
 src/common/hmac_openssl.c             |   46 +-
 src/include/storage/buf_internals.h   |    9 +
 src/include/utils/catcache.h          |    3 -
 src/include/utils/plancache.h         |    2 +
 src/include/utils/resowner.h          |   45 +-
 src/include/utils/resowner_private.h  |  112 ---
 src/tools/pgindent/typedefs.list      |    4 +-
 22 files changed, 913 insertions(+), 1130 deletions(-)
 delete mode 100644 src/include/utils/resowner_private.h

diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 72a2c3d3db6..a4b830d307f 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -30,9 +30,27 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 #include "utils/syscache.h"
 
+/* ResourceOwner callbacks to hold tupledesc references  */
+static void ResOwnerReleaseTupleDesc(Datum res);
+static void ResOwnerPrintTupleDescLeakWarning(Datum res);
+
+static ResourceOwnerFuncs tupdesc_resowner_funcs =
+{
+	/* relcache references */
+	.name = "tupdesc reference",
+	.phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.ReleaseResource = ResOwnerReleaseTupleDesc,
+	.PrintLeakWarning = ResOwnerPrintTupleDescLeakWarning
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+#define ResourceOwnerRememberTupleDesc(owner, tupdesc) \
+	ResourceOwnerRemember(owner, PointerGetDatum(tupdesc), &tupdesc_resowner_funcs)
+#define ResourceOwnerForgetTupleDesc(owner, tupdesc) \
+	ResourceOwnerForget(owner, PointerGetDatum(tupdesc), &tupdesc_resowner_funcs)
 
 /*
  * CreateTemplateTupleDesc
@@ -367,7 +385,7 @@ IncrTupleDescRefCount(TupleDesc tupdesc)
 {
 	Assert(tupdesc->tdrefcount >= 0);
 
-	ResourceOwnerEnlargeTupleDescs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	tupdesc->tdrefcount++;
 	ResourceOwnerRememberTupleDesc(CurrentResourceOwner, tupdesc);
 }
@@ -919,3 +937,23 @@ BuildDescFromLists(List *names, List *types, List *typmods, List *collations)
 
 	return desc;
 }
+
+
+/*
+ * ResourceOwner callbacks
+ */
+static void
+ResOwnerReleaseTupleDesc(Datum res)
+{
+	DecrTupleDescRefCount((TupleDesc) DatumGetPointer(res));
+}
+
+static void
+ResOwnerPrintTupleDescLeakWarning(Datum res)
+{
+	TupleDesc	tupdesc = (TupleDesc) DatumGetPointer(res);
+
+	elog(WARNING,
+		 "TupleDesc reference leak: TupleDesc %p (%u,%d) still referenced",
+		 tupdesc, tupdesc->tdtypeid, tupdesc->tdtypmod);
+}
diff --git a/src/backend/jit/jit.c b/src/backend/jit/jit.c
index fd1cf184c8e..58aff8e30cc 100644
--- a/src/backend/jit/jit.c
+++ b/src/backend/jit/jit.c
@@ -26,7 +26,6 @@
 #include "jit/jit.h"
 #include "miscadmin.h"
 #include "utils/fmgrprotos.h"
-#include "utils/resowner_private.h"
 
 /* GUCs */
 bool		jit_enabled = true;
@@ -140,7 +139,6 @@ jit_release_context(JitContext *context)
 	if (provider_successfully_loaded)
 		provider.release_context(context);
 
-	ResourceOwnerForgetJIT(context->resowner, PointerGetDatum(context));
 	pfree(context);
 }
 
diff --git a/src/backend/jit/llvm/llvmjit.c b/src/backend/jit/llvm/llvmjit.c
index 312612115ca..ab9b8fcfde9 100644
--- a/src/backend/jit/llvm/llvmjit.c
+++ b/src/backend/jit/llvm/llvmjit.c
@@ -40,7 +40,7 @@
 #include "portability/instr_time.h"
 #include "storage/ipc.h"
 #include "utils/memutils.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 
 /* Handle of a module emitted via ORC JIT */
 typedef struct LLVMJitHandle
@@ -121,6 +121,25 @@ static LLVMOrcLLJITRef llvm_create_jit_instance(LLVMTargetMachineRef tm);
 static char *llvm_error_message(LLVMErrorRef error);
 #endif							/* LLVM_VERSION_MAJOR > 11 */
 
+/* ResourceOwner callbacks to hold JitContexts  */
+static void ResOwnerReleaseJitContext(Datum res);
+static void ResOwnerPrintJitContextLeakWarning(Datum res);
+
+static ResourceOwnerFuncs jit_resowner_funcs =
+{
+	/* relcache references */
+	.name = "LLVM JIT context",
+	.phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.ReleaseResource = ResOwnerReleaseJitContext,
+	.PrintLeakWarning = ResOwnerPrintJitContextLeakWarning
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+#define ResourceOwnerRememberJIT(owner, handle) \
+	ResourceOwnerRemember(owner, PointerGetDatum(handle), &jit_resowner_funcs)
+#define ResourceOwnerForgetJIT(owner, handle) \
+	ResourceOwnerForget(owner, PointerGetDatum(handle), &jit_resowner_funcs)
+
 PG_MODULE_MAGIC;

@@ -151,7 +170,7 @@ llvm_create_context(int jitFlags)
 
 	llvm_session_initialize();
 
-	ResourceOwnerEnlargeJIT(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	context = MemoryContextAllocZero(TopMemoryContext,
 									 sizeof(LLVMJitContext));
@@ -159,7 +178,7 @@ llvm_create_context(int jitFlags)
 
 	/* ensure cleanup */
 	context->base.resowner = CurrentResourceOwner;
-	ResourceOwnerRememberJIT(CurrentResourceOwner, PointerGetDatum(context));
+	ResourceOwnerRememberJIT(CurrentResourceOwner, context);
 
 	return context;
 }
@@ -221,6 +240,8 @@ llvm_release_context(JitContext *context)
 	}
 	list_free(llvm_context->handles);
 	llvm_context->handles = NIL;
+
+	ResourceOwnerForgetJIT(context->resowner, context);
 }
 
 /*
@@ -1266,3 +1287,21 @@ llvm_error_message(LLVMErrorRef error)
 }
 
 #endif							/* LLVM_VERSION_MAJOR > 11 */
+
+/*
+ * ResourceOwner callbacks
+ */
+static void
+ResOwnerReleaseJitContext(Datum res)
+{
+	jit_release_context((JitContext *) DatumGetPointer(res));
+}
+
+static void
+ResOwnerPrintJitContextLeakWarning(Datum res)
+{
+	/* XXX: We used to not print these. Was that intentional? */
+	JitContext *context = (JitContext *) DatumGetPointer(res);
+
+	elog(WARNING, "JIT context leak: context %p still referenced", context);
+}
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index a7a829fad46..8fb05a72fa0 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -54,7 +54,7 @@
 #include "utils/memdebug.h"
 #include "utils/ps_status.h"
 #include "utils/rel.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 #include "utils/timestamp.h"

@@ -208,6 +208,18 @@ static PrivateRefCountEntry *GetPrivateRefCountEntry(Buffer buffer, bool do_move
 static inline int32 GetPrivateRefCount(Buffer buffer);
 static void ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref);
 
+/* ResourceOwner callbacks to hold buffer pins */
+static void ResOwnerReleaseBuffer(Datum res);
+static void ResOwnerPrintBufferLeakWarning(Datum res);
+
+ResourceOwnerFuncs buffer_resowner_funcs =
+{
+	.name = "buffer",
+	.phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.ReleaseResource = ResOwnerReleaseBuffer,
+	.PrintLeakWarning = ResOwnerPrintBufferLeakWarning
+};
+
 /*
  * Ensure that the PrivateRefCountArray has sufficient space to store one more
  * entry. This has to be called before using NewPrivateRefCountEntry() to fill
@@ -622,7 +634,7 @@ ReadRecentBuffer(RelFileLocator rlocator, ForkNumber forkNum, BlockNumber blockN
 
 	Assert(BufferIsValid(recent_buffer));
 
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	ReservePrivateRefCountEntry();
 	InitBufferTag(&tag, &rlocator, forkNum, blockNum);
 
@@ -1219,7 +1231,7 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 		 * pin.
 		 */
 		ReservePrivateRefCountEntry();
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		/*
 		 * Select a victim buffer.  The buffer is returned with its header
@@ -1752,7 +1764,7 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
 	bool		result;
 	PrivateRefCountEntry *ref;
 
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	ref = GetPrivateRefCountEntry(b, true);
 
@@ -1835,7 +1847,7 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
  *
  * As this function is called with the spinlock held, the caller has to
  * previously call ReservePrivateRefCountEntry() and
- * ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+ * ResourceOwnerEnlarge(CurrentResourceOwner);
  *
  * Currently, no callers of this function want to modify the buffer's
  * usage_count at all, so there's no need for a strategy parameter.
@@ -2573,7 +2585,7 @@ SyncOneBuffer(int buf_id, bool skip_recently_used, WritebackContext *wb_context)
 
 	/* Make sure we can handle the pin */
 	ReservePrivateRefCountEntry();
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	/*
 	 * Check whether buffer needs writing.
@@ -3644,7 +3656,7 @@ FlushRelationBuffers(Relation rel)
 
 		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (BufTagMatchesRelFileLocator(&bufHdr->tag, &rel->rd_locator) &&
@@ -3741,7 +3753,7 @@ FlushRelationsAllBuffers(SMgrRelation *smgrs, int nrels)
 
 		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (BufTagMatchesRelFileLocator(&bufHdr->tag, &srelent->rlocator) &&
@@ -3950,7 +3962,7 @@ FlushDatabaseBuffers(Oid dbid)
 
 		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (bufHdr->tag.dbOid == dbid &&
@@ -4033,7 +4045,7 @@ void
 IncrBufferRefCount(Buffer buffer)
 {
 	Assert(BufferIsPinned(buffer));
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	if (BufferIsLocal(buffer))
 		LocalRefCount[-buffer - 1]++;
 	else
@@ -5087,3 +5099,18 @@ TestForOldSnapshot_impl(Snapshot snapshot, Relation relation)
 				(errcode(ERRCODE_SNAPSHOT_TOO_OLD),
 				 errmsg("snapshot too old")));
 }
+
+/*
+ * ResourceOwner callbacks
+ */
+static void
+ResOwnerReleaseBuffer(Datum res)
+{
+	ReleaseBuffer(DatumGetInt32(res));
+}
+
+static void
+ResOwnerPrintBufferLeakWarning(Datum res)
+{
+	PrintBufferLeakWarning(DatumGetInt32(res));
+}
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index bba6640aa68..8e2ba6882a1 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -23,7 +23,7 @@
 #include "storage/bufmgr.h"
 #include "utils/guc_hooks.h"
 #include "utils/memutils.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"

 /*#define LBDEBUG*/
@@ -125,7 +125,7 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
 		InitLocalBuffers();
 
 	/* Make sure we will have room to remember the buffer pin */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	/* See if the desired buffer already exists */
 	hresult = (LocalBufferLookupEnt *)
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 926d000f2ea..7bd716542e7 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -98,7 +98,7 @@
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "utils/guc.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 
 /* Define PG_FLUSH_DATA_WORKS if we have an implementation for pg_flush_data */
 #if defined(HAVE_SYNC_FILE_RANGE)
@@ -349,6 +349,24 @@ static void unlink_if_exists_fname(const char *fname, bool isdir, int elevel);
 static int	fsync_parent_path(const char *fname, int elevel);

+/* ResourceOwner callbacks to hold virtual file descriptors */
+static void ResOwnerReleaseFile(Datum res);
+static void ResOwnerPrintFileLeakWarning(Datum res);
+
+static ResourceOwnerFuncs file_resowner_funcs =
+{
+	.name = "File",
+	.phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.ReleaseResource = ResOwnerReleaseFile,
+	.PrintLeakWarning = ResOwnerPrintFileLeakWarning
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+#define ResourceOwnerRememberFile(owner, file) \
+	ResourceOwnerRemember(owner, Int32GetDatum(file), &file_resowner_funcs)
+#define ResourceOwnerForgetFile(owner, file) \
+	ResourceOwnerForget(owner, Int32GetDatum(file), &file_resowner_funcs)
+
 /*
  * pg_fsync --- do fsync with or without writethrough
  */
@@ -1453,7 +1471,7 @@ ReportTemporaryFileUsage(const char *path, off_t size)
 
 /*
  * Called to register a temporary file for automatic close.
- * ResourceOwnerEnlargeFiles(CurrentResourceOwner) must have been called
+ * ResourceOwnerEnlarge(CurrentResourceOwner) must have been called
  * before the file was opened.
  */
 static void
@@ -1637,7 +1655,7 @@ OpenTemporaryFile(bool interXact)
 	 * open it, if we'll be registering it below.
 	 */
 	if (!interXact)
-		ResourceOwnerEnlargeFiles(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	/*
 	 * If some temp tablespace(s) have been given to us, try to use the next
@@ -1769,7 +1787,7 @@ PathNameCreateTemporaryFile(const char *path, bool error_on_failure)
 
 	Assert(temporary_files_allowed);	/* check temp file access is up */
 
-	ResourceOwnerEnlargeFiles(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	/*
 	 * Open the file.  Note: we don't use O_EXCL, in case there is an orphaned
@@ -1809,7 +1827,7 @@ PathNameOpenTemporaryFile(const char *path, int mode)
 
 	Assert(temporary_files_allowed);	/* check temp file access is up */
 
-	ResourceOwnerEnlargeFiles(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	file = PathNameOpenFile(path, mode | PG_BINARY);
 
@@ -3737,3 +3755,19 @@ data_sync_elevel(int elevel)
 {
 	return data_sync_retry ? elevel : PANIC;
 }
+
+/*
+ * ResourceOwner callbacks
+ */
+static void
+ResOwnerReleaseFile(Datum res)
+{
+	FileClose((File) DatumGetInt32(res));
+}
+
+static void
+ResOwnerPrintFileLeakWarning(Datum res)
+{
+	elog(WARNING, "temporary file leak: File %d still referenced",
+		 DatumGetInt32(res));
+}
diff --git a/src/backend/storage/ipc/dsm.c b/src/backend/storage/ipc/dsm.c
index 10b029bb162..61927f7ccc0 100644
--- a/src/backend/storage/ipc/dsm.c
+++ b/src/backend/storage/ipc/dsm.c
@@ -38,13 +38,15 @@
 #include "miscadmin.h"
 #include "port/pg_bitutils.h"
 #include "storage/dsm.h"
+#include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/lwlock.h"
 #include "storage/pg_shmem.h"
+#include "storage/shmem.h"
 #include "utils/freepage.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 
 #define PG_DYNSHMEM_CONTROL_MAGIC		0x9a503d32
 
@@ -140,6 +142,25 @@ static dsm_control_header *dsm_control;
 static Size dsm_control_mapped_size = 0;
 static void *dsm_control_impl_private = NULL;
 
+
+/* ResourceOwner callbacks to hold DSM segments */
+static void ResOwnerReleaseDSM(Datum res);
+static void ResOwnerPrintDSMLeakWarning(Datum res);
+
+static ResourceOwnerFuncs dsm_resowner_funcs =
+{
+	.name = "dynamic shared memory segment",
+	.phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.ReleaseResource = ResOwnerReleaseDSM,
+	.PrintLeakWarning = ResOwnerPrintDSMLeakWarning
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+#define ResourceOwnerRememberDSM(owner, seg) \
+	ResourceOwnerRemember(owner, PointerGetDatum(seg), &dsm_resowner_funcs)
+#define ResourceOwnerForgetDSM(owner, seg) \
+	ResourceOwnerForget(owner, PointerGetDatum(seg), &dsm_resowner_funcs)
+
 /*
  * Start up the dynamic shared memory system.
  *
@@ -907,7 +928,7 @@ void
 dsm_unpin_mapping(dsm_segment *seg)
 {
 	Assert(seg->resowner == NULL);
-	ResourceOwnerEnlargeDSMs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	seg->resowner = CurrentResourceOwner;
 	ResourceOwnerRememberDSM(seg->resowner, seg);
 }
@@ -1174,7 +1195,7 @@ dsm_create_descriptor(void)
 	dsm_segment *seg;
 
 	if (CurrentResourceOwner)
-		ResourceOwnerEnlargeDSMs(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	seg = MemoryContextAlloc(TopMemoryContext, sizeof(dsm_segment));
 	dlist_push_head(&dsm_segment_list, &seg->node);
@@ -1253,3 +1274,20 @@ is_main_region_dsm_handle(dsm_handle handle)
 {
 	return handle & 1;
 }
+
+/*
+ * ResourceOwner callbacks
+ */
+static void
+ResOwnerReleaseDSM(Datum res)
+{
+	dsm_detach((dsm_segment *) DatumGetPointer(res));
+}
+static void
+ResOwnerPrintDSMLeakWarning(Datum res)
+{
+	dsm_segment *seg = (dsm_segment *) res;
+
+	elog(WARNING, "dynamic shared memory leak: segment %u still referenced",
+		 dsm_segment_handle(seg));
+}
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 42595b38b2c..d86f35e6a66 100644
--- a/src/backend/storage/lmgr/lock.c
+++ b/src/backend/storage/lmgr/lock.c
@@ -48,7 +48,7 @@
 #include "storage/standby.h"
 #include "utils/memutils.h"
 #include "utils/ps_status.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"

 /* This configuration variable is used to set the lock table size */
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 9ef8b16a023..af90d1e0134 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -31,12 +31,13 @@
 #endif
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
+#include "utils/catcache.h"
 #include "utils/datum.h"
 #include "utils/fmgroids.h"
 #include "utils/inval.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 #include "utils/syscache.h"

@@ -104,6 +105,42 @@ static void CatCacheCopyKeys(TupleDesc tupdesc, int nkeys, int *attnos,
  *					internal support functions
  */
 
+/* ResourceOwner callbacks to hold catcache references */
+
+static void ResOwnerReleaseCatCache(Datum res);
+static void ResOwnerPrintCatCacheLeakWarning(Datum res);
+static void ResOwnerReleaseCatCacheList(Datum res);
+static void ResOwnerPrintCatCacheListLeakWarning(Datum res);
+
+static ResourceOwnerFuncs catcache_resowner_funcs =
+{
+	/* catcache references */
+	.name = "catcache reference",
+	.phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.ReleaseResource = ResOwnerReleaseCatCache,
+	.PrintLeakWarning = ResOwnerPrintCatCacheLeakWarning
+};
+
+static ResourceOwnerFuncs catlistref_resowner_funcs =
+{
+	/* catcache-list pins */
+	.name = "catcache list reference",
+	.phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.ReleaseResource = ResOwnerReleaseCatCacheList,
+	.PrintLeakWarning = ResOwnerPrintCatCacheListLeakWarning
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+#define ResourceOwnerRememberCatCacheRef(owner, tuple) \
+	ResourceOwnerRemember(owner, PointerGetDatum(tuple), &catcache_resowner_funcs)
+#define ResourceOwnerForgetCatCacheRef(owner, tuple) \
+	ResourceOwnerForget(owner, PointerGetDatum(tuple), &catcache_resowner_funcs)
+#define ResourceOwnerRememberCatCacheListRef(owner, list) \
+	ResourceOwnerRemember(owner, PointerGetDatum(list), &catlistref_resowner_funcs)
+#define ResourceOwnerForgetCatCacheListRef(owner, list) \
+	ResourceOwnerForget(owner, PointerGetDatum(list), &catlistref_resowner_funcs)
+
+
 /*
  * Hash and equality functions for system types that are used as cache key
  * fields.  In some cases, we just call the regular SQL-callable functions for
@@ -1265,7 +1302,7 @@ SearchCatCacheInternal(CatCache *cache,
 		 */
 		if (!ct->negative)
 		{
-			ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner);
+			ResourceOwnerEnlarge(CurrentResourceOwner);
 			ct->refcount++;
 			ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple);
 
@@ -1366,7 +1403,7 @@ SearchCatCacheMiss(CatCache *cache,
 									 hashValue, hashIndex,
 									 false);
 		/* immediately set the refcount to 1 */
-		ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 		ct->refcount++;
 		ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple);
 		break;					/* assume only one match */
@@ -1578,7 +1615,7 @@ SearchCatCacheList(CatCache *cache,
 		dlist_move_head(&cache->cc_lists, &cl->cache_elem);
 
 		/* Bump the list's refcount and return it */
-		ResourceOwnerEnlargeCatCacheListRefs(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 		cl->refcount++;
 		ResourceOwnerRememberCatCacheListRef(CurrentResourceOwner, cl);
 
@@ -1690,7 +1727,7 @@ SearchCatCacheList(CatCache *cache,
 		table_close(relation, AccessShareLock);
 
 		/* Make sure the resource owner has room to remember this entry. */
-		ResourceOwnerEnlargeCatCacheListRefs(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		/* Now we can build the CatCList entry. */
 		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
@@ -2062,14 +2099,19 @@ PrepareToInvalidateCacheTuple(Relation relation,
 	}
 }
 
-
 /*
- * Subroutines for warning about reference leaks.  These are exported so
- * that resowner.c can call them.
+ * ResourceOwner callbacks
  */
-void
-PrintCatCacheLeakWarning(HeapTuple tuple)
+static void
+ResOwnerReleaseCatCache(Datum res)
+{
+	ReleaseCatCache((HeapTuple) DatumGetPointer(res));
+}
+
+static void
+ResOwnerPrintCatCacheLeakWarning(Datum res)
 {
+	HeapTuple	tuple = (HeapTuple) DatumGetPointer(res);
 	CatCTup    *ct = (CatCTup *) (((char *) tuple) -
 								  offsetof(CatCTup, tuple));
 
@@ -2083,9 +2125,17 @@ PrintCatCacheLeakWarning(HeapTuple tuple)
 		 ct->refcount);
 }
 
-void
-PrintCatCacheListLeakWarning(CatCList *list)
+static void
+ResOwnerReleaseCatCacheList(Datum res)
 {
+	ReleaseCatCacheList((CatCList *) DatumGetPointer(res));
+}
+
+static void
+ResOwnerPrintCatCacheListLeakWarning(Datum res)
+{
+	CatCList   *list = (CatCList *) DatumGetPointer(res);
+
 	elog(WARNING, "cache reference leak: cache %s (%d), list %p has count %d",
 		 list->my_cache->cc_relname, list->my_cache->id,
 		 list, list->refcount);
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 77c2ba3f8f4..5fa486c5eb5 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -69,7 +69,7 @@
 #include "tcop/utility.h"
 #include "utils/inval.h"
 #include "utils/memutils.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -115,6 +115,26 @@ static void PlanCacheRelCallback(Datum arg, Oid relid);
 static void PlanCacheObjectCallback(Datum arg, int cacheid, uint32 hashvalue);
 static void PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue);
 
+/* ResourceOwner callbacks to track plancache references */
+static void ResOwnerReleaseCachedPlan(Datum res);
+static void ResOwnerPrintPlanCacheLeakWarning(Datum res);
+
+/* this is exported for ResourceOwnerReleaseAllPlanCacheRefs() */
+ResourceOwnerFuncs planref_resowner_funcs =
+{
+	.name = "plancache reference",
+	.phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.ReleaseResource = ResOwnerReleaseCachedPlan,
+	.PrintLeakWarning = ResOwnerPrintPlanCacheLeakWarning
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+#define ResourceOwnerRememberPlanCacheRef(owner, plan) \
+	ResourceOwnerRemember(owner, PointerGetDatum(plan), &planref_resowner_funcs)
+#define ResourceOwnerForgetPlanCacheRef(owner, plan) \
+	ResourceOwnerForget(owner, PointerGetDatum(plan), &planref_resowner_funcs)
+
+
 /* GUC parameter */
 int			plan_cache_mode = PLAN_CACHE_MODE_AUTO;
 
@@ -1229,7 +1249,7 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 
 	/* Flag the plan as in use by caller */
 	if (owner)
-		ResourceOwnerEnlargePlanCacheRefs(owner);
+		ResourceOwnerEnlarge(owner);
 	plan->refcount++;
 	if (owner)
 		ResourceOwnerRememberPlanCacheRef(owner, plan);
@@ -1392,7 +1412,7 @@ CachedPlanAllowsSimpleValidityCheck(CachedPlanSource *plansource,
 	/* Bump refcount if requested. */
 	if (owner)
 	{
-		ResourceOwnerEnlargePlanCacheRefs(owner);
+		ResourceOwnerEnlarge(owner);
 		plan->refcount++;
 		ResourceOwnerRememberPlanCacheRef(owner, plan);
 	}
@@ -1451,7 +1471,7 @@ CachedPlanIsSimplyValid(CachedPlanSource *plansource, CachedPlan *plan,
 	/* It's still good.  Bump refcount if requested. */
 	if (owner)
 	{
-		ResourceOwnerEnlargePlanCacheRefs(owner);
+		ResourceOwnerEnlarge(owner);
 		plan->refcount++;
 		ResourceOwnerRememberPlanCacheRef(owner, plan);
 	}
@@ -2206,3 +2226,20 @@ ResetPlanCache(void)
 		cexpr->is_valid = false;
 	}
 }
+
+/*
+ * ResourceOwner callbacks
+ */
+
+static void
+ResOwnerReleaseCachedPlan(Datum res)
+{
+	ReleaseCachedPlan((CachedPlan *) DatumGetPointer(res), CurrentResourceOwner);
+}
+
+static void
+ResOwnerPrintPlanCacheLeakWarning(Datum res)
+{
+	elog(WARNING, "plancache reference leak: plan %p not closed",
+		 DatumGetPointer(res));
+}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13f79873733..286155e20ac 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -80,13 +80,14 @@
 #include "storage/smgr.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
+#include "utils/catcache.h"
 #include "utils/datum.h"
 #include "utils/fmgroids.h"
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/relmapper.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 
@@ -2115,6 +2116,24 @@ RelationIdGetRelation(Oid relationId)
  * ----------------------------------------------------------------
  */
 
+/* ResourceOwner callbacks to track relcache references */
+static void ResOwnerReleaseRelation(Datum res);
+static void ResOwnerPrintRelCacheLeakWarning(Datum res);
+
+static ResourceOwnerFuncs relref_resowner_funcs =
+{
+	.name = "relcache reference",
+	.phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.ReleaseResource = ResOwnerReleaseRelation,
+	.PrintLeakWarning = ResOwnerPrintRelCacheLeakWarning
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+#define ResourceOwnerRememberRelationRef(owner, rel) \
+	ResourceOwnerRemember(owner, PointerGetDatum(rel), &relref_resowner_funcs)
+#define ResourceOwnerForgetRelationRef(owner, rel) \
+	ResourceOwnerForget(owner, PointerGetDatum(rel), &relref_resowner_funcs)
+
 /*
  * RelationIncrementReferenceCount
  *		Increments relation reference count.
@@ -2126,7 +2145,7 @@ RelationIdGetRelation(Oid relationId)
 void
 RelationIncrementReferenceCount(Relation rel)
 {
-	ResourceOwnerEnlargeRelationRefs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	rel->rd_refcnt += 1;
 	if (!IsBootstrapProcessingMode())
 		ResourceOwnerRememberRelationRef(CurrentResourceOwner, rel);
@@ -6783,3 +6802,21 @@ unlink_initfile(const char *initfilename, int elevel)
 							initfilename)));
 	}
 }
+
+/*
+ * ResourceOwner callbacks
+ */
+static void
+ResOwnerPrintRelCacheLeakWarning(Datum res)
+{
+	Relation	rel = (Relation) res;
+
+	elog(WARNING, "relcache reference leak: relation \"%s\" not closed",
+		 RelationGetRelationName(rel));
+}
+
+static void
+ResOwnerReleaseRelation(Datum res)
+{
+	RelationClose((Relation) res);
+}
diff --git a/src/backend/utils/resowner/README b/src/backend/utils/resowner/README
index f94c9700df4..e32f7a27384 100644
--- a/src/backend/utils/resowner/README
+++ b/src/backend/utils/resowner/README
@@ -54,12 +54,18 @@ The basic operations on a ResourceOwner are:
 * delete a ResourceOwner (including child owner objects); all resources
   must have been released beforehand
 
-This API directly supports the resource types listed in the definition of
-ResourceOwnerData struct in src/backend/utils/resowner/resowner.c.
-Other objects can be associated with a ResourceOwner by recording the address
-of the owning ResourceOwner in such an object.  There is an API for other
-modules to get control during ResourceOwner release, so that they can scan
-their own data structures to find the objects that need to be deleted.
+ResourceOwner can record ownership of many different kinds of resources.  In
+core PostgreSQL, it is used for buffer pins, lmgr locks, and catalog cache
+references, to name a few examples.  ResourceOwner treats all resources the
+same, and extensions can define new kinds of resources by filling in a
+ResourceOwnerFuncs struct with suitable callback functions.
+
+There is also an API for other modules to get control during ResourceOwner
+release, so that they can scan their own data structures to find the objects
+that need to be deleted.  See RegisterResourceReleaseCallback function.
+This used to be the only way for extensions to use the resource owner
+mechanism with new kinds of objects; nowadays it easier to write custom
+ResourceOwnerFuncs callbacks.
 
 Locks are handled specially because in non-error situations a lock should
 be held until end of transaction, even if it was originally taken by a
diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index 19b6241e45d..7e594e3f2e4 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -20,76 +20,45 @@
  */
 #include "postgres.h"
 
-#include "common/cryptohash.h"
 #include "common/hashfn.h"
-#include "common/hmac.h"
-#include "jit/jit.h"
-#include "storage/bufmgr.h"
 #include "storage/ipc.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
 #include "utils/memutils.h"
-#include "utils/rel.h"
-#include "utils/resowner_private.h"
-#include "utils/snapmgr.h"
-
-
-/*
- * All resource IDs managed by this code are required to fit into a Datum,
- * which is fine since they are generally pointers or integers.
- *
- * Provide Datum conversion macros for a couple of things that are really
- * just "int".
- */
-#define FileGetDatum(file) Int32GetDatum(file)
-#define DatumGetFile(datum) ((File) DatumGetInt32(datum))
-#define BufferGetDatum(buffer) Int32GetDatum(buffer)
-#define DatumGetBuffer(datum) ((Buffer) DatumGetInt32(datum))
+#include "utils/plancache.h"
+#include "utils/resowner.h"
 
 /*
- * ResourceArray is a common structure for storing all types of resource IDs.
- *
- * We manage small sets of resource IDs by keeping them in a simple array:
- * itemsarr[k] holds an ID, for 0 <= k < nitems <= maxitems = capacity.
- *
- * If a set grows large, we switch over to using open-addressing hashing.
- * Then, itemsarr[] is a hash table of "capacity" slots, with each
- * slot holding either an ID or "invalidval".  nitems is the number of valid
- * items present; if it would exceed maxitems, we enlarge the array and
- * re-hash.  In this mode, maxitems should be rather less than capacity so
- * that we don't waste too much time searching for empty slots.
+ * ResourceElem represents a reference associated with a resource owner.
  *
- * In either mode, lastidx remembers the location of the last item inserted
- * or returned by GetAny; this speeds up searches in ResourceArrayRemove.
+ * All objects managed by this code are required to fit into a Datum,
+ * which is fine since they are generally pointers or integers.
  */
-typedef struct ResourceArray
+typedef struct ResourceElem
 {
-	Datum	   *itemsarr;		/* buffer for storing values */
-	Datum		invalidval;		/* value that is considered invalid */
-	uint32		capacity;		/* allocated length of itemsarr[] */
-	uint32		nitems;			/* how many items are stored in items array */
-	uint32		maxitems;		/* current limit on nitems before enlarging */
-	uint32		lastidx;		/* index of last item returned by GetAny */
-} ResourceArray;
+	Datum		item;
+	ResourceOwnerFuncs *kind;	/* NULL indicates a free hash table slot */
+} ResourceElem;
 
 /*
- * Initially allocated size of a ResourceArray.  Must be power of two since
- * we'll use (arraysize - 1) as mask for hashing.
+ * Size of the small fixed-size array to hold most-recently remembered resources.
  */
-#define RESARRAY_INIT_SIZE 16
+#define RESOWNER_ARRAY_SIZE 32
 
 /*
- * When to switch to hashing vs. simple array logic in a ResourceArray.
+ * Initially allocated size of a ResourceOwner's hash.  Must be power of two since
+ * we'll use (capacity - 1) as mask for hashing.
  */
-#define RESARRAY_MAX_ARRAY 64
-#define RESARRAY_IS_ARRAY(resarr) ((resarr)->capacity <= RESARRAY_MAX_ARRAY)
+#define RESOWNER_HASH_INIT_SIZE 64
 
 /*
- * How many items may be stored in a resource array of given capacity.
+ * How many items may be stored in a hash of given capacity.
  * When this number is reached, we must resize.
  */
-#define RESARRAY_MAX_ITEMS(capacity) \
-	((capacity) <= RESARRAY_MAX_ARRAY ? (capacity) : (capacity)/4 * 3)
+#define RESOWNER_HASH_MAX_ITEMS(capacity) ((capacity)/4 * 3)
+
+StaticAssertDecl(RESOWNER_HASH_MAX_ITEMS(RESOWNER_HASH_INIT_SIZE) > RESOWNER_ARRAY_SIZE,
+				 "initial hash size too small compared to array size");
 
 /*
  * To speed up bulk releasing or reassigning locks from a resource owner to
@@ -119,24 +88,33 @@ typedef struct ResourceOwnerData
 	ResourceOwner nextchild;	/* next child of same parent */
 	const char *name;			/* name (just for debugging) */
 
-	/* We have built-in support for remembering: */
-	ResourceArray bufferarr;	/* owned buffers */
-	ResourceArray catrefarr;	/* catcache references */
-	ResourceArray catlistrefarr;	/* catcache-list pins */
-	ResourceArray relrefarr;	/* relcache references */
-	ResourceArray planrefarr;	/* plancache references */
-	ResourceArray tupdescarr;	/* tupdesc references */
-	ResourceArray snapshotarr;	/* snapshot references */
-	ResourceArray filearr;		/* open temporary files */
-	ResourceArray dsmarr;		/* dynamic shmem segments */
-	ResourceArray jitarr;		/* JIT contexts */
-	ResourceArray cryptohasharr;	/* cryptohash contexts */
-	ResourceArray hmacarr;		/* HMAC contexts */
+	/*
+	 * These structs keep track of the objects registered with this owner.
+	 *
+	 * We manage a small set of references by keeping them in a simple array.
+	 * When the array gets full, all the elements in the array are moved to a
+	 * hash table.  This way, the array always contains a few most recently
+	 * remembered references.  To find a particular reference, you need to
+	 * search both the array and the hash table.
+	 */
+	ResourceElem arr[RESOWNER_ARRAY_SIZE];
+	uint32		narr;			/* how many items are stored in the array */
+
+	/*
+	 * The hash table.  Uses open-addressing.  'nhash' is the number of items
+	 * present; if it would exceed 'grow_at', we enlarge it and re-hash.
+	 * 'grow_at' should be rather less than 'capacity' so that we don't waste
+	 * too much time searching for empty slots.
+	 */
+	ResourceElem *hash;
+	uint32		nhash;			/* how many items are stored in the hash */
+	uint32		capacity;		/* allocated length of hash[] */
+	uint32		grow_at;		/* grow hash when reach this */
 
 	/* We can remember up to MAX_RESOWNER_LOCKS references to local locks. */
 	int			nlocks;			/* number of owned locks */
 	LOCALLOCK  *locks[MAX_RESOWNER_LOCKS];	/* list of owned locks */
-}			ResourceOwnerData;
+} ResourceOwnerData;

 /*****************************************************************************
@@ -148,6 +126,18 @@ ResourceOwner CurTransactionResourceOwner = NULL;
 ResourceOwner TopTransactionResourceOwner = NULL;
 ResourceOwner AuxProcessResourceOwner = NULL;
 
+/* #define RESOWNER_STATS */
+/* #define RESOWNER_TRACE */
+
+#ifdef RESOWNER_STATS
+static int	narray_lookups = 0;
+static int	nhash_lookups = 0;
+#endif
+
+#ifdef RESOWNER_TRACE
+static int	resowner_trace_counter = 0;
+#endif
+
 /*
  * List of add-on callbacks for resource releasing
  */
@@ -162,298 +152,340 @@ static ResourceReleaseCallbackItem *ResourceRelease_callbacks = NULL;

 /* Internal routines */
-static void ResourceArrayInit(ResourceArray *resarr, Datum invalidval);
-static void ResourceArrayEnlarge(ResourceArray *resarr);
-static void ResourceArrayAdd(ResourceArray *resarr, Datum value);
-static bool ResourceArrayRemove(ResourceArray *resarr, Datum value);
-static bool ResourceArrayGetAny(ResourceArray *resarr, Datum *value);
-static void ResourceArrayFree(ResourceArray *resarr);
+static inline uint32 hash_resource_elem(Datum value, ResourceOwnerFuncs *kind);
+static void ResourceOwnerAddToHash(ResourceOwner owner, Datum value,
+								   ResourceOwnerFuncs *kind);
+static void ResourceOwnerReleaseAll(ResourceOwner owner,
+									ResourceReleasePhase phase,
+									bool printLeakWarnings);
 static void ResourceOwnerReleaseInternal(ResourceOwner owner,
 										 ResourceReleasePhase phase,
 										 bool isCommit,
 										 bool isTopLevel);
 static void ReleaseAuxProcessResourcesCallback(int code, Datum arg);
-static void PrintRelCacheLeakWarning(Relation rel);
-static void PrintPlanCacheLeakWarning(CachedPlan *plan);
-static void PrintTupleDescLeakWarning(TupleDesc tupdesc);
-static void PrintSnapshotLeakWarning(Snapshot snapshot);
-static void PrintFileLeakWarning(File file);
-static void PrintDSMLeakWarning(dsm_segment *seg);
-static void PrintCryptoHashLeakWarning(Datum handle);
-static void PrintHMACLeakWarning(Datum handle);

 /*****************************************************************************
  *	  INTERNAL ROUTINES														 *
  *****************************************************************************/
 
+static inline uint32
+hash_resource_elem(Datum value, ResourceOwnerFuncs *kind)
+{
+	Datum		data[2];
+
+	data[0] = value;
+	data[1] = PointerGetDatum(kind);
+
+	return hash_bytes((unsigned char *) &data, 2 * SIZEOF_DATUM);
+}
 
 /*
- * Initialize a ResourceArray
+ * Adds 'value' of given 'kind' to the ResourceOwner's hash table
  */
 static void
-ResourceArrayInit(ResourceArray *resarr, Datum invalidval)
+ResourceOwnerAddToHash(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind)
 {
-	/* Assert it's empty */
-	Assert(resarr->itemsarr == NULL);
-	Assert(resarr->capacity == 0);
-	Assert(resarr->nitems == 0);
-	Assert(resarr->maxitems == 0);
-	/* Remember the appropriate "invalid" value */
-	resarr->invalidval = invalidval;
-	/* We don't allocate any storage until needed */
+	/* Insert into first free slot at or after hash location. */
+	uint32		mask = owner->capacity - 1;
+	uint32		idx;
+
+	Assert(kind != NULL);
+
+	idx = hash_resource_elem(value, kind) & mask;
+	for (;;)
+	{
+		if (owner->hash[idx].kind == NULL)
+			break;				/* found a free slot */
+		idx = (idx + 1) & mask;
+	}
+	owner->hash[idx].item = value;
+	owner->hash[idx].kind = kind;
+	owner->nhash++;
 }
 
 /*
- * Make sure there is room for at least one more resource in an array.
- *
- * This is separate from actually inserting a resource because if we run out
- * of memory, it's critical to do so *before* acquiring the resource.
+ * Call the ReleaseResource callback on entries with given 'phase'.
  */
 static void
-ResourceArrayEnlarge(ResourceArray *resarr)
+ResourceOwnerReleaseAll(ResourceOwner owner, ResourceReleasePhase phase,
+						bool printLeakWarnings)
 {
-	uint32		i,
-				oldcap,
-				newcap;
-	Datum	   *olditemsarr;
-	Datum	   *newitemsarr;
+	bool		found;
+	int			capacity;
 
-	if (resarr->nitems < resarr->maxitems)
-		return;					/* no work needed */
-
-	olditemsarr = resarr->itemsarr;
-	oldcap = resarr->capacity;
-
-	/* Double the capacity of the array (capacity must stay a power of 2!) */
-	newcap = (oldcap > 0) ? oldcap * 2 : RESARRAY_INIT_SIZE;
-	newitemsarr = (Datum *) MemoryContextAlloc(TopMemoryContext,
-											   newcap * sizeof(Datum));
-	for (i = 0; i < newcap; i++)
-		newitemsarr[i] = resarr->invalidval;
+	/*
+	 * First handle all the entries in the array.
+	 *
+	 * Note that ReleaseResource() will call ResourceOwnerForget) and remove
+	 * the entry from our array, so we just have to iterate till there is
+	 * nothing left to remove.
+	 */
+	do
+	{
+		found = false;
+		for (int i = 0; i < owner->narr; i++)
+		{
+			if (owner->arr[i].kind->phase == phase)
+			{
+				Datum		value = owner->arr[i].item;
+				ResourceOwnerFuncs *kind = owner->arr[i].kind;
 
-	/* We assume we can't fail below this point, so OK to scribble on resarr */
-	resarr->itemsarr = newitemsarr;
-	resarr->capacity = newcap;
-	resarr->maxitems = RESARRAY_MAX_ITEMS(newcap);
-	resarr->nitems = 0;
+				if (printLeakWarnings)
+					kind->PrintLeakWarning(value);
+				kind->ReleaseResource(value);
+				found = true;
+			}
+		}
 
-	if (olditemsarr != NULL)
-	{
 		/*
-		 * Transfer any pre-existing entries into the new array; they don't
-		 * necessarily go where they were before, so this simple logic is the
-		 * best way.  Note that if we were managing the set as a simple array,
-		 * the entries after nitems are garbage, but that shouldn't matter
-		 * because we won't get here unless nitems was equal to oldcap.
+		 * If any resources were released, check again because some of the
+		 * elements might have been moved by the callbacks.  We don't want to
+		 * miss them.
 		 */
-		for (i = 0; i < oldcap; i++)
+	} while (found && owner->narr > 0);
+
+	/*
+	 * Ok, the array has now been handled.  Then the hash.  Like with the
+	 * array, ReleaseResource() will remove the entry from the hash.
+	 */
+	do
+	{
+		capacity = owner->capacity;
+		for (int idx = 0; idx < capacity; idx++)
 		{
-			if (olditemsarr[i] != resarr->invalidval)
-				ResourceArrayAdd(resarr, olditemsarr[i]);
+			while (owner->hash[idx].kind != NULL &&
+				   owner->hash[idx].kind->phase == phase)
+			{
+				Datum		value = owner->hash[idx].item;
+				ResourceOwnerFuncs *kind = owner->hash[idx].kind;
+
+				if (printLeakWarnings)
+					kind->PrintLeakWarning(value);
+				kind->ReleaseResource(value);
+
+				/*
+				 * If the same resource is remembered more than once in this
+				 * resource owner, the ReleaseResource callback might've
+				 * released a different copy of it.  Because of that, loop to
+				 * check the same index again.
+				 */
+			}
 		}
 
-		/* And release old array. */
-		pfree(olditemsarr);
-	}
-
-	Assert(resarr->nitems < resarr->maxitems);
+		/*
+		 * It's possible that the callbacks acquired more resources, causing
+		 * the hash table to grow and the existing entries to be moved around.
+		 * If that happened, scan the hash table again, so that we don't miss
+		 * entries that were moved.  (XXX: I'm not sure if any of the
+		 * callbacks actually do that, but this is cheap to check, and better
+		 * safe than sorry.)
+		 */
+		Assert(owner->capacity >= capacity);
+	} while (capacity != owner->capacity);
 }
 
+
+/*****************************************************************************
+ *	  EXPORTED ROUTINES														 *
+ *****************************************************************************/
+
+
 /*
- * Add a resource to ResourceArray
+ * ResourceOwnerCreate
+ *		Create an empty ResourceOwner.
  *
- * Caller must have previously done ResourceArrayEnlarge()
+ * All ResourceOwner objects are kept in TopMemoryContext, since they should
+ * only be freed explicitly.
  */
-static void
-ResourceArrayAdd(ResourceArray *resarr, Datum value)
+ResourceOwner
+ResourceOwnerCreate(ResourceOwner parent, const char *name)
 {
-	uint32		idx;
+	ResourceOwner owner;
 
-	Assert(value != resarr->invalidval);
-	Assert(resarr->nitems < resarr->maxitems);
+	owner = (ResourceOwner) MemoryContextAllocZero(TopMemoryContext,
+												   sizeof(ResourceOwnerData));
+	owner->name = name;
 
-	if (RESARRAY_IS_ARRAY(resarr))
+	if (parent)
 	{
-		/* Append to linear array. */
-		idx = resarr->nitems;
+		owner->parent = parent;
+		owner->nextchild = parent->firstchild;
+		parent->firstchild = owner;
 	}
-	else
-	{
-		/* Insert into first free slot at or after hash location. */
-		uint32		mask = resarr->capacity - 1;
 
-		idx = DatumGetUInt32(hash_any((void *) &value, sizeof(value))) & mask;
-		for (;;)
-		{
-			if (resarr->itemsarr[idx] == resarr->invalidval)
-				break;
-			idx = (idx + 1) & mask;
-		}
-	}
-	resarr->lastidx = idx;
-	resarr->itemsarr[idx] = value;
-	resarr->nitems++;
+#ifdef RESOWNER_TRACE
+	elog(LOG, "CREATE %d: %p %s",
+		 resowner_trace_counter++, owner, name);
+#endif
+
+	return owner;
 }
 
 /*
- * Remove a resource from ResourceArray
+ * Make sure there is room for at least one more resource in an array.
  *
- * Returns true on success, false if resource was not found.
+ * This is separate from actually inserting a resource because if we run out
+ * of memory, it's critical to do so *before* acquiring the resource.
  *
- * Note: if same resource ID appears more than once, one instance is removed.
+ * NB: Make sure there are no unrelated ResourceOwnerRemember() calls between
+ * your ResourceOwnerEnlarge() call and the ResourceOwnerRemember() call that
+ * you reserved the space for!
  */
-static bool
-ResourceArrayRemove(ResourceArray *resarr, Datum value)
+void
+ResourceOwnerEnlarge(ResourceOwner owner)
 {
-	uint32		i,
-				idx,
-				lastidx = resarr->lastidx;
-
-	Assert(value != resarr->invalidval);
+	if (owner->narr < RESOWNER_ARRAY_SIZE)
+		return;					/* no work needed */
 
-	/* Search through all items, but try lastidx first. */
-	if (RESARRAY_IS_ARRAY(resarr))
+	/* Is there space in the hash? If not, enlarge it. */
+	if (owner->narr + owner->nhash >= owner->grow_at)
 	{
-		if (lastidx < resarr->nitems &&
-			resarr->itemsarr[lastidx] == value)
-		{
-			resarr->itemsarr[lastidx] = resarr->itemsarr[resarr->nitems - 1];
-			resarr->nitems--;
-			/* Update lastidx to make reverse-order removals fast. */
-			resarr->lastidx = resarr->nitems - 1;
-			return true;
-		}
-		for (i = 0; i < resarr->nitems; i++)
+		uint32		i,
+					oldcap,
+					newcap;
+		ResourceElem *oldhash;
+		ResourceElem *newhash;
+
+		oldhash = owner->hash;
+		oldcap = owner->capacity;
+
+		/* Double the capacity (it must stay a power of 2!) */
+		newcap = (oldcap > 0) ? oldcap * 2 : RESOWNER_HASH_INIT_SIZE;
+		newhash = (ResourceElem *) MemoryContextAllocZero(TopMemoryContext,
+														  newcap * sizeof(ResourceElem));
+
+		/*
+		 * We assume we can't fail below this point, so OK to scribble on the
+		 * owner
+		 */
+		owner->hash = newhash;
+		owner->capacity = newcap;
+		owner->grow_at = RESOWNER_HASH_MAX_ITEMS(newcap);
+		owner->nhash = 0;
+
+		if (oldhash != NULL)
 		{
-			if (resarr->itemsarr[i] == value)
+			/*
+			 * Transfer any pre-existing entries into the new hash table; they
+			 * don't necessarily go where they were before, so this simple
+			 * logic is the best way.
+			 */
+			for (i = 0; i < oldcap; i++)
 			{
-				resarr->itemsarr[i] = resarr->itemsarr[resarr->nitems - 1];
-				resarr->nitems--;
-				/* Update lastidx to make reverse-order removals fast. */
-				resarr->lastidx = resarr->nitems - 1;
-				return true;
+				if (oldhash[i].kind != NULL)
+					ResourceOwnerAddToHash(owner, oldhash[i].item, oldhash[i].kind);
 			}
+
+			/* And release old hash table. */
+			pfree(oldhash);
 		}
 	}
-	else
-	{
-		uint32		mask = resarr->capacity - 1;
 
-		if (lastidx < resarr->capacity &&
-			resarr->itemsarr[lastidx] == value)
-		{
-			resarr->itemsarr[lastidx] = resarr->invalidval;
-			resarr->nitems--;
-			return true;
-		}
-		idx = DatumGetUInt32(hash_any((void *) &value, sizeof(value))) & mask;
-		for (i = 0; i < resarr->capacity; i++)
-		{
-			if (resarr->itemsarr[idx] == value)
-			{
-				resarr->itemsarr[idx] = resarr->invalidval;
-				resarr->nitems--;
-				return true;
-			}
-			idx = (idx + 1) & mask;
-		}
+	/* Move items from the array to the hash */
+	Assert(owner->narr == RESOWNER_ARRAY_SIZE);
+	for (int i = 0; i < owner->narr; i++)
+	{
+		ResourceOwnerAddToHash(owner, owner->arr[i].item, owner->arr[i].kind);
 	}
+	owner->narr = 0;
 
-	return false;
+	Assert(owner->nhash < owner->grow_at);
 }
 
 /*
- * Get any convenient entry in a ResourceArray.
- *
- * "Convenient" is defined as "easy for ResourceArrayRemove to remove";
- * we help that along by setting lastidx to match.  This avoids O(N^2) cost
- * when removing all ResourceArray items during ResourceOwner destruction.
+ * Remember that an object is owner by a ReourceOwner
  *
- * Returns true if we found an element, or false if the array is empty.
+ * Caller must have previously done ResourceOwnerEnlarge()
  */
-static bool
-ResourceArrayGetAny(ResourceArray *resarr, Datum *value)
+void
+ResourceOwnerRemember(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind)
 {
-	if (resarr->nitems == 0)
-		return false;
+	uint32		idx;
 
-	if (RESARRAY_IS_ARRAY(resarr))
-	{
-		/* Linear array: just return the first element. */
-		resarr->lastidx = 0;
-	}
-	else
-	{
-		/* Hash: search forward from wherever we were last. */
-		uint32		mask = resarr->capacity - 1;
+#ifdef RESOWNER_TRACE
+	elog(LOG, "REMEMBER %d: owner %p value " UINT64_FORMAT ", kind: %s",
+		 resowner_trace_counter++, owner, DatumGetUInt64(value), kind->name);
+#endif
 
-		for (;;)
-		{
-			resarr->lastidx &= mask;
-			if (resarr->itemsarr[resarr->lastidx] != resarr->invalidval)
-				break;
-			resarr->lastidx++;
-		}
+	if (owner->narr >= RESOWNER_ARRAY_SIZE)
+	{
+		/* forgot to call ResourceOwnerEnlarge? */
+		elog(ERROR, "ResourceOwnerRemember called but array was full");
 	}
 
-	*value = resarr->itemsarr[resarr->lastidx];
-	return true;
+	/* Append to linear array. */
+	idx = owner->narr;
+	owner->arr[idx].item = value;
+	owner->arr[idx].kind = kind;
+	owner->narr++;
 }
 
 /*
- * Trash a ResourceArray (we don't care about its state after this)
+ * Forget that an object is owned by a ResourceOwner
+ *
+ * Note: if same resource ID is associated with the ResourceOwner more than once,
+ * one instance is removed.
  */
-static void
-ResourceArrayFree(ResourceArray *resarr)
+void
+ResourceOwnerForget(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind)
 {
-	if (resarr->itemsarr)
-		pfree(resarr->itemsarr);
-}
-
+	uint32		i,
+				idx;
 
-/*****************************************************************************
- *	  EXPORTED ROUTINES														 *
- *****************************************************************************/
+#ifdef RESOWNER_TRACE
+	elog(LOG, "FORGET %d: owner %p value " UINT64_FORMAT ", kind: %s",
+		 resowner_trace_counter++, owner, DatumGetUInt64(value), kind->name);
+#endif
 
+	/* Search through all items, but check the array first. */
+	for (i = 0; i < owner->narr; i++)
+	{
+		if (owner->arr[i].item == value &&
+			owner->arr[i].kind == kind)
+		{
+			owner->arr[i] = owner->arr[owner->narr - 1];
+			owner->narr--;
 
-/*
- * ResourceOwnerCreate
- *		Create an empty ResourceOwner.
- *
- * All ResourceOwner objects are kept in TopMemoryContext, since they should
- * only be freed explicitly.
- */
-ResourceOwner
-ResourceOwnerCreate(ResourceOwner parent, const char *name)
-{
-	ResourceOwner owner;
+#ifdef RESOWNER_STATS
+			narray_lookups++;
+#endif
 
-	owner = (ResourceOwner) MemoryContextAllocZero(TopMemoryContext,
-												   sizeof(ResourceOwnerData));
-	owner->name = name;
+			return;
+		}
+	}
 
-	if (parent)
+	/* Search hash */
+	if (owner->nhash > 0)
 	{
-		owner->parent = parent;
-		owner->nextchild = parent->firstchild;
-		parent->firstchild = owner;
-	}
+		uint32		mask = owner->capacity - 1;
 
-	ResourceArrayInit(&(owner->bufferarr), BufferGetDatum(InvalidBuffer));
-	ResourceArrayInit(&(owner->catrefarr), PointerGetDatum(NULL));
-	ResourceArrayInit(&(owner->catlistrefarr), PointerGetDatum(NULL));
-	ResourceArrayInit(&(owner->relrefarr), PointerGetDatum(NULL));
-	ResourceArrayInit(&(owner->planrefarr), PointerGetDatum(NULL));
-	ResourceArrayInit(&(owner->tupdescarr), PointerGetDatum(NULL));
-	ResourceArrayInit(&(owner->snapshotarr), PointerGetDatum(NULL));
-	ResourceArrayInit(&(owner->filearr), FileGetDatum(-1));
-	ResourceArrayInit(&(owner->dsmarr), PointerGetDatum(NULL));
-	ResourceArrayInit(&(owner->jitarr), PointerGetDatum(NULL));
-	ResourceArrayInit(&(owner->cryptohasharr), PointerGetDatum(NULL));
-	ResourceArrayInit(&(owner->hmacarr), PointerGetDatum(NULL));
+		idx = hash_resource_elem(value, kind) & mask;
+		for (i = 0; i < owner->capacity; i++)
+		{
+			if (owner->hash[idx].item == value &&
+				owner->hash[idx].kind == kind)
+			{
+				owner->hash[idx].item = (Datum) 0;
+				owner->hash[idx].kind = NULL;
+				owner->nhash--;
+
+#ifdef RESOWNER_STATS
+				nhash_lookups++;
+#endif
+				return;
+			}
+			idx = (idx + 1) & mask;
+		}
+	}
 
-	return owner;
+	/*
+	 * Use %p to print the reference, since most objects tracked by a resource
+	 * owner are pointers.  It's a bit misleading if it's not a pointer, but
+	 * this is a programmer error, anyway.
+	 */
+	elog(ERROR, "%s %p is not owned by resource owner %s",
+		 kind->name, DatumGetPointer(value), owner->name);
 }
 
 /*
@@ -490,6 +522,15 @@ ResourceOwnerRelease(ResourceOwner owner,
 {
 	/* There's not currently any setup needed before recursing */
 	ResourceOwnerReleaseInternal(owner, phase, isCommit, isTopLevel);
+
+#ifdef RESOWNER_STATS
+	if (isTopLevel)
+	{
+		elog(LOG, "RESOWNER STATS: lookups: array %d, hash %d", narray_lookups, nhash_lookups);
+		narray_lookups = 0;
+		nhash_lookups = 0;
+	}
+#endif
 }
 
 static void
@@ -502,7 +543,6 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 	ResourceOwner save;
 	ResourceReleaseCallbackItem *item;
 	ResourceReleaseCallbackItem *next;
-	Datum		foundres;
 
 	/* Recurse to handle descendants */
 	for (child = owner->firstchild; child != NULL; child = child->nextchild)
@@ -518,71 +558,13 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 	if (phase == RESOURCE_RELEASE_BEFORE_LOCKS)
 	{
 		/*
-		 * Release buffer pins.  Note that ReleaseBuffer will remove the
-		 * buffer entry from our array, so we just have to iterate till there
-		 * are none.
+		 * Release all resources that need to be released before the locks.
 		 *
-		 * During a commit, there shouldn't be any remaining pins --- that
-		 * would indicate failure to clean up the executor correctly --- so
-		 * issue warnings.  In the abort case, just clean up quietly.
+		 * During a commit, there shouldn't be any remaining resources ---
+		 * that would indicate failure to clean up the executor correctly ---
+		 * so issue warnings.  In the abort case, just clean up quietly.
 		 */
-		while (ResourceArrayGetAny(&(owner->bufferarr), &foundres))
-		{
-			Buffer		res = DatumGetBuffer(foundres);
-
-			if (isCommit)
-				PrintBufferLeakWarning(res);
-			ReleaseBuffer(res);
-		}
-
-		/* Ditto for relcache references */
-		while (ResourceArrayGetAny(&(owner->relrefarr), &foundres))
-		{
-			Relation	res = (Relation) DatumGetPointer(foundres);
-
-			if (isCommit)
-				PrintRelCacheLeakWarning(res);
-			RelationClose(res);
-		}
-
-		/* Ditto for dynamic shared memory segments */
-		while (ResourceArrayGetAny(&(owner->dsmarr), &foundres))
-		{
-			dsm_segment *res = (dsm_segment *) DatumGetPointer(foundres);
-
-			if (isCommit)
-				PrintDSMLeakWarning(res);
-			dsm_detach(res);
-		}
-
-		/* Ditto for JIT contexts */
-		while (ResourceArrayGetAny(&(owner->jitarr), &foundres))
-		{
-			JitContext *context = (JitContext *) DatumGetPointer(foundres);
-
-			jit_release_context(context);
-		}
-
-		/* Ditto for cryptohash contexts */
-		while (ResourceArrayGetAny(&(owner->cryptohasharr), &foundres))
-		{
-			pg_cryptohash_ctx *context =
-			(pg_cryptohash_ctx *) DatumGetPointer(foundres);
-
-			if (isCommit)
-				PrintCryptoHashLeakWarning(foundres);
-			pg_cryptohash_free(context);
-		}
-
-		/* Ditto for HMAC contexts */
-		while (ResourceArrayGetAny(&(owner->hmacarr), &foundres))
-		{
-			pg_hmac_ctx *context = (pg_hmac_ctx *) DatumGetPointer(foundres);
-
-			if (isCommit)
-				PrintHMACLeakWarning(foundres);
-			pg_hmac_free(context);
-		}
+		ResourceOwnerReleaseAll(owner, phase, isCommit);
 	}
 	else if (phase == RESOURCE_RELEASE_LOCKS)
 	{
@@ -635,70 +617,9 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 	else if (phase == RESOURCE_RELEASE_AFTER_LOCKS)
 	{
 		/*
-		 * Release catcache references.  Note that ReleaseCatCache will remove
-		 * the catref entry from our array, so we just have to iterate till
-		 * there are none.
-		 *
-		 * As with buffer pins, warn if any are left at commit time.
+		 * Release all resources that need to be released after the locks.
 		 */
-		while (ResourceArrayGetAny(&(owner->catrefarr), &foundres))
-		{
-			HeapTuple	res = (HeapTuple) DatumGetPointer(foundres);
-
-			if (isCommit)
-				PrintCatCacheLeakWarning(res);
-			ReleaseCatCache(res);
-		}
-
-		/* Ditto for catcache lists */
-		while (ResourceArrayGetAny(&(owner->catlistrefarr), &foundres))
-		{
-			CatCList   *res = (CatCList *) DatumGetPointer(foundres);
-
-			if (isCommit)
-				PrintCatCacheListLeakWarning(res);
-			ReleaseCatCacheList(res);
-		}
-
-		/* Ditto for plancache references */
-		while (ResourceArrayGetAny(&(owner->planrefarr), &foundres))
-		{
-			CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres);
-
-			if (isCommit)
-				PrintPlanCacheLeakWarning(res);
-			ReleaseCachedPlan(res, owner);
-		}
-
-		/* Ditto for tupdesc references */
-		while (ResourceArrayGetAny(&(owner->tupdescarr), &foundres))
-		{
-			TupleDesc	res = (TupleDesc) DatumGetPointer(foundres);
-
-			if (isCommit)
-				PrintTupleDescLeakWarning(res);
-			DecrTupleDescRefCount(res);
-		}
-
-		/* Ditto for snapshot references */
-		while (ResourceArrayGetAny(&(owner->snapshotarr), &foundres))
-		{
-			Snapshot	res = (Snapshot) DatumGetPointer(foundres);
-
-			if (isCommit)
-				PrintSnapshotLeakWarning(res);
-			UnregisterSnapshot(res);
-		}
-
-		/* Ditto for temporary files */
-		while (ResourceArrayGetAny(&(owner->filearr), &foundres))
-		{
-			File		res = DatumGetFile(foundres);
-
-			if (isCommit)
-				PrintFileLeakWarning(res);
-			FileClose(res);
-		}
+		ResourceOwnerReleaseAll(owner, phase, isCommit);
 	}
 
 	/* Let add-on modules get a chance too */
@@ -722,13 +643,42 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 void
 ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner)
 {
-	Datum		foundres;
+	/* array first */
+	for (int i = 0; i < owner->narr; i++)
+	{
+		if (owner->arr[i].kind == &planref_resowner_funcs)
+		{
+			CachedPlan *planref = (CachedPlan *) DatumGetPointer(owner->arr[i].item);
+
+			owner->arr[i] = owner->arr[owner->narr - 1];
+			owner->narr--;
+			i--;
+
+			/*
+			 * pass 'NULL' because we already removed the entry from the
+			 * resowner
+			 */
+			ReleaseCachedPlan(planref, NULL);
+		}
+	}
 
-	while (ResourceArrayGetAny(&(owner->planrefarr), &foundres))
+	/* Then hash */
+	for (int i = 0; i < owner->capacity; i++)
 	{
-		CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres);
+		if (owner->hash[i].kind == &planref_resowner_funcs)
+		{
+			CachedPlan *planref = (CachedPlan *) DatumGetPointer(owner->hash[i].item);
+
+			owner->hash[i].item = (Datum) 0;
+			owner->hash[i].kind = NULL;
+			owner->nhash--;
 
-		ReleaseCachedPlan(res, owner);
+			/*
+			 * pass 'NULL' because we already removed the entry from the
+			 * resowner
+			 */
+			ReleaseCachedPlan(planref, NULL);
+		}
 	}
 }
 
@@ -745,20 +695,15 @@ ResourceOwnerDelete(ResourceOwner owner)
 	Assert(owner != CurrentResourceOwner);
 
 	/* And it better not own any resources, either */
-	Assert(owner->bufferarr.nitems == 0);
-	Assert(owner->catrefarr.nitems == 0);
-	Assert(owner->catlistrefarr.nitems == 0);
-	Assert(owner->relrefarr.nitems == 0);
-	Assert(owner->planrefarr.nitems == 0);
-	Assert(owner->tupdescarr.nitems == 0);
-	Assert(owner->snapshotarr.nitems == 0);
-	Assert(owner->filearr.nitems == 0);
-	Assert(owner->dsmarr.nitems == 0);
-	Assert(owner->jitarr.nitems == 0);
-	Assert(owner->cryptohasharr.nitems == 0);
-	Assert(owner->hmacarr.nitems == 0);
+	Assert(owner->narr == 0);
+	Assert(owner->nhash == 0);
 	Assert(owner->nlocks == 0 || owner->nlocks == MAX_RESOWNER_LOCKS + 1);
 
+#ifdef RESOWNER_TRACE
+	elog(LOG, "DELETE %d: %p %s",
+		 resowner_trace_counter++, owner, owner->name);
+#endif
+
 	/*
 	 * Delete children.  The recursive call will delink the child from me, so
 	 * just iterate as long as there is a child.
@@ -774,19 +719,8 @@ ResourceOwnerDelete(ResourceOwner owner)
 	ResourceOwnerNewParent(owner, NULL);
 
 	/* And free the object. */
-	ResourceArrayFree(&(owner->bufferarr));
-	ResourceArrayFree(&(owner->catrefarr));
-	ResourceArrayFree(&(owner->catlistrefarr));
-	ResourceArrayFree(&(owner->relrefarr));
-	ResourceArrayFree(&(owner->planrefarr));
-	ResourceArrayFree(&(owner->tupdescarr));
-	ResourceArrayFree(&(owner->snapshotarr));
-	ResourceArrayFree(&(owner->filearr));
-	ResourceArrayFree(&(owner->dsmarr));
-	ResourceArrayFree(&(owner->jitarr));
-	ResourceArrayFree(&(owner->cryptohasharr));
-	ResourceArrayFree(&(owner->hmacarr));
-
+	if (owner->hash)
+		pfree(owner->hash);
 	pfree(owner);
 }
 
@@ -844,11 +778,10 @@ ResourceOwnerNewParent(ResourceOwner owner,
 /*
  * Register or deregister callback functions for resource cleanup
  *
- * These functions are intended for use by dynamically loaded modules.
- * For built-in modules we generally just hardwire the appropriate calls.
- *
- * Note that the callback occurs post-commit or post-abort, so the callback
- * functions can only do noncritical cleanup.
+ * These functions can be used by dynamically loaded modules.  These used
+ * to be the only way for an extension to register custom resource types
+ * with a resource owner, but nowadays it is easier to define a new
+ * ResourceOwnerFuncs instance with custom callbacks.
  */
 void
 RegisterResourceReleaseCallback(ResourceReleaseCallback callback, void *arg)
@@ -938,58 +871,20 @@ ReleaseAuxProcessResourcesCallback(int code, Datum arg)
 	ReleaseAuxProcessResources(isCommit);
 }
 
-
 /*
- * Make sure there is room for at least one more entry in a ResourceOwner's
- * buffer array.
+ * Remember that a Local Lock is owned by a ResourceOwner
  *
- * This is separate from actually inserting an entry because if we run out
- * of memory, it's critical to do so *before* acquiring the resource.
+ * This is different from the other Remember functions in that the list of
+ * locks is only a lossy cache.  It can hold up to MAX_RESOWNER_LOCKS entries,
+ * and when it overflows, we stop tracking locks.  The point of only remembering
+ * only up to MAX_RESOWNER_LOCKS entries is that if a lot of locks are held,
+ * ResourceOwnerForgetLock doesn't need to scan through a large array to find
+ * the entry.
  */
 void
-ResourceOwnerEnlargeBuffers(ResourceOwner owner)
+ResourceOwnerRememberLock(ResourceOwner owner, LOCALLOCK *locallock)
 {
-	/* We used to allow pinning buffers without a resowner, but no more */
-	Assert(owner != NULL);
-	ResourceArrayEnlarge(&(owner->bufferarr));
-}
-
-/*
- * Remember that a buffer pin is owned by a ResourceOwner
- *
- * Caller must have previously done ResourceOwnerEnlargeBuffers()
- */
-void
-ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer)
-{
-	ResourceArrayAdd(&(owner->bufferarr), BufferGetDatum(buffer));
-}
-
-/*
- * Forget that a buffer pin is owned by a ResourceOwner
- */
-void
-ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer)
-{
-	if (!ResourceArrayRemove(&(owner->bufferarr), BufferGetDatum(buffer)))
-		elog(ERROR, "buffer %d is not owned by resource owner %s",
-			 buffer, owner->name);
-}
-
-/*
- * Remember that a Local Lock is owned by a ResourceOwner
- *
- * This is different from the other Remember functions in that the list of
- * locks is only a lossy cache. It can hold up to MAX_RESOWNER_LOCKS entries,
- * and when it overflows, we stop tracking locks. The point of only remembering
- * only up to MAX_RESOWNER_LOCKS entries is that if a lot of locks are held,
- * ResourceOwnerForgetLock doesn't need to scan through a large array to find
- * the entry.
- */
-void
-ResourceOwnerRememberLock(ResourceOwner owner, LOCALLOCK *locallock)
-{
-	Assert(locallock != NULL);
+	Assert(locallock != NULL);
 
 	if (owner->nlocks > MAX_RESOWNER_LOCKS)
 		return;					/* we have already overflowed */
@@ -1027,469 +922,3 @@ ResourceOwnerForgetLock(ResourceOwner owner, LOCALLOCK *locallock)
 	elog(ERROR, "lock reference %p is not owned by resource owner %s",
 		 locallock, owner->name);
 }
-
-/*
- * Make sure there is room for at least one more entry in a ResourceOwner's
- * catcache reference array.
- *
- * This is separate from actually inserting an entry because if we run out
- * of memory, it's critical to do so *before* acquiring the resource.
- */
-void
-ResourceOwnerEnlargeCatCacheRefs(ResourceOwner owner)
-{
-	ResourceArrayEnlarge(&(owner->catrefarr));
-}
-
-/*
- * Remember that a catcache reference is owned by a ResourceOwner
- *
- * Caller must have previously done ResourceOwnerEnlargeCatCacheRefs()
- */
-void
-ResourceOwnerRememberCatCacheRef(ResourceOwner owner, HeapTuple tuple)
-{
-	ResourceArrayAdd(&(owner->catrefarr), PointerGetDatum(tuple));
-}
-
-/*
- * Forget that a catcache reference is owned by a ResourceOwner
- */
-void
-ResourceOwnerForgetCatCacheRef(ResourceOwner owner, HeapTuple tuple)
-{
-	if (!ResourceArrayRemove(&(owner->catrefarr), PointerGetDatum(tuple)))
-		elog(ERROR, "catcache reference %p is not owned by resource owner %s",
-			 tuple, owner->name);
-}
-
-/*
- * Make sure there is room for at least one more entry in a ResourceOwner's
- * catcache-list reference array.
- *
- * This is separate from actually inserting an entry because if we run out
- * of memory, it's critical to do so *before* acquiring the resource.
- */
-void
-ResourceOwnerEnlargeCatCacheListRefs(ResourceOwner owner)
-{
-	ResourceArrayEnlarge(&(owner->catlistrefarr));
-}
-
-/*
- * Remember that a catcache-list reference is owned by a ResourceOwner
- *
- * Caller must have previously done ResourceOwnerEnlargeCatCacheListRefs()
- */
-void
-ResourceOwnerRememberCatCacheListRef(ResourceOwner owner, CatCList *list)
-{
-	ResourceArrayAdd(&(owner->catlistrefarr), PointerGetDatum(list));
-}
-
-/*
- * Forget that a catcache-list reference is owned by a ResourceOwner
- */
-void
-ResourceOwnerForgetCatCacheListRef(ResourceOwner owner, CatCList *list)
-{
-	if (!ResourceArrayRemove(&(owner->catlistrefarr), PointerGetDatum(list)))
-		elog(ERROR, "catcache list reference %p is not owned by resource owner %s",
-			 list, owner->name);
-}
-
-/*
- * Make sure there is room for at least one more entry in a ResourceOwner's
- * relcache reference array.
- *
- * This is separate from actually inserting an entry because if we run out
- * of memory, it's critical to do so *before* acquiring the resource.
- */
-void
-ResourceOwnerEnlargeRelationRefs(ResourceOwner owner)
-{
-	ResourceArrayEnlarge(&(owner->relrefarr));
-}
-
-/*
- * Remember that a relcache reference is owned by a ResourceOwner
- *
- * Caller must have previously done ResourceOwnerEnlargeRelationRefs()
- */
-void
-ResourceOwnerRememberRelationRef(ResourceOwner owner, Relation rel)
-{
-	ResourceArrayAdd(&(owner->relrefarr), PointerGetDatum(rel));
-}
-
-/*
- * Forget that a relcache reference is owned by a ResourceOwner
- */
-void
-ResourceOwnerForgetRelationRef(ResourceOwner owner, Relation rel)
-{
-	if (!ResourceArrayRemove(&(owner->relrefarr), PointerGetDatum(rel)))
-		elog(ERROR, "relcache reference %s is not owned by resource owner %s",
-			 RelationGetRelationName(rel), owner->name);
-}
-
-/*
- * Debugging subroutine
- */
-static void
-PrintRelCacheLeakWarning(Relation rel)
-{
-	elog(WARNING, "relcache reference leak: relation \"%s\" not closed",
-		 RelationGetRelationName(rel));
-}
-
-/*
- * Make sure there is room for at least one more entry in a ResourceOwner's
- * plancache reference array.
- *
- * This is separate from actually inserting an entry because if we run out
- * of memory, it's critical to do so *before* acquiring the resource.
- */
-void
-ResourceOwnerEnlargePlanCacheRefs(ResourceOwner owner)
-{
-	ResourceArrayEnlarge(&(owner->planrefarr));
-}
-
-/*
- * Remember that a plancache reference is owned by a ResourceOwner
- *
- * Caller must have previously done ResourceOwnerEnlargePlanCacheRefs()
- */
-void
-ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
-{
-	ResourceArrayAdd(&(owner->planrefarr), PointerGetDatum(plan));
-}
-
-/*
- * Forget that a plancache reference is owned by a ResourceOwner
- */
-void
-ResourceOwnerForgetPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
-{
-	if (!ResourceArrayRemove(&(owner->planrefarr), PointerGetDatum(plan)))
-		elog(ERROR, "plancache reference %p is not owned by resource owner %s",
-			 plan, owner->name);
-}
-
-/*
- * Debugging subroutine
- */
-static void
-PrintPlanCacheLeakWarning(CachedPlan *plan)
-{
-	elog(WARNING, "plancache reference leak: plan %p not closed", plan);
-}
-
-/*
- * Make sure there is room for at least one more entry in a ResourceOwner's
- * tupdesc reference array.
- *
- * This is separate from actually inserting an entry because if we run out
- * of memory, it's critical to do so *before* acquiring the resource.
- */
-void
-ResourceOwnerEnlargeTupleDescs(ResourceOwner owner)
-{
-	ResourceArrayEnlarge(&(owner->tupdescarr));
-}
-
-/*
- * Remember that a tupdesc reference is owned by a ResourceOwner
- *
- * Caller must have previously done ResourceOwnerEnlargeTupleDescs()
- */
-void
-ResourceOwnerRememberTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
-{
-	ResourceArrayAdd(&(owner->tupdescarr), PointerGetDatum(tupdesc));
-}
-
-/*
- * Forget that a tupdesc reference is owned by a ResourceOwner
- */
-void
-ResourceOwnerForgetTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
-{
-	if (!ResourceArrayRemove(&(owner->tupdescarr), PointerGetDatum(tupdesc)))
-		elog(ERROR, "tupdesc reference %p is not owned by resource owner %s",
-			 tupdesc, owner->name);
-}
-
-/*
- * Debugging subroutine
- */
-static void
-PrintTupleDescLeakWarning(TupleDesc tupdesc)
-{
-	elog(WARNING,
-		 "TupleDesc reference leak: TupleDesc %p (%u,%d) still referenced",
-		 tupdesc, tupdesc->tdtypeid, tupdesc->tdtypmod);
-}
-
-/*
- * Make sure there is room for at least one more entry in a ResourceOwner's
- * snapshot reference array.
- *
- * This is separate from actually inserting an entry because if we run out
- * of memory, it's critical to do so *before* acquiring the resource.
- */
-void
-ResourceOwnerEnlargeSnapshots(ResourceOwner owner)
-{
-	ResourceArrayEnlarge(&(owner->snapshotarr));
-}
-
-/*
- * Remember that a snapshot reference is owned by a ResourceOwner
- *
- * Caller must have previously done ResourceOwnerEnlargeSnapshots()
- */
-void
-ResourceOwnerRememberSnapshot(ResourceOwner owner, Snapshot snapshot)
-{
-	ResourceArrayAdd(&(owner->snapshotarr), PointerGetDatum(snapshot));
-}
-
-/*
- * Forget that a snapshot reference is owned by a ResourceOwner
- */
-void
-ResourceOwnerForgetSnapshot(ResourceOwner owner, Snapshot snapshot)
-{
-	if (!ResourceArrayRemove(&(owner->snapshotarr), PointerGetDatum(snapshot)))
-		elog(ERROR, "snapshot reference %p is not owned by resource owner %s",
-			 snapshot, owner->name);
-}
-
-/*
- * Debugging subroutine
- */
-static void
-PrintSnapshotLeakWarning(Snapshot snapshot)
-{
-	elog(WARNING, "Snapshot reference leak: Snapshot %p still referenced",
-		 snapshot);
-}
-
-
-/*
- * Make sure there is room for at least one more entry in a ResourceOwner's
- * files reference array.
- *
- * This is separate from actually inserting an entry because if we run out
- * of memory, it's critical to do so *before* acquiring the resource.
- */
-void
-ResourceOwnerEnlargeFiles(ResourceOwner owner)
-{
-	ResourceArrayEnlarge(&(owner->filearr));
-}
-
-/*
- * Remember that a temporary file is owned by a ResourceOwner
- *
- * Caller must have previously done ResourceOwnerEnlargeFiles()
- */
-void
-ResourceOwnerRememberFile(ResourceOwner owner, File file)
-{
-	ResourceArrayAdd(&(owner->filearr), FileGetDatum(file));
-}
-
-/*
- * Forget that a temporary file is owned by a ResourceOwner
- */
-void
-ResourceOwnerForgetFile(ResourceOwner owner, File file)
-{
-	if (!ResourceArrayRemove(&(owner->filearr), FileGetDatum(file)))
-		elog(ERROR, "temporary file %d is not owned by resource owner %s",
-			 file, owner->name);
-}
-
-/*
- * Debugging subroutine
- */
-static void
-PrintFileLeakWarning(File file)
-{
-	elog(WARNING, "temporary file leak: File %d still referenced",
-		 file);
-}
-
-/*
- * Make sure there is room for at least one more entry in a ResourceOwner's
- * dynamic shmem segment reference array.
- *
- * This is separate from actually inserting an entry because if we run out
- * of memory, it's critical to do so *before* acquiring the resource.
- */
-void
-ResourceOwnerEnlargeDSMs(ResourceOwner owner)
-{
-	ResourceArrayEnlarge(&(owner->dsmarr));
-}
-
-/*
- * Remember that a dynamic shmem segment is owned by a ResourceOwner
- *
- * Caller must have previously done ResourceOwnerEnlargeDSMs()
- */
-void
-ResourceOwnerRememberDSM(ResourceOwner owner, dsm_segment *seg)
-{
-	ResourceArrayAdd(&(owner->dsmarr), PointerGetDatum(seg));
-}
-
-/*
- * Forget that a dynamic shmem segment is owned by a ResourceOwner
- */
-void
-ResourceOwnerForgetDSM(ResourceOwner owner, dsm_segment *seg)
-{
-	if (!ResourceArrayRemove(&(owner->dsmarr), PointerGetDatum(seg)))
-		elog(ERROR, "dynamic shared memory segment %u is not owned by resource owner %s",
-			 dsm_segment_handle(seg), owner->name);
-}
-
-/*
- * Debugging subroutine
- */
-static void
-PrintDSMLeakWarning(dsm_segment *seg)
-{
-	elog(WARNING, "dynamic shared memory leak: segment %u still referenced",
-		 dsm_segment_handle(seg));
-}
-
-/*
- * Make sure there is room for at least one more entry in a ResourceOwner's
- * JIT context reference array.
- *
- * This is separate from actually inserting an entry because if we run out of
- * memory, it's critical to do so *before* acquiring the resource.
- */
-void
-ResourceOwnerEnlargeJIT(ResourceOwner owner)
-{
-	ResourceArrayEnlarge(&(owner->jitarr));
-}
-
-/*
- * Remember that a JIT context is owned by a ResourceOwner
- *
- * Caller must have previously done ResourceOwnerEnlargeJIT()
- */
-void
-ResourceOwnerRememberJIT(ResourceOwner owner, Datum handle)
-{
-	ResourceArrayAdd(&(owner->jitarr), handle);
-}
-
-/*
- * Forget that a JIT context is owned by a ResourceOwner
- */
-void
-ResourceOwnerForgetJIT(ResourceOwner owner, Datum handle)
-{
-	if (!ResourceArrayRemove(&(owner->jitarr), handle))
-		elog(ERROR, "JIT context %p is not owned by resource owner %s",
-			 DatumGetPointer(handle), owner->name);
-}
-
-/*
- * Make sure there is room for at least one more entry in a ResourceOwner's
- * cryptohash context reference array.
- *
- * This is separate from actually inserting an entry because if we run out of
- * memory, it's critical to do so *before* acquiring the resource.
- */
-void
-ResourceOwnerEnlargeCryptoHash(ResourceOwner owner)
-{
-	ResourceArrayEnlarge(&(owner->cryptohasharr));
-}
-
-/*
- * Remember that a cryptohash context is owned by a ResourceOwner
- *
- * Caller must have previously done ResourceOwnerEnlargeCryptoHash()
- */
-void
-ResourceOwnerRememberCryptoHash(ResourceOwner owner, Datum handle)
-{
-	ResourceArrayAdd(&(owner->cryptohasharr), handle);
-}
-
-/*
- * Forget that a cryptohash context is owned by a ResourceOwner
- */
-void
-ResourceOwnerForgetCryptoHash(ResourceOwner owner, Datum handle)
-{
-	if (!ResourceArrayRemove(&(owner->cryptohasharr), handle))
-		elog(ERROR, "cryptohash context %p is not owned by resource owner %s",
-			 DatumGetPointer(handle), owner->name);
-}
-
-/*
- * Debugging subroutine
- */
-static void
-PrintCryptoHashLeakWarning(Datum handle)
-{
-	elog(WARNING, "cryptohash context reference leak: context %p still referenced",
-		 DatumGetPointer(handle));
-}
-
-/*
- * Make sure there is room for at least one more entry in a ResourceOwner's
- * hmac context reference array.
- *
- * This is separate from actually inserting an entry because if we run out of
- * memory, it's critical to do so *before* acquiring the resource.
- */
-void
-ResourceOwnerEnlargeHMAC(ResourceOwner owner)
-{
-	ResourceArrayEnlarge(&(owner->hmacarr));
-}
-
-/*
- * Remember that a HMAC context is owned by a ResourceOwner
- *
- * Caller must have previously done ResourceOwnerEnlargeHMAC()
- */
-void
-ResourceOwnerRememberHMAC(ResourceOwner owner, Datum handle)
-{
-	ResourceArrayAdd(&(owner->hmacarr), handle);
-}
-
-/*
- * Forget that a HMAC context is owned by a ResourceOwner
- */
-void
-ResourceOwnerForgetHMAC(ResourceOwner owner, Datum handle)
-{
-	if (!ResourceArrayRemove(&(owner->hmacarr), handle))
-		elog(ERROR, "HMAC context %p is not owned by resource owner %s",
-			 DatumGetPointer(handle), owner->name);
-}
-
-/*
- * Debugging subroutine
- */
-static void
-PrintHMACLeakWarning(Datum handle)
-{
-	elog(WARNING, "HMAC context reference leak: context %p still referenced",
-		 DatumGetPointer(handle));
-}
diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c
index 7d11ae34781..7da51381cd2 100644
--- a/src/backend/utils/time/snapmgr.c
+++ b/src/backend/utils/time/snapmgr.c
@@ -57,6 +57,7 @@
 #include "lib/pairingheap.h"
 #include "miscadmin.h"
 #include "port/pg_lfind.h"
+#include "storage/fd.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
@@ -67,7 +68,7 @@
 #include "utils/memutils.h"
 #include "utils/old_snapshot.h"
 #include "utils/rel.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
@@ -175,6 +176,24 @@ static Snapshot CopySnapshot(Snapshot snapshot);
 static void FreeSnapshot(Snapshot snapshot);
 static void SnapshotResetXmin(void);
 
+/* ResourceOwner callbacks to track snapshot references */
+static void ResOwnerReleaseSnapshot(Datum res);
+static void ResOwnerPrintSnapshotLeakWarning(Datum res);
+
+static ResourceOwnerFuncs snapshot_resowner_funcs =
+{
+	.name = "snapshot reference",
+	.phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.ReleaseResource = ResOwnerReleaseSnapshot,
+	.PrintLeakWarning = ResOwnerPrintSnapshotLeakWarning
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+#define ResourceOwnerRememberSnapshot(owner, snap) \
+	ResourceOwnerRemember(owner, PointerGetDatum(snap), &snapshot_resowner_funcs)
+#define ResourceOwnerForgetSnapshot(owner, snap) \
+	ResourceOwnerForget(owner, PointerGetDatum(snap), &snapshot_resowner_funcs)
+
 /*
  * Snapshot fields to be serialized.
  *
@@ -850,7 +869,7 @@ RegisterSnapshotOnOwner(Snapshot snapshot, ResourceOwner owner)
 	snap = snapshot->copied ? snapshot : CopySnapshot(snapshot);
 
 	/* and tell resowner.c about it */
-	ResourceOwnerEnlargeSnapshots(owner);
+	ResourceOwnerEnlarge(owner);
 	snap->regd_count++;
 	ResourceOwnerRememberSnapshot(owner, snap);
 
@@ -2379,3 +2398,19 @@ XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot)
 
 	return false;
 }
+
+/*
+ * ResourceOwner callbacks
+ */
+static void
+ResOwnerReleaseSnapshot(Datum res)
+{
+	UnregisterSnapshot((Snapshot) DatumGetPointer(res));
+}
+
+static void
+ResOwnerPrintSnapshotLeakWarning(Datum res)
+{
+	elog(WARNING, "Snapshot reference leak: Snapshot %p still referenced",
+		 DatumGetPointer(res));
+}
diff --git a/src/common/cryptohash_openssl.c b/src/common/cryptohash_openssl.c
index a654cd4ad40..0fb6dd6736d 100644
--- a/src/common/cryptohash_openssl.c
+++ b/src/common/cryptohash_openssl.c
@@ -31,7 +31,6 @@
 #ifndef FRONTEND
 #include "utils/memutils.h"
 #include "utils/resowner.h"
-#include "utils/resowner_private.h"
 #endif
 
 /*
@@ -74,6 +73,27 @@ struct pg_cryptohash_ctx
 #endif
 };
 
+/* ResourceOwner callbacks to hold cryptohash contexts */
+#ifndef FRONTEND
+static void ResOwnerReleaseCryptoHash(Datum res);
+static void ResOwnerPrintCryptoHashLeakWarning(Datum res);
+
+static ResourceOwnerFuncs cryptohash_resowner_funcs =
+{
+	/* relcache references */
+	.name = "OpenSSL cryptohash context",
+	.phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.ReleaseResource = ResOwnerReleaseCryptoHash,
+	.PrintLeakWarning = ResOwnerPrintCryptoHashLeakWarning,
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+#define ResourceOwnerRememberCryptoHash(owner, ctx) \
+	ResourceOwnerRemember(owner, PointerGetDatum(ctx), &cryptohash_resowner_funcs)
+#define ResourceOwnerForgetCryptoHash(owner, ctx) \
+	ResourceOwnerForget(owner, PointerGetDatum(ctx), &cryptohash_resowner_funcs)
+#endif
+
 static const char *
 SSLerrmessage(unsigned long ecode)
 {
@@ -104,7 +124,7 @@ pg_cryptohash_create(pg_cryptohash_type type)
 	 * allocation to avoid leaking.
 	 */
 #ifndef FRONTEND
-	ResourceOwnerEnlargeCryptoHash(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 #endif
 
 	ctx = ALLOC(sizeof(pg_cryptohash_ctx));
@@ -138,8 +158,7 @@ pg_cryptohash_create(pg_cryptohash_type type)
 
 #ifndef FRONTEND
 	ctx->resowner = CurrentResourceOwner;
-	ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
-									PointerGetDatum(ctx));
+	ResourceOwnerRememberCryptoHash(CurrentResourceOwner, ctx);
 #endif
 
 	return ctx;
@@ -307,8 +326,7 @@ pg_cryptohash_free(pg_cryptohash_ctx *ctx)
 	EVP_MD_CTX_destroy(ctx->evpctx);
 
 #ifndef FRONTEND
-	ResourceOwnerForgetCryptoHash(ctx->resowner,
-								  PointerGetDatum(ctx));
+	ResourceOwnerForgetCryptoHash(ctx->resowner, ctx);
 #endif
 
 	explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
@@ -351,3 +369,21 @@ pg_cryptohash_error(pg_cryptohash_ctx *ctx)
 	Assert(false);				/* cannot be reached */
 	return _("success");
 }
+
+/*
+ * ResourceOwner callbacks
+ */
+#ifndef FRONTEND
+static void
+ResOwnerReleaseCryptoHash(Datum res)
+{
+	pg_cryptohash_free((pg_cryptohash_ctx *) DatumGetPointer(res));
+}
+
+static void
+ResOwnerPrintCryptoHashLeakWarning(Datum res)
+{
+	elog(WARNING, "cryptohash context reference leak: context %p still referenced",
+		 DatumGetPointer(res));
+}
+#endif
diff --git a/src/common/hmac_openssl.c b/src/common/hmac_openssl.c
index 12be542fa27..9f68462fb4a 100644
--- a/src/common/hmac_openssl.c
+++ b/src/common/hmac_openssl.c
@@ -31,7 +31,6 @@
 #ifndef FRONTEND
 #include "utils/memutils.h"
 #include "utils/resowner.h"
-#include "utils/resowner_private.h"
 #endif
 
 /*
@@ -73,6 +72,27 @@ struct pg_hmac_ctx
 #endif
 };
 
+/* ResourceOwner callbacks to hold HMAC contexts */
+#ifndef FRONTEND
+static void ResOwnerReleaseHMAC(Datum res);
+static void ResOwnerPrintHMACLeakWarning(Datum res);
+
+static ResourceOwnerFuncs hmac_resowner_funcs =
+{
+	/* relcache references */
+	.name = "OpenSSL HMAC context",
+	.phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.ReleaseResource = ResOwnerReleaseHMAC,
+	.PrintLeakWarning = ResOwnerPrintHMACLeakWarning,
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+#define ResourceOwnerRememberHMAC(owner, ctx) \
+	ResourceOwnerRemember(owner, PointerGetDatum(ctx), &hmac_resowner_funcs)
+#define ResourceOwnerForgetHMAC(owner, ctx) \
+	ResourceOwnerForget(owner, PointerGetDatum(ctx), &hmac_resowner_funcs)
+#endif
+
 static const char *
 SSLerrmessage(unsigned long ecode)
 {
@@ -115,7 +135,7 @@ pg_hmac_create(pg_cryptohash_type type)
 	ERR_clear_error();
 #ifdef HAVE_HMAC_CTX_NEW
 #ifndef FRONTEND
-	ResourceOwnerEnlargeHMAC(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 #endif
 	ctx->hmacctx = HMAC_CTX_new();
 #else
@@ -137,7 +157,7 @@ pg_hmac_create(pg_cryptohash_type type)
 #ifdef HAVE_HMAC_CTX_NEW
 #ifndef FRONTEND
 	ctx->resowner = CurrentResourceOwner;
-	ResourceOwnerRememberHMAC(CurrentResourceOwner, PointerGetDatum(ctx));
+	ResourceOwnerRememberHMAC(CurrentResourceOwner, ctx);
 #endif
 #else
 	memset(ctx->hmacctx, 0, sizeof(HMAC_CTX));
@@ -303,7 +323,7 @@ pg_hmac_free(pg_hmac_ctx *ctx)
 #ifdef HAVE_HMAC_CTX_FREE
 	HMAC_CTX_free(ctx->hmacctx);
 #ifndef FRONTEND
-	ResourceOwnerForgetHMAC(ctx->resowner, PointerGetDatum(ctx));
+	ResourceOwnerForgetHMAC(ctx->resowner, ctx);
 #endif
 #else
 	explicit_bzero(ctx->hmacctx, sizeof(HMAC_CTX));
@@ -346,3 +366,21 @@ pg_hmac_error(pg_hmac_ctx *ctx)
 	Assert(false);				/* cannot be reached */
 	return _("success");
 }
+
+/*
+ * ResourceOwner callbacks
+ */
+#ifndef FRONTEND
+static void
+ResOwnerReleaseHMAC(Datum res)
+{
+	pg_hmac_free((pg_hmac_ctx *) DatumGetPointer(res));
+}
+
+static void
+ResOwnerPrintHMACLeakWarning(Datum res)
+{
+	elog(WARNING, "HMAC context reference leak: context %p still referenced",
+		 DatumGetPointer(res));
+}
+#endif
diff --git a/src/include/storage/buf_internals.h b/src/include/storage/buf_internals.h
index 0b448147407..d8e39753211 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -383,6 +383,15 @@ typedef struct CkptSortItem
 
 extern PGDLLIMPORT CkptSortItem *CkptBufferIds;
 
+/* ResourceOwner callbacks to hold buffer pins */
+extern ResourceOwnerFuncs buffer_resowner_funcs;
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+#define ResourceOwnerRememberBuffer(owner, buffer) \
+	ResourceOwnerRemember(owner, Int32GetDatum(buffer), &buffer_resowner_funcs)
+#define ResourceOwnerForgetBuffer(owner, buffer) \
+	ResourceOwnerForget(owner, Int32GetDatum(buffer), &buffer_resowner_funcs)
+
 /*
  * Internal buffer management routines
  */
diff --git a/src/include/utils/catcache.h b/src/include/utils/catcache.h
index af0b3418736..df405382182 100644
--- a/src/include/utils/catcache.h
+++ b/src/include/utils/catcache.h
@@ -225,7 +225,4 @@ extern void PrepareToInvalidateCacheTuple(Relation relation,
 										  HeapTuple newtuple,
 										  void (*function) (int, uint32, Oid));
 
-extern void PrintCatCacheLeakWarning(HeapTuple tuple);
-extern void PrintCatCacheListLeakWarning(CatCList *list);
-
 #endif							/* CATCACHE_H */
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index a443181d416..bc4fdec36ec 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -233,4 +233,6 @@ extern bool CachedPlanIsSimplyValid(CachedPlanSource *plansource,
 extern CachedExpression *GetCachedExpression(Node *expr);
 extern void FreeCachedExpression(CachedExpression *cexpr);
 
+extern ResourceOwnerFuncs planref_resowner_funcs;
+
 #endif							/* PLANCACHE_H */
diff --git a/src/include/utils/resowner.h b/src/include/utils/resowner.h
index cd070b6080e..441b346bdfb 100644
--- a/src/include/utils/resowner.h
+++ b/src/include/utils/resowner.h
@@ -50,6 +50,36 @@ typedef enum
 	RESOURCE_RELEASE_AFTER_LOCKS
 } ResourceReleasePhase;
 
+/*
+ * In order to track an object, resowner.c needs a few callbacks for it.
+ * The callbacks for resources of a specific kind are encapsulated in
+ * ResourceOwnerFuncs.
+ *
+ * Note that the callback occurs post-commit or post-abort, so these callback
+ * functions can only do noncritical cleanup.
+ */
+typedef struct ResourceOwnerFuncs
+{
+	const char *name;			/* name for the object kind, for debugging */
+
+	ResourceReleasePhase phase; /* when are these objects released? */
+
+	/*
+	 * Release resource.
+	 *
+	 * NOTE: this must call ResourceOwnerForget to disassociate it with the
+	 * resource owner.
+	 */
+	void		(*ReleaseResource) (Datum res);
+
+	/*
+	 * Print a warning, when a resource has not been properly released before
+	 * commit.
+	 */
+	void		(*PrintLeakWarning) (Datum res);
+
+} ResourceOwnerFuncs;
+
 /*
  *	Dynamically loaded modules can get control during ResourceOwnerRelease
  *	by providing a callback of this form.
@@ -71,16 +101,29 @@ extern void ResourceOwnerRelease(ResourceOwner owner,
 								 ResourceReleasePhase phase,
 								 bool isCommit,
 								 bool isTopLevel);
-extern void ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner);
 extern void ResourceOwnerDelete(ResourceOwner owner);
 extern ResourceOwner ResourceOwnerGetParent(ResourceOwner owner);
 extern void ResourceOwnerNewParent(ResourceOwner owner,
 								   ResourceOwner newparent);
+
+extern void ResourceOwnerEnlarge(ResourceOwner owner);
+extern void ResourceOwnerRemember(ResourceOwner owner, Datum res, ResourceOwnerFuncs *kind);
+extern void ResourceOwnerForget(ResourceOwner owner, Datum res, ResourceOwnerFuncs *kind);
+
 extern void RegisterResourceReleaseCallback(ResourceReleaseCallback callback,
 											void *arg);
 extern void UnregisterResourceReleaseCallback(ResourceReleaseCallback callback,
 											  void *arg);
+
 extern void CreateAuxProcessResourceOwner(void);
 extern void ReleaseAuxProcessResources(bool isCommit);
 
+/* special support for local lock management */
+struct LOCALLOCK;
+extern void ResourceOwnerRememberLock(ResourceOwner owner, struct LOCALLOCK *locallock);
+extern void ResourceOwnerForgetLock(ResourceOwner owner, struct LOCALLOCK *locallock);
+
+/* special function to relase all plancache references */
+extern void ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner);
+
 #endif							/* RESOWNER_H */
diff --git a/src/include/utils/resowner_private.h b/src/include/utils/resowner_private.h
deleted file mode 100644
index 1b1f3181b54..00000000000
--- a/src/include/utils/resowner_private.h
+++ /dev/null
@@ -1,112 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * resowner_private.h
- *	  POSTGRES resource owner private definitions.
- *
- * See utils/resowner/README for more info.
- *
- *
- * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * src/include/utils/resowner_private.h
- *
- *-------------------------------------------------------------------------
- */
-#ifndef RESOWNER_PRIVATE_H
-#define RESOWNER_PRIVATE_H
-
-#include "storage/dsm.h"
-#include "storage/fd.h"
-#include "storage/lock.h"
-#include "utils/catcache.h"
-#include "utils/plancache.h"
-#include "utils/resowner.h"
-#include "utils/snapshot.h"
-
-
-/* support for buffer refcount management */
-extern void ResourceOwnerEnlargeBuffers(ResourceOwner owner);
-extern void ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer);
-extern void ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer);
-
-/* support for local lock management */
-extern void ResourceOwnerRememberLock(ResourceOwner owner, LOCALLOCK *locallock);
-extern void ResourceOwnerForgetLock(ResourceOwner owner, LOCALLOCK *locallock);
-
-/* support for catcache refcount management */
-extern void ResourceOwnerEnlargeCatCacheRefs(ResourceOwner owner);
-extern void ResourceOwnerRememberCatCacheRef(ResourceOwner owner,
-											 HeapTuple tuple);
-extern void ResourceOwnerForgetCatCacheRef(ResourceOwner owner,
-										   HeapTuple tuple);
-extern void ResourceOwnerEnlargeCatCacheListRefs(ResourceOwner owner);
-extern void ResourceOwnerRememberCatCacheListRef(ResourceOwner owner,
-												 CatCList *list);
-extern void ResourceOwnerForgetCatCacheListRef(ResourceOwner owner,
-											   CatCList *list);
-
-/* support for relcache refcount management */
-extern void ResourceOwnerEnlargeRelationRefs(ResourceOwner owner);
-extern void ResourceOwnerRememberRelationRef(ResourceOwner owner,
-											 Relation rel);
-extern void ResourceOwnerForgetRelationRef(ResourceOwner owner,
-										   Relation rel);
-
-/* support for plancache refcount management */
-extern void ResourceOwnerEnlargePlanCacheRefs(ResourceOwner owner);
-extern void ResourceOwnerRememberPlanCacheRef(ResourceOwner owner,
-											  CachedPlan *plan);
-extern void ResourceOwnerForgetPlanCacheRef(ResourceOwner owner,
-											CachedPlan *plan);
-
-/* support for tupledesc refcount management */
-extern void ResourceOwnerEnlargeTupleDescs(ResourceOwner owner);
-extern void ResourceOwnerRememberTupleDesc(ResourceOwner owner,
-										   TupleDesc tupdesc);
-extern void ResourceOwnerForgetTupleDesc(ResourceOwner owner,
-										 TupleDesc tupdesc);
-
-/* support for snapshot refcount management */
-extern void ResourceOwnerEnlargeSnapshots(ResourceOwner owner);
-extern void ResourceOwnerRememberSnapshot(ResourceOwner owner,
-										  Snapshot snapshot);
-extern void ResourceOwnerForgetSnapshot(ResourceOwner owner,
-										Snapshot snapshot);
-
-/* support for temporary file management */
-extern void ResourceOwnerEnlargeFiles(ResourceOwner owner);
-extern void ResourceOwnerRememberFile(ResourceOwner owner,
-									  File file);
-extern void ResourceOwnerForgetFile(ResourceOwner owner,
-									File file);
-
-/* support for dynamic shared memory management */
-extern void ResourceOwnerEnlargeDSMs(ResourceOwner owner);
-extern void ResourceOwnerRememberDSM(ResourceOwner owner,
-									 dsm_segment *);
-extern void ResourceOwnerForgetDSM(ResourceOwner owner,
-								   dsm_segment *);
-
-/* support for JITContext management */
-extern void ResourceOwnerEnlargeJIT(ResourceOwner owner);
-extern void ResourceOwnerRememberJIT(ResourceOwner owner,
-									 Datum handle);
-extern void ResourceOwnerForgetJIT(ResourceOwner owner,
-								   Datum handle);
-
-/* support for cryptohash context management */
-extern void ResourceOwnerEnlargeCryptoHash(ResourceOwner owner);
-extern void ResourceOwnerRememberCryptoHash(ResourceOwner owner,
-											Datum handle);
-extern void ResourceOwnerForgetCryptoHash(ResourceOwner owner,
-										  Datum handle);
-
-/* support for HMAC context management */
-extern void ResourceOwnerEnlargeHMAC(ResourceOwner owner);
-extern void ResourceOwnerRememberHMAC(ResourceOwner owner,
-									  Datum handle);
-extern void ResourceOwnerForgetHMAC(ResourceOwner owner,
-									Datum handle);
-
-#endif							/* RESOWNER_PRIVATE_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 22ea42c16b5..271c31185a1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2337,8 +2337,10 @@ ReplicationStateOnDisk
 ResTarget
 ReservoirState
 ReservoirStateData
-ResourceArray
+ResourceElem
 ResourceOwner
+ResourceOwnerData
+ResourceOwnerFuncs
 ResourceReleaseCallback
 ResourceReleaseCallbackItem
 ResourceReleasePhase
-- 
2.30.2

From ceb84cd6551887f55d540e295e71f82fce157df0 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email-masked]>
Date: Wed, 15 Feb 2023 11:17:00 +0200
Subject: [PATCH v13 3/5] Release resources in priority order.

XXX: To be squashed with previous commit, but keeping it separate for
now to show what changed since last patchset version.

Allow each resource kind to specify a release priority, and release the
resources in that order.

To make that possible, this restricts what you can do between phases.
After calling ResourceOwnerRelease, you are no longer allowed to use
the resource owner to remember any more resources in it, or to forget
any previously remembered resources by calling ResourceOwnerForget.
There was one case where that was done previously. At subtransaction
commit, AtEOSubXact_Inval() would handle the invalidation messages and
call RelationFlushRelation(), which temporarily increased the
reference count on the relation being flushed. We now switch to the
parent subtransaction's resource owner before calling
AtEOSubXact_Inval(), so that there is a valid ResourceOwner to
temporarily hold that relcache reference.

Other end-of-xact routines make similar calls to AtEOXact_Inval()
between release phases, but I didn't see any regression test failures
from those, so I'm not sure if they could reach a codepath that needs
remembering extra resources.

Also, the release callback no longer needs to call ResourceOwnerForget
on the resource being released. ResourceOwnerRelease unregisters the
resource from the owner before calling the callback. That needed some
changes in bufmgr.c and some other files, where releasing the resources
previously always called ResourceOwnerForget.

Add tests for ResourceOwners in src/test/modules/test_resowner.

- XXX in llvmjit.c
---
 src/backend/access/common/tupdesc.c           |  16 +-
 src/backend/access/transam/xact.c             |  15 +
 src/backend/jit/llvm/llvmjit.c                |  12 +-
 src/backend/storage/buffer/bufmgr.c           |  49 ++-
 src/backend/storage/file/fd.c                 |  18 +-
 src/backend/storage/ipc/dsm.c                 |  13 +-
 src/backend/utils/cache/catcache.c            |  35 +-
 src/backend/utils/cache/plancache.c           |  17 +-
 src/backend/utils/cache/relcache.c            |  24 +-
 src/backend/utils/resowner/README             |  60 +++
 src/backend/utils/resowner/resowner.c         | 385 ++++++++++++------
 src/backend/utils/time/snapmgr.c              |  20 +-
 src/common/cryptohash_openssl.c               |  17 +-
 src/common/hmac_openssl.c                     |  17 +-
 src/include/utils/plancache.h                 |   4 +-
 src/include/utils/resowner.h                  |  42 +-
 src/pl/plpgsql/src/pl_exec.c                  |   2 +-
 src/pl/plpgsql/src/pl_handler.c               |   6 +-
 src/test/modules/Makefile                     |   1 +
 src/test/modules/test_resowner/.gitignore     |   4 +
 src/test/modules/test_resowner/Makefile       |  24 ++
 .../test_resowner/expected/test_resowner.out  | 197 +++++++++
 src/test/modules/test_resowner/meson.build    |  37 ++
 .../test_resowner/test_resowner--1.0.sql      |  30 ++
 .../test_resowner/test_resowner.control       |   4 +
 .../test_resowner/test_resowner_basic.c       | 210 ++++++++++
 .../test_resowner/test_resowner_many.c        | 287 +++++++++++++
 27 files changed, 1337 insertions(+), 209 deletions(-)
 create mode 100644 src/test/modules/test_resowner/.gitignore
 create mode 100644 src/test/modules/test_resowner/Makefile
 create mode 100644 src/test/modules/test_resowner/expected/test_resowner.out
 create mode 100644 src/test/modules/test_resowner/meson.build
 create mode 100644 src/test/modules/test_resowner/test_resowner--1.0.sql
 create mode 100644 src/test/modules/test_resowner/test_resowner.control
 create mode 100644 src/test/modules/test_resowner/test_resowner_basic.c
 create mode 100644 src/test/modules/test_resowner/test_resowner_many.c

diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index a4b830d307f..de61541796b 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -39,9 +39,9 @@ static void ResOwnerPrintTupleDescLeakWarning(Datum res);
 
 static ResourceOwnerFuncs tupdesc_resowner_funcs =
 {
-	/* relcache references */
 	.name = "tupdesc reference",
-	.phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_TUPDESC_REFS,
 	.ReleaseResource = ResOwnerReleaseTupleDesc,
 	.PrintLeakWarning = ResOwnerPrintTupleDescLeakWarning
 };
@@ -939,13 +939,17 @@ BuildDescFromLists(List *names, List *types, List *typmods, List *collations)
 }

-/*
- * ResourceOwner callbacks
- */
+/* ResourceOwner callbacks */
+
 static void
 ResOwnerReleaseTupleDesc(Datum res)
 {
-	DecrTupleDescRefCount((TupleDesc) DatumGetPointer(res));
+	TupleDesc tupdesc = (TupleDesc) DatumGetPointer(res);
+
+	/* Like DecrTupleDescRefCount, but don't call ResourceOwnerForget() */
+	Assert(tupdesc->tdrefcount > 0);
+	if (--tupdesc->tdrefcount == 0)
+		FreeTupleDesc(tupdesc);
 }
 
 static void
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index b8764012607..aba2bd3b961 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -5180,9 +5180,24 @@ AbortSubTransaction(void)
 		ResourceOwnerRelease(s->curTransactionOwner,
 							 RESOURCE_RELEASE_BEFORE_LOCKS,
 							 false, false);
+
 		AtEOSubXact_RelationCache(false, s->subTransactionId,
 								  s->parent->subTransactionId);
+
+
+		/*
+		 * AtEOSubXact_Inval sometimes needs to temporarily
+		 * bump the refcount on the relcache entries that it processes.
+		 * We cannot use the subtransaction's resource owner anymore,
+		 * because we've already started releasing it.  But we can use
+		 * the parent resource owner.
+		 */
+		CurrentResourceOwner = s->parent->curTransactionOwner;
+
 		AtEOSubXact_Inval(false);
+
+		CurrentResourceOwner = s->curTransactionOwner;
+
 		ResourceOwnerRelease(s->curTransactionOwner,
 							 RESOURCE_RELEASE_LOCKS,
 							 false, false);
diff --git a/src/backend/jit/llvm/llvmjit.c b/src/backend/jit/llvm/llvmjit.c
index ab9b8fcfde9..e6265eff3dd 100644
--- a/src/backend/jit/llvm/llvmjit.c
+++ b/src/backend/jit/llvm/llvmjit.c
@@ -127,9 +127,9 @@ static void ResOwnerPrintJitContextLeakWarning(Datum res);
 
 static ResourceOwnerFuncs jit_resowner_funcs =
 {
-	/* relcache references */
 	.name = "LLVM JIT context",
-	.phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_JIT_CONTEXTS,
 	.ReleaseResource = ResOwnerReleaseJitContext,
 	.PrintLeakWarning = ResOwnerPrintJitContextLeakWarning
 };
@@ -241,7 +241,8 @@ llvm_release_context(JitContext *context)
 	list_free(llvm_context->handles);
 	llvm_context->handles = NIL;
 
-	ResourceOwnerForgetJIT(context->resowner, context);
+	if (context->resowner)
+		ResourceOwnerForgetJIT(context->resowner, context);
 }
 
 /*
@@ -1294,7 +1295,10 @@ llvm_error_message(LLVMErrorRef error)
 static void
 ResOwnerReleaseJitContext(Datum res)
 {
-	jit_release_context((JitContext *) DatumGetPointer(res));
+	JitContext *context = (JitContext *) DatumGetPointer(res);
+
+	context->resowner = NULL;
+	jit_release_context(context);
 }
 
 static void
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 8fb05a72fa0..3b1bffd5279 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -215,7 +215,8 @@ static void ResOwnerPrintBufferLeakWarning(Datum res);
 ResourceOwnerFuncs buffer_resowner_funcs =
 {
 	.name = "buffer",
-	.phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_BUFFERS,
 	.ReleaseResource = ResOwnerReleaseBuffer,
 	.PrintLeakWarning = ResOwnerPrintBufferLeakWarning
 };
@@ -469,6 +470,7 @@ static Buffer ReadBuffer_common(SMgrRelation smgr, char relpersistence,
 static bool PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy);
 static void PinBuffer_Locked(BufferDesc *buf);
 static void UnpinBuffer(BufferDesc *buf);
+static void UnpinBufferNoOwner(BufferDesc *buf);
 static void BufferSync(int flags);
 static uint32 WaitBufHdrUnlocked(BufferDesc *buf);
 static int	SyncOneBuffer(int buf_id, bool skip_recently_used,
@@ -1908,6 +1910,15 @@ PinBuffer_Locked(BufferDesc *buf)
  */
 static void
 UnpinBuffer(BufferDesc *buf)
+{
+	Buffer		b = BufferDescriptorGetBuffer(buf);
+
+	ResourceOwnerForgetBuffer(CurrentResourceOwner, b);
+	UnpinBufferNoOwner(buf);
+}
+
+static void
+UnpinBufferNoOwner(BufferDesc *buf)
 {
 	PrivateRefCountEntry *ref;
 	Buffer		b = BufferDescriptorGetBuffer(buf);
@@ -1915,10 +1926,8 @@ UnpinBuffer(BufferDesc *buf)
 	/* not moving as we're likely deleting it soon anyway */
 	ref = GetPrivateRefCountEntry(b, false);
 	Assert(ref != NULL);
-
-	ResourceOwnerForgetBuffer(CurrentResourceOwner, b);
-
 	Assert(ref->refcount > 0);
+
 	ref->refcount--;
 	if (ref->refcount == 0)
 	{
@@ -4009,16 +4018,17 @@ ReleaseBuffer(Buffer buffer)
 	if (!BufferIsValid(buffer))
 		elog(ERROR, "bad buffer ID: %d", buffer);
 
+	ResourceOwnerForgetBuffer(CurrentResourceOwner, buffer);
+
 	if (BufferIsLocal(buffer))
 	{
-		ResourceOwnerForgetBuffer(CurrentResourceOwner, buffer);
-
 		Assert(LocalRefCount[-buffer - 1] > 0);
 		LocalRefCount[-buffer - 1]--;
-		return;
 	}
-
-	UnpinBuffer(GetBufferDescriptor(buffer - 1));
+	else
+	{
+		UnpinBufferNoOwner(GetBufferDescriptor(buffer - 1));
+	}
 }
 
 /*
@@ -5100,13 +5110,26 @@ TestForOldSnapshot_impl(Snapshot snapshot, Relation relation)
 				 errmsg("snapshot too old")));
 }
 
-/*
- * ResourceOwner callbacks
- */
+/* ResourceOwner callbacks */
+
 static void
 ResOwnerReleaseBuffer(Datum res)
 {
-	ReleaseBuffer(DatumGetInt32(res));
+	Buffer buffer = DatumGetInt32(res);
+
+	/* Like ReleaseBuffer, but don't call ResourceOwnerForgetBuffer */
+	if (!BufferIsValid(buffer))
+		elog(ERROR, "bad buffer ID: %d", buffer);
+
+	if (BufferIsLocal(buffer))
+	{
+		Assert(LocalRefCount[-buffer - 1] > 0);
+		LocalRefCount[-buffer - 1]--;
+	}
+	else
+	{
+		UnpinBufferNoOwner(GetBufferDescriptor(buffer - 1));
+	}
 }
 
 static void
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 7bd716542e7..296da188296 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -356,7 +356,8 @@ static void ResOwnerPrintFileLeakWarning(Datum res);
 static ResourceOwnerFuncs file_resowner_funcs =
 {
 	.name = "File",
-	.phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_FILES,
 	.ReleaseResource = ResOwnerReleaseFile,
 	.PrintLeakWarning = ResOwnerPrintFileLeakWarning
 };
@@ -3756,13 +3757,20 @@ data_sync_elevel(int elevel)
 	return data_sync_retry ? elevel : PANIC;
 }
 
-/*
- * ResourceOwner callbacks
- */
+/* ResourceOwner callbacks */
+
 static void
 ResOwnerReleaseFile(Datum res)
 {
-	FileClose((File) DatumGetInt32(res));
+	File		file = (File) DatumGetInt32(res);
+	Vfd		   *vfdP;
+
+	Assert(FileIsValid(file));
+
+	vfdP = &VfdCache[file];
+	vfdP->resowner = NULL;
+
+	FileClose(file);
 }
 
 static void
diff --git a/src/backend/storage/ipc/dsm.c b/src/backend/storage/ipc/dsm.c
index 61927f7ccc0..21c53bc6ae4 100644
--- a/src/backend/storage/ipc/dsm.c
+++ b/src/backend/storage/ipc/dsm.c
@@ -150,7 +150,8 @@ static void ResOwnerPrintDSMLeakWarning(Datum res);
 static ResourceOwnerFuncs dsm_resowner_funcs =
 {
 	.name = "dynamic shared memory segment",
-	.phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_DSMS,
 	.ReleaseResource = ResOwnerReleaseDSM,
 	.PrintLeakWarning = ResOwnerPrintDSMLeakWarning
 };
@@ -1275,13 +1276,15 @@ is_main_region_dsm_handle(dsm_handle handle)
 	return handle & 1;
 }
 
-/*
- * ResourceOwner callbacks
- */
+/* ResourceOwner callbacks */
+
 static void
 ResOwnerReleaseDSM(Datum res)
 {
-	dsm_detach((dsm_segment *) DatumGetPointer(res));
+	dsm_segment *seg = (dsm_segment *) res;
+
+	seg->resowner = NULL;
+	dsm_detach(seg);
 }
 static void
 ResOwnerPrintDSMLeakWarning(Datum res)
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index af90d1e0134..f05ab9f04fe 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -95,6 +95,8 @@ static CatCTup *CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp,
 										uint32 hashValue, Index hashIndex,
 										bool negative);
 
+static void ReleaseCatCacheWithOwner(HeapTuple tuple, ResourceOwner resowner);
+static void ReleaseCatCacheListWithOwner(CatCList *list, ResourceOwner resowner);
 static void CatCacheFreeKeys(TupleDesc tupdesc, int nkeys, int *attnos,
 							 Datum *keys);
 static void CatCacheCopyKeys(TupleDesc tupdesc, int nkeys, int *attnos,
@@ -116,7 +118,8 @@ static ResourceOwnerFuncs catcache_resowner_funcs =
 {
 	/* catcache references */
 	.name = "catcache reference",
-	.phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_CATCACHE_REFS,
 	.ReleaseResource = ResOwnerReleaseCatCache,
 	.PrintLeakWarning = ResOwnerPrintCatCacheLeakWarning
 };
@@ -125,7 +128,8 @@ static ResourceOwnerFuncs catlistref_resowner_funcs =
 {
 	/* catcache-list pins */
 	.name = "catcache list reference",
-	.phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_CATCACHE_LIST_REFS,
 	.ReleaseResource = ResOwnerReleaseCatCacheList,
 	.PrintLeakWarning = ResOwnerPrintCatCacheListLeakWarning
 };
@@ -1470,6 +1474,12 @@ SearchCatCacheMiss(CatCache *cache,
  */
 void
 ReleaseCatCache(HeapTuple tuple)
+{
+	ReleaseCatCacheWithOwner(tuple, CurrentResourceOwner);
+}
+
+static void
+ReleaseCatCacheWithOwner(HeapTuple tuple, ResourceOwner resowner)
 {
 	CatCTup    *ct = (CatCTup *) (((char *) tuple) -
 								  offsetof(CatCTup, tuple));
@@ -1479,7 +1489,8 @@ ReleaseCatCache(HeapTuple tuple)
 	Assert(ct->refcount > 0);
 
 	ct->refcount--;
-	ResourceOwnerForgetCatCacheRef(CurrentResourceOwner, &ct->tuple);
+	if (resowner)
+		ResourceOwnerForgetCatCacheRef(CurrentResourceOwner, &ct->tuple);
 
 	if (
 #ifndef CATCACHE_FORCE_RELEASE
@@ -1818,12 +1829,19 @@ SearchCatCacheList(CatCache *cache,
  */
 void
 ReleaseCatCacheList(CatCList *list)
+{
+	ReleaseCatCacheListWithOwner(list, CurrentResourceOwner);
+}
+
+static void
+ReleaseCatCacheListWithOwner(CatCList *list, ResourceOwner resowner)
 {
 	/* Safety checks to ensure we were handed a cache entry */
 	Assert(list->cl_magic == CL_MAGIC);
 	Assert(list->refcount > 0);
 	list->refcount--;
-	ResourceOwnerForgetCatCacheListRef(CurrentResourceOwner, list);
+	if (resowner)
+		ResourceOwnerForgetCatCacheListRef(CurrentResourceOwner, list);
 
 	if (
 #ifndef CATCACHE_FORCE_RELEASE
@@ -2099,13 +2117,12 @@ PrepareToInvalidateCacheTuple(Relation relation,
 	}
 }
 
-/*
- * ResourceOwner callbacks
- */
+/* ResourceOwner callbacks */
+
 static void
 ResOwnerReleaseCatCache(Datum res)
 {
-	ReleaseCatCache((HeapTuple) DatumGetPointer(res));
+	ReleaseCatCacheWithOwner((HeapTuple) DatumGetPointer(res), NULL);
 }
 
 static void
@@ -2128,7 +2145,7 @@ ResOwnerPrintCatCacheLeakWarning(Datum res)
 static void
 ResOwnerReleaseCatCacheList(Datum res)
 {
-	ReleaseCatCacheList((CatCList *) DatumGetPointer(res));
+	ReleaseCatCacheListWithOwner((CatCList *) DatumGetPointer(res), NULL);
 }
 
 static void
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 5fa486c5eb5..b295fcb2c20 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -119,11 +119,11 @@ static void PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue);
 static void ResOwnerReleaseCachedPlan(Datum res);
 static void ResOwnerPrintPlanCacheLeakWarning(Datum res);
 
-/* this is exported for ResourceOwnerReleaseAllPlanCacheRefs() */
-ResourceOwnerFuncs planref_resowner_funcs =
+static ResourceOwnerFuncs planref_resowner_funcs =
 {
 	.name = "plancache reference",
-	.phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_PLANCACHE_REFS,
 	.ReleaseResource = ResOwnerReleaseCachedPlan,
 	.PrintLeakWarning = ResOwnerPrintPlanCacheLeakWarning
 };
@@ -2228,13 +2228,20 @@ ResetPlanCache(void)
 }
 
 /*
- * ResourceOwner callbacks
+ * Release all CachedPlans remembered by 'owner'
  */
+void
+ReleaseAllPlanCacheRefsInOwner(ResourceOwner owner)
+{
+	ResourceOwnerReleaseAllOfKind(owner, &planref_resowner_funcs);
+}
+
+/* ResourceOwner callbacks */
 
 static void
 ResOwnerReleaseCachedPlan(Datum res)
 {
-	ReleaseCachedPlan((CachedPlan *) DatumGetPointer(res), CurrentResourceOwner);
+	ReleaseCachedPlan((CachedPlan *) DatumGetPointer(res), NULL);
 }
 
 static void
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 286155e20ac..26430859934 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -274,6 +274,7 @@ static HTAB *OpClassCache = NULL;
 
 /* non-export function prototypes */
 
+static void RelationCloseCleanup(Relation relation);
 static void RelationDestroyRelation(Relation relation, bool remember_tupdesc);
 static void RelationClearRelation(Relation relation, bool rebuild);
 
@@ -2123,7 +2124,8 @@ static void ResOwnerPrintRelCacheLeakWarning(Datum res);
 static ResourceOwnerFuncs relref_resowner_funcs =
 {
 	.name = "relcache reference",
-	.phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_RELCACHE_REFS,
 	.ReleaseResource = ResOwnerReleaseRelation,
 	.PrintLeakWarning = ResOwnerPrintRelCacheLeakWarning
 };
@@ -2181,6 +2183,12 @@ RelationClose(Relation relation)
 	/* Note: no locking manipulations needed */
 	RelationDecrementReferenceCount(relation);
 
+	RelationCloseCleanup(relation);
+}
+
+static void
+RelationCloseCleanup(Relation relation)
+{
 	/*
 	 * If the relation is no longer open in this session, we can clean up any
 	 * stale partition descriptors it has.  This is unlikely, so check to see
@@ -6809,7 +6817,7 @@ unlink_initfile(const char *initfilename, int elevel)
 static void
 ResOwnerPrintRelCacheLeakWarning(Datum res)
 {
-	Relation	rel = (Relation) res;
+	Relation	rel = (Relation) DatumGetPointer(res);
 
 	elog(WARNING, "relcache reference leak: relation \"%s\" not closed",
 		 RelationGetRelationName(rel));
@@ -6818,5 +6826,15 @@ ResOwnerPrintRelCacheLeakWarning(Datum res)
 static void
 ResOwnerReleaseRelation(Datum res)
 {
-	RelationClose((Relation) res);
+	Relation	rel = (Relation) DatumGetPointer(res);
+
+	/*
+	 * This reference has already been removed from ther resource owner, so
+	 * decrement reference count without calling
+	 * ResourceOwnerForgetRelationRef.
+	 */
+	Assert(rel->rd_refcnt > 0);
+	rel->rd_refcnt -= 1;
+
+	RelationCloseCleanup((Relation) res);
 }
diff --git a/src/backend/utils/resowner/README b/src/backend/utils/resowner/README
index e32f7a27384..2a703a395dc 100644
--- a/src/backend/utils/resowner/README
+++ b/src/backend/utils/resowner/README
@@ -85,3 +85,63 @@ CurrentResourceOwner must point to the same resource owner that was current
 when the buffer, lock, or cache reference was acquired.  It would be possible
 to relax this restriction given additional bookkeeping effort, but at present
 there seems no need.
+
+
+Releasing
+---------
+
+Releasing the resources of a ResourceOwner happens in three phases:
+
+1. "Before-locks" resources
+
+2. Locks
+
+3. "After-locks" resources
+
+Each resource type specifies whether it needs to be released before or after
+locks.  Each resource type also has a priority, which determines the order
+that the resources are released in.  Note that the phases are performed fully
+for the whole tree of resource owners, before moving to the next phase, but
+the priority within each phase only determines the order within that
+ResourceOwner.  Child resource owners are always handled before the parent,
+within each phase.
+
+For example, imagine that you have two ResourceOwners, parent and child,
+as follows:
+
+Parent
+  parent resource BEFORE_LOCKS priority 1
+  parent resource BEFORE_LOCKS priority 2
+  parent resource AFTER_LOCKS priority 10001
+  parent resource AFTER_LOCKS priority 10002
+  Child
+    child resource BEFORE_LOCKS priority 1
+    child resource BEFORE_LOCKS priority 2
+    child resource AFTER_LOCKS priority 10001
+    child resource AFTER_LOCKS priority 10002
+
+These resources would be released in the following order:
+
+child resource BEFORE_LOCKS priority 1
+child resource BEFORE_LOCKS priority 2
+parent resource BEFORE_LOCKS priority 1
+parent resource BEFORE_LOCKS priority 2
+(locks)
+child resource AFTER_LOCKS priority 10001
+child resource AFTER_LOCKS priority 10002
+parent resource AFTER_LOCKS priority 10001
+parent resource AFTER_LOCKS priority 10002
+
+To release all the resources, you need to call ResourceOwnerRelease() three
+times, once for each phase. You may perform additional tasks between the
+phases, but after the first call to ResourceOwnerRelease(), you cannot use
+the ResourceOwner to remember any more resources. You also cannot call
+ResourceOwnerForget on the resource owner to release any previously
+remembered resources "in retail", after you have started the release process.
+
+Normally, you are expected to call ResourceOwnerForget on every resource so
+that at commit, the ResourceOwner is empty (locks are an exception). If there
+are any resources still held at commit, ResourceOwnerRelease will call the
+PrintLeakWarning callback on each such resource. At abort, however, we truly
+rely on the ResourceOwner mechanism and it is normal that there are resources
+to be released.
diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index 7e594e3f2e4..6de09913bd0 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -6,7 +6,31 @@
  * Query-lifespan resources are tracked by associating them with
  * ResourceOwner objects.  This provides a simple mechanism for ensuring
  * that such resources are freed at the right time.
- * See utils/resowner/README for more info.
+ * See utils/resowner/README for more info on how to use it.
+ *
+ * The implementation consists of a small fixed-size array and a hash table.
+ * New entries are inserted to the fixed-size array, and when the array fills
+ * up, all the entries are moved to the hash table.  This way, the array
+ * always contains a few most recently remembered references.  To find a
+ * particular reference, you need to search both the array and the hash table.
+ *
+ * The most frequent usage is that a resource is remembered, and forgotten
+ * shortly thereafter.  For example, pin a buffer, read one tuple from it,
+ * release the pin.  Linearly scanning the small array handles that case
+ * efficiently.  However, some resources are held for a longer time, and
+ * sometimes a lot of resources need to be held simultaneously.  The hash
+ * table handles those cases.
+ *
+ * When it's time to release the resources, we sort them according to the
+ * release-priority of each resource, and release them in that order.
+ *
+ * Local lock references are special, they are not stored in the array or the
+ * hash table.  Instead, each resource owner has a separate small cache of
+ * locks it owns.  The lock manager has the same information in its local lock
+ * hash table, and we fall back on that if cache overflows, but traversing the
+ * hash table is slower when there are a lot of locks belonging to other
+ * resource owners.  This is to speed up bulk releasing or reassigning locks
+ * from a resource owner to its parent.
  *
  *
  * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
@@ -25,7 +49,6 @@
 #include "storage/predicate.h"
 #include "storage/proc.h"
 #include "utils/memutils.h"
-#include "utils/plancache.h"
 #include "utils/resowner.h"
 
 /*
@@ -41,33 +64,34 @@ typedef struct ResourceElem
 } ResourceElem;
 
 /*
- * Size of the small fixed-size array to hold most-recently remembered resources.
+ * Size of the fixed-size array to hold most-recently remembered resources.
  */
 #define RESOWNER_ARRAY_SIZE 32
 
 /*
- * Initially allocated size of a ResourceOwner's hash.  Must be power of two since
- * we'll use (capacity - 1) as mask for hashing.
+ * Initially allocated size of a ResourceOwner's hash table.  Must be power of
+ * two because we use (capacity - 1) as mask for hashing.
  */
 #define RESOWNER_HASH_INIT_SIZE 64
 
 /*
- * How many items may be stored in a hash of given capacity.
- * When this number is reached, we must resize.
+ * How many items may be stored in a hash table of given capacity.  When this
+ * number is reached, we must resize.
+ *
+ * The hash table must always have enough free space that we can copy the
+ * entries from the array to it, in ResouceOwnerSort.  We also insist that the
+ * initial size is large enough that we don't hit the max size immediately
+ * when it's created. Aside from those limitations, 0.75 is a reasonable fill
+ * factor.
  */
-#define RESOWNER_HASH_MAX_ITEMS(capacity) ((capacity)/4 * 3)
+#define RESOWNER_HASH_MAX_ITEMS(capacity) \
+	Min(capacity - RESOWNER_ARRAY_SIZE, (capacity)/4 * 3)
 
-StaticAssertDecl(RESOWNER_HASH_MAX_ITEMS(RESOWNER_HASH_INIT_SIZE) > RESOWNER_ARRAY_SIZE,
+StaticAssertDecl(RESOWNER_HASH_MAX_ITEMS(RESOWNER_HASH_INIT_SIZE) >= RESOWNER_ARRAY_SIZE,
 				 "initial hash size too small compared to array size");
 
 /*
- * To speed up bulk releasing or reassigning locks from a resource owner to
- * its parent, each resource owner has a small cache of locks it owns. The
- * lock manager has the same information in its local lock hash table, and
- * we fall back on that if cache overflows, but traversing the hash table
- * is slower when there are a lot of locks belonging to other resource owners.
- *
- * MAX_RESOWNER_LOCKS is the size of the per-resource owner cache. It's
+ * MAX_RESOWNER_LOCKS is the size of the per-resource owner locks cache. It's
  * chosen based on some testing with pg_dump with a large schema. When the
  * tests were done (on 9.2), resource owners in a pg_dump run contained up
  * to 9 locks, regardless of the schema size, except for the top resource
@@ -88,33 +112,35 @@ typedef struct ResourceOwnerData
 	ResourceOwner nextchild;	/* next child of same parent */
 	const char *name;			/* name (just for debugging) */
 
+	bool		releasing;		/* has ResourceOwnerRelease been called? */
+
 	/*
-	 * These structs keep track of the objects registered with this owner.
+	 * The fixed-size array for recent resources.
 	 *
-	 * We manage a small set of references by keeping them in a simple array.
-	 * When the array gets full, all the elements in the array are moved to a
-	 * hash table.  This way, the array always contains a few most recently
-	 * remembered references.  To find a particular reference, you need to
-	 * search both the array and the hash table.
+	 * If 'releasing' is set, the contents are sorted by release priority.
 	 */
-	ResourceElem arr[RESOWNER_ARRAY_SIZE];
 	uint32		narr;			/* how many items are stored in the array */
+	ResourceElem arr[RESOWNER_ARRAY_SIZE];
 
 	/*
 	 * The hash table.  Uses open-addressing.  'nhash' is the number of items
 	 * present; if it would exceed 'grow_at', we enlarge it and re-hash.
 	 * 'grow_at' should be rather less than 'capacity' so that we don't waste
 	 * too much time searching for empty slots.
+	 *
+	 * If 'releasing' is set, the contents are no longer hashed, but sorted by
+	 * release priority.  The first 'nhash' elements are occupied, the rest
+	 * are empty.
 	 */
 	ResourceElem *hash;
 	uint32		nhash;			/* how many items are stored in the hash */
 	uint32		capacity;		/* allocated length of hash[] */
 	uint32		grow_at;		/* grow hash when reach this */
 
-	/* We can remember up to MAX_RESOWNER_LOCKS references to local locks. */
-	int			nlocks;			/* number of owned locks */
+	/* The local locks cache. */
+	uint32		nlocks;			/* number of owned locks */
 	LOCALLOCK  *locks[MAX_RESOWNER_LOCKS];	/* list of owned locks */
-} ResourceOwnerData;
+}			ResourceOwnerData;

 /*****************************************************************************
@@ -155,6 +181,8 @@ static ResourceReleaseCallbackItem *ResourceRelease_callbacks = NULL;
 static inline uint32 hash_resource_elem(Datum value, ResourceOwnerFuncs *kind);
 static void ResourceOwnerAddToHash(ResourceOwner owner, Datum value,
 								   ResourceOwnerFuncs *kind);
+static int resource_priority_cmp(const void *a, const void *b);
+static void ResourceOwnerSort(ResourceOwner owner);
 static void ResourceOwnerReleaseAll(ResourceOwner owner,
 									ResourceReleasePhase phase,
 									bool printLeakWarnings);
@@ -186,12 +214,12 @@ hash_resource_elem(Datum value, ResourceOwnerFuncs *kind)
 static void
 ResourceOwnerAddToHash(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind)
 {
-	/* Insert into first free slot at or after hash location. */
 	uint32		mask = owner->capacity - 1;
 	uint32		idx;
 
 	Assert(kind != NULL);
 
+	/* Insert into first free slot at or after hash location. */
 	idx = hash_resource_elem(value, kind) & mask;
 	for (;;)
 	{
@@ -205,84 +233,140 @@ ResourceOwnerAddToHash(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kin
 }
 
 /*
- * Call the ReleaseResource callback on entries with given 'phase'.
+ * Comparison function to sort by release phase and priority
+ */
+static int
+resource_priority_cmp(const void *a, const void *b)
+{
+	const ResourceElem *ra = (const ResourceElem *) a;
+	const ResourceElem *rb = (const ResourceElem *) b;
+
+	/* Note: reverse order */
+	if (ra->kind->release_phase == rb->kind->release_phase)
+	{
+		if (ra->kind->release_priority == rb->kind->release_priority)
+			return 0;
+		else if (ra->kind->release_priority > rb->kind->release_priority)
+			return -1;
+		else
+			return 1;
+	}
+	else if (ra->kind->release_phase > rb->kind->release_phase)
+		return -1;
+	else
+		return 1;
+}
+
+/*
+ * Sort resources in reverse release priority.
+ *
+ * If the hash table is in use, all the elements from the fixed-size array are
+ * moved to the hash table, and then the hash table is sorted.  If there is no
+ * hash table, then the fixed-size array is sorted directly.  In either case,
+ * the result is one sorted array that contains all the resources.
  */
 static void
-ResourceOwnerReleaseAll(ResourceOwner owner, ResourceReleasePhase phase,
-						bool printLeakWarnings)
+ResourceOwnerSort(ResourceOwner owner)
 {
-	bool		found;
-	int			capacity;
+	ResourceElem *items;
+	uint32		nitems;
 
-	/*
-	 * First handle all the entries in the array.
-	 *
-	 * Note that ReleaseResource() will call ResourceOwnerForget) and remove
-	 * the entry from our array, so we just have to iterate till there is
-	 * nothing left to remove.
-	 */
-	do
+	if (!owner->hash)
 	{
-		found = false;
-		for (int i = 0; i < owner->narr; i++)
+		items = owner->arr;
+		nitems = owner->narr;
+	}
+	else
+	{
+		/*
+		 * Compact the hash table, so that all the elements are in the
+		 * beginning of the 'hash' array, with no empty elements.
+		 */
+		uint32		dst = 0;
+
+		for (int idx = 0; idx < owner->capacity; idx++)
 		{
-			if (owner->arr[i].kind->phase == phase)
+			if (owner->hash[idx].kind != NULL)
 			{
-				Datum		value = owner->arr[i].item;
-				ResourceOwnerFuncs *kind = owner->arr[i].kind;
-
-				if (printLeakWarnings)
-					kind->PrintLeakWarning(value);
-				kind->ReleaseResource(value);
-				found = true;
+				if (dst != idx)
+					owner->hash[dst] = owner->hash[idx];
+				dst++;
 			}
 		}
 
 		/*
-		 * If any resources were released, check again because some of the
-		 * elements might have been moved by the callbacks.  We don't want to
-		 * miss them.
+		 * Move all entries from the fixed-size array to 'hash'.
+		 *
+		 * RESOWNER_HASH_MAX_ITEMS is defined so that there is always enough
+		 * free space to move all the elements from the fixed-size array to
+		 * the hash.
 		 */
-	} while (found && owner->narr > 0);
+		Assert(dst + owner->narr <= owner->capacity);
+		for (int idx = 0; idx < owner->narr; idx++)
+		{
+			owner->hash[dst] = owner->arr[idx];
+			dst++;
+		}
+		Assert(dst == owner->nhash + owner->narr);
+		owner->narr = 0;
+		owner->nhash = dst;
+
+		items = owner->hash;
+		nitems = owner->nhash;
+	}
+
+	qsort(items, nitems, sizeof(ResourceElem), resource_priority_cmp);
+}
+
+/*
+ * Call the ReleaseResource callback on entries with given 'phase'.
+ */
+static void
+ResourceOwnerReleaseAll(ResourceOwner owner, ResourceReleasePhase phase,
+						bool printLeakWarnings)
+{
+	ResourceElem *items;
+	uint32		nitems;
+
+	/* ResourceOwnerSort must've been called already */
+	Assert(owner->releasing);
+	if (!owner->hash)
+	{
+		items = owner->arr;
+		nitems = owner->narr;
+	}
+	else
+	{
+		Assert(owner->narr == 0);
+		items = owner->hash;
+		nitems = owner->nhash;
+	}
 
 	/*
-	 * Ok, the array has now been handled.  Then the hash.  Like with the
-	 * array, ReleaseResource() will remove the entry from the hash.
+	 * The resources are sorted in reverse priority order.  Release them
+	 * starting from the end, until we hit the end of the phase that we are
+	 * releasing now.  We will continue from there when called again for the
+	 * next phase.
 	 */
-	do
+	while (nitems > 0)
 	{
-		capacity = owner->capacity;
-		for (int idx = 0; idx < capacity; idx++)
-		{
-			while (owner->hash[idx].kind != NULL &&
-				   owner->hash[idx].kind->phase == phase)
-			{
-				Datum		value = owner->hash[idx].item;
-				ResourceOwnerFuncs *kind = owner->hash[idx].kind;
-
-				if (printLeakWarnings)
-					kind->PrintLeakWarning(value);
-				kind->ReleaseResource(value);
-
-				/*
-				 * If the same resource is remembered more than once in this
-				 * resource owner, the ReleaseResource callback might've
-				 * released a different copy of it.  Because of that, loop to
-				 * check the same index again.
-				 */
-			}
-		}
+		uint32		idx = nitems - 1;
+		Datum		value = items[idx].item;
+		ResourceOwnerFuncs *kind = items[idx].kind;
 
-		/*
-		 * It's possible that the callbacks acquired more resources, causing
-		 * the hash table to grow and the existing entries to be moved around.
-		 * If that happened, scan the hash table again, so that we don't miss
-		 * entries that were moved.  (XXX: I'm not sure if any of the
-		 * callbacks actually do that, but this is cheap to check, and better
-		 * safe than sorry.)
-		 */
-		Assert(owner->capacity >= capacity);
-	} while (capacity != owner->capacity);
+		if (kind->release_phase > phase)
+			break;
+		Assert(kind->release_phase == phase);
+
+		if (printLeakWarnings)
+			kind->PrintLeakWarning(value);
+		kind->ReleaseResource(value);
+		nitems--;
+	}
+	if (!owner->hash)
+		owner->narr = nitems;
+	else
+		owner->nhash = nitems;
 }

@@ -335,10 +419,16 @@ ResourceOwnerCreate(ResourceOwner parent, const char *name)
 void
 ResourceOwnerEnlarge(ResourceOwner owner)
 {
+	/* Mustn't try to remember more resources after we have already started releasing */
+	if (owner->releasing)
+		elog(ERROR, "ResourceOwnerEnlarge called after release started");
+
 	if (owner->narr < RESOWNER_ARRAY_SIZE)
 		return;					/* no work needed */
 
-	/* Is there space in the hash? If not, enlarge it. */
+	/*
+	 * Is there space in the hash? If not, enlarge it.
+	 */
 	if (owner->narr + owner->nhash >= owner->grow_at)
 	{
 		uint32		i,
@@ -383,14 +473,11 @@ ResourceOwnerEnlarge(ResourceOwner owner)
 	}
 
 	/* Move items from the array to the hash */
-	Assert(owner->narr == RESOWNER_ARRAY_SIZE);
 	for (int i = 0; i < owner->narr; i++)
-	{
 		ResourceOwnerAddToHash(owner, owner->arr[i].item, owner->arr[i].kind);
-	}
 	owner->narr = 0;
 
-	Assert(owner->nhash < owner->grow_at);
+	Assert(owner->nhash <= owner->grow_at);
 }
 
 /*
@@ -408,13 +495,19 @@ ResourceOwnerRemember(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind
 		 resowner_trace_counter++, owner, DatumGetUInt64(value), kind->name);
 #endif
 
+	/*
+	 * Mustn't try to remember more resources after we have already started
+	 * releasing.  We already checked this in ResourceOwnerEnlarge.
+	 */
+	Assert(!owner->releasing);
+
 	if (owner->narr >= RESOWNER_ARRAY_SIZE)
 	{
 		/* forgot to call ResourceOwnerEnlarge? */
 		elog(ERROR, "ResourceOwnerRemember called but array was full");
 	}
 
-	/* Append to linear array. */
+	/* Append to the array. */
 	idx = owner->narr;
 	owner->arr[idx].item = value;
 	owner->arr[idx].kind = kind;
@@ -424,22 +517,27 @@ ResourceOwnerRemember(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind
 /*
  * Forget that an object is owned by a ResourceOwner
  *
- * Note: if same resource ID is associated with the ResourceOwner more than once,
- * one instance is removed.
+ * Note: if same resource ID is associated with the ResourceOwner more than
+ * once, one instance is removed.
  */
 void
 ResourceOwnerForget(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind)
 {
-	uint32		i,
-				idx;
-
 #ifdef RESOWNER_TRACE
 	elog(LOG, "FORGET %d: owner %p value " UINT64_FORMAT ", kind: %s",
 		 resowner_trace_counter++, owner, DatumGetUInt64(value), kind->name);
 #endif
 
-	/* Search through all items, but check the array first. */
-	for (i = 0; i < owner->narr; i++)
+	/*
+	 * Mustn't call this after we have already started releasing resources.
+	 * (Release callback functions are not allowed to release additional
+	 * resources.)
+	 */
+	if (owner->releasing)
+		elog(ERROR, "ResourceOwnerForget called for %s after release started", kind->name);
+
+	/* Search through all items in the array first. */
+	for (uint32 i = 0; i < owner->narr; i++)
 	{
 		if (owner->arr[i].item == value &&
 			owner->arr[i].kind == kind)
@@ -459,9 +557,10 @@ ResourceOwnerForget(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind)
 	if (owner->nhash > 0)
 	{
 		uint32		mask = owner->capacity - 1;
+		uint32		idx;
 
 		idx = hash_resource_elem(value, kind) & mask;
-		for (i = 0; i < owner->capacity; i++)
+		for (uint32 i = 0; i < owner->capacity; i++)
 		{
 			if (owner->hash[idx].item == value &&
 				owner->hash[idx].kind == kind)
@@ -473,6 +572,7 @@ ResourceOwnerForget(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind)
 #ifdef RESOWNER_STATS
 				nhash_lookups++;
 #endif
+
 				return;
 			}
 			idx = (idx + 1) & mask;
@@ -513,6 +613,12 @@ ResourceOwnerForget(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind)
  * isTopLevel is passed when we are releasing TopTransactionResourceOwner
  * at completion of a main transaction.  This generally means that *all*
  * resources will be released, and so we can optimize things a bit.
+ *
+ * NOTE: After starting the release process, by calling this function, no new
+ * resources can be remembered in the resource owner.  You also cannot call
+ * ResourceOwnerForget on any previously remembered resources to release
+ * resources "in retail" after that, you must let the bulk release take care
+ * of them.
  */
 void
 ResourceOwnerRelease(ResourceOwner owner,
@@ -549,8 +655,32 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 		ResourceOwnerReleaseInternal(child, phase, isCommit, isTopLevel);
 
 	/*
-	 * Make CurrentResourceOwner point to me, so that ReleaseBuffer etc don't
-	 * get confused.
+	 * To release the resources in the right order, sort them by phase and
+	 * priority.
+	 *
+	 * The ReleaseResource callback functions are not allowed to remember or
+	 * forget any other resources after this. Otherwise we lose track of where
+	 * we are in processing the hash/array.
+	 */
+	if (!owner->releasing)
+	{
+		Assert(phase == RESOURCE_RELEASE_BEFORE_LOCKS);
+		ResourceOwnerSort(owner);
+		owner->releasing = true;
+	}
+	else
+	{
+		/*
+		 * Phase is normally > RESOURCE_RELEASE_BEFORE_LOCKS, if this is not
+		 * the first call to ReleaseOwnerRelease. But if an error happens
+		 * between the release phases, we might get called again for the same
+		 * ResourceOwner from AbortTransaction.
+		 */
+	}
+
+	/*
+	 * Make CurrentResourceOwner point to me, so that the release callback
+	 * functions know which resource owner is been released.
 	 */
 	save = CurrentResourceOwner;
 	CurrentResourceOwner = owner;
@@ -634,52 +764,53 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 }
 
 /*
- * ResourceOwnerReleaseAllPlanCacheRefs
- *		Release the plancache references (only) held by this owner.
- *
- * We might eventually add similar functions for other resource types,
- * but for now, only this is needed.
+ * ResourceOwnerReleaseAllOfKind
+ *		Release all resources of a certain type held by this owner.
  */
 void
-ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner)
+ResourceOwnerReleaseAllOfKind(ResourceOwner owner, ResourceOwnerFuncs *kind)
 {
-	/* array first */
+	/* Mustn't call this after we have already started releasing resources. */
+	if (owner->releasing)
+		elog(ERROR, "ResourceOwnerForget called for %s after release started", kind->name);
+
+	/*
+	 * Temporarily set 'releasing', to prevent calls to ResourceOwnerRemember
+	 * while we're scanning the owner.  Enlarging the hash would cause us to
+	 * lose track of the point we're scanning.
+	 */
+	owner->releasing = true;
+
+	/* Array first */
 	for (int i = 0; i < owner->narr; i++)
 	{
-		if (owner->arr[i].kind == &planref_resowner_funcs)
+		if (owner->arr[i].kind == kind)
 		{
-			CachedPlan *planref = (CachedPlan *) DatumGetPointer(owner->arr[i].item);
+			Datum		value = owner->arr[i].item;
 
 			owner->arr[i] = owner->arr[owner->narr - 1];
 			owner->narr--;
 			i--;
 
-			/*
-			 * pass 'NULL' because we already removed the entry from the
-			 * resowner
-			 */
-			ReleaseCachedPlan(planref, NULL);
+			kind->ReleaseResource(value);
 		}
 	}
 
 	/* Then hash */
 	for (int i = 0; i < owner->capacity; i++)
 	{
-		if (owner->hash[i].kind == &planref_resowner_funcs)
+		if (owner->hash[i].kind == kind)
 		{
-			CachedPlan *planref = (CachedPlan *) DatumGetPointer(owner->hash[i].item);
+			Datum		value = owner->hash[i].item;
 
 			owner->hash[i].item = (Datum) 0;
 			owner->hash[i].kind = NULL;
 			owner->nhash--;
 
-			/*
-			 * pass 'NULL' because we already removed the entry from the
-			 * resowner
-			 */
-			ReleaseCachedPlan(planref, NULL);
+			kind->ReleaseResource(value);
 		}
 	}
+	owner->releasing = false;
 }
 
 /*
@@ -857,6 +988,8 @@ ReleaseAuxProcessResources(bool isCommit)
 	ResourceOwnerRelease(AuxProcessResourceOwner,
 						 RESOURCE_RELEASE_AFTER_LOCKS,
 						 isCommit, true);
+	/* allow it to be reused */
+	AuxProcessResourceOwner->releasing = false;
 }
 
 /*
@@ -874,7 +1007,7 @@ ReleaseAuxProcessResourcesCallback(int code, Datum arg)
 /*
  * Remember that a Local Lock is owned by a ResourceOwner
  *
- * This is different from the other Remember functions in that the list of
+ * This is different from the generic ResourceOwnerRemember in that the list of
  * locks is only a lossy cache.  It can hold up to MAX_RESOWNER_LOCKS entries,
  * and when it overflows, we stop tracking locks.  The point of only remembering
  * only up to MAX_RESOWNER_LOCKS entries is that if a lot of locks are held,
diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c
index 7da51381cd2..6ab630821a8 100644
--- a/src/backend/utils/time/snapmgr.c
+++ b/src/backend/utils/time/snapmgr.c
@@ -173,6 +173,7 @@ static List *exportedSnapshots = NIL;
 /* Prototypes for local functions */
 static TimestampTz AlignTimestampToMinuteBoundary(TimestampTz ts);
 static Snapshot CopySnapshot(Snapshot snapshot);
+static void UnregisterSnapshotNoOwner(Snapshot snapshot);
 static void FreeSnapshot(Snapshot snapshot);
 static void SnapshotResetXmin(void);
 
@@ -183,7 +184,8 @@ static void ResOwnerPrintSnapshotLeakWarning(Datum res);
 static ResourceOwnerFuncs snapshot_resowner_funcs =
 {
 	.name = "snapshot reference",
-	.phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_SNAPSHOT_REFS,
 	.ReleaseResource = ResOwnerReleaseSnapshot,
 	.PrintLeakWarning = ResOwnerPrintSnapshotLeakWarning
 };
@@ -905,11 +907,16 @@ UnregisterSnapshotFromOwner(Snapshot snapshot, ResourceOwner owner)
 	if (snapshot == NULL)
 		return;
 
+	ResourceOwnerForgetSnapshot(owner, snapshot);
+	UnregisterSnapshotNoOwner(snapshot);
+}
+
+static void
+UnregisterSnapshotNoOwner(Snapshot snapshot)
+{
 	Assert(snapshot->regd_count > 0);
 	Assert(!pairingheap_is_empty(&RegisteredSnapshots));
 
-	ResourceOwnerForgetSnapshot(owner, snapshot);
-
 	snapshot->regd_count--;
 	if (snapshot->regd_count == 0)
 		pairingheap_remove(&RegisteredSnapshots, &snapshot->ph_node);
@@ -2399,13 +2406,12 @@ XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot)
 	return false;
 }
 
-/*
- * ResourceOwner callbacks
- */
+/* ResourceOwner callbacks */
+
 static void
 ResOwnerReleaseSnapshot(Datum res)
 {
-	UnregisterSnapshot((Snapshot) DatumGetPointer(res));
+	UnregisterSnapshotNoOwner((Snapshot) DatumGetPointer(res));
 }
 
 static void
diff --git a/src/common/cryptohash_openssl.c b/src/common/cryptohash_openssl.c
index 0fb6dd6736d..5254e53f114 100644
--- a/src/common/cryptohash_openssl.c
+++ b/src/common/cryptohash_openssl.c
@@ -80,9 +80,9 @@ static void ResOwnerPrintCryptoHashLeakWarning(Datum res);
 
 static ResourceOwnerFuncs cryptohash_resowner_funcs =
 {
-	/* relcache references */
 	.name = "OpenSSL cryptohash context",
-	.phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_CRYPTOHASH_CONTEXTS,
 	.ReleaseResource = ResOwnerReleaseCryptoHash,
 	.PrintLeakWarning = ResOwnerPrintCryptoHashLeakWarning,
 };
@@ -326,7 +326,8 @@ pg_cryptohash_free(pg_cryptohash_ctx *ctx)
 	EVP_MD_CTX_destroy(ctx->evpctx);
 
 #ifndef FRONTEND
-	ResourceOwnerForgetCryptoHash(ctx->resowner, ctx);
+	if (ctx->resowner)
+		ResourceOwnerForgetCryptoHash(ctx->resowner, ctx);
 #endif
 
 	explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
@@ -370,14 +371,16 @@ pg_cryptohash_error(pg_cryptohash_ctx *ctx)
 	return _("success");
 }
 
-/*
- * ResourceOwner callbacks
- */
+/* ResourceOwner callbacks */
+
 #ifndef FRONTEND
 static void
 ResOwnerReleaseCryptoHash(Datum res)
 {
-	pg_cryptohash_free((pg_cryptohash_ctx *) DatumGetPointer(res));
+	pg_cryptohash_ctx *ctx = (pg_cryptohash_ctx *) DatumGetPointer(res);
+
+	ctx->resowner = NULL;
+	pg_cryptohash_free(ctx);
 }
 
 static void
diff --git a/src/common/hmac_openssl.c b/src/common/hmac_openssl.c
index 9f68462fb4a..9b47ad63ba1 100644
--- a/src/common/hmac_openssl.c
+++ b/src/common/hmac_openssl.c
@@ -79,9 +79,9 @@ static void ResOwnerPrintHMACLeakWarning(Datum res);
 
 static ResourceOwnerFuncs hmac_resowner_funcs =
 {
-	/* relcache references */
 	.name = "OpenSSL HMAC context",
-	.phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_HMAC_CONTEXTS,
 	.ReleaseResource = ResOwnerReleaseHMAC,
 	.PrintLeakWarning = ResOwnerPrintHMACLeakWarning,
 };
@@ -323,7 +323,8 @@ pg_hmac_free(pg_hmac_ctx *ctx)
 #ifdef HAVE_HMAC_CTX_FREE
 	HMAC_CTX_free(ctx->hmacctx);
 #ifndef FRONTEND
-	ResourceOwnerForgetHMAC(ctx->resowner, ctx);
+	if (ctx->resowner)
+		ResourceOwnerForgetHMAC(ctx->resowner, ctx);
 #endif
 #else
 	explicit_bzero(ctx->hmacctx, sizeof(HMAC_CTX));
@@ -367,14 +368,16 @@ pg_hmac_error(pg_hmac_ctx *ctx)
 	return _("success");
 }
 
-/*
- * ResourceOwner callbacks
- */
+/* ResourceOwner callbacks */
+
 #ifndef FRONTEND
 static void
 ResOwnerReleaseHMAC(Datum res)
 {
-	pg_hmac_free((pg_hmac_ctx *) DatumGetPointer(res));
+	pg_hmac_ctx *ctx = (pg_hmac_ctx *) DatumGetPointer(res);
+
+	ctx->resowner = NULL;
+	pg_hmac_free(ctx);
 }
 
 static void
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index bc4fdec36ec..60b4904a4d9 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -188,6 +188,8 @@ typedef struct CachedExpression
 extern void InitPlanCache(void);
 extern void ResetPlanCache(void);
 
+extern void ReleaseAllPlanCacheRefsInOwner(ResourceOwner owner);
+
 extern CachedPlanSource *CreateCachedPlan(struct RawStmt *raw_parse_tree,
 										  const char *query_string,
 										  CommandTag commandTag);
@@ -233,6 +235,4 @@ extern bool CachedPlanIsSimplyValid(CachedPlanSource *plansource,
 extern CachedExpression *GetCachedExpression(Node *expr);
 extern void FreeCachedExpression(CachedExpression *cexpr);
 
-extern ResourceOwnerFuncs planref_resowner_funcs;
-
 #endif							/* PLANCACHE_H */
diff --git a/src/include/utils/resowner.h b/src/include/utils/resowner.h
index 441b346bdfb..0e0a32548c5 100644
--- a/src/include/utils/resowner.h
+++ b/src/include/utils/resowner.h
@@ -42,6 +42,11 @@ extern PGDLLIMPORT ResourceOwner AuxProcessResourceOwner;
  * when we release a lock that another backend may be waiting on, it will
  * see us as being fully out of our transaction.  The post-lock phase
  * should be used for backend-internal cleanup.
+ *
+ * Within each phase, resources are released in priority order.  Priority is
+ * just an integer specified in ResourceOwnerFuncs.  The priorities of
+ * built-in resource types are given below, extensions may use any priority
+ * relative to those or RELEASE_PRIO_FIRST/LAST.
  */
 typedef enum
 {
@@ -50,6 +55,27 @@ typedef enum
 	RESOURCE_RELEASE_AFTER_LOCKS
 } ResourceReleasePhase;
 
+typedef uint32 ResourceReleasePriority;
+
+/* priorities of built-in BEFORE_LOCKS resources */
+#define RELEASE_PRIO_BUFFERS			    100
+#define RELEASE_PRIO_RELCACHE_REFS			200
+#define RELEASE_PRIO_DSMS					300
+#define RELEASE_PRIO_JIT_CONTEXTS			400
+#define RELEASE_PRIO_CRYPTOHASH_CONTEXTS	500
+#define RELEASE_PRIO_HMAC_CONTEXTS			600
+
+/* priorities of built-in AFTER_LOCKS resources */
+#define RELEASE_PRIO_CATCACHE_REFS			100
+#define RELEASE_PRIO_CATCACHE_LIST_REFS		200
+#define RELEASE_PRIO_PLANCACHE_REFS			300
+#define RELEASE_PRIO_TUPDESC_REFS			400
+#define RELEASE_PRIO_SNAPSHOT_REFS			500
+#define RELEASE_PRIO_FILES					600
+
+#define RELEASE_PRIO_FIRST					0
+#define RELEASE_PRIO_LAST					UINT32_MAX
+
 /*
  * In order to track an object, resowner.c needs a few callbacks for it.
  * The callbacks for resources of a specific kind are encapsulated in
@@ -62,13 +88,18 @@ typedef struct ResourceOwnerFuncs
 {
 	const char *name;			/* name for the object kind, for debugging */
 
-	ResourceReleasePhase phase; /* when are these objects released? */
+	/* when are these objects released? */
+	ResourceReleasePhase release_phase;
+	ResourceReleasePriority release_priority;
 
 	/*
 	 * Release resource.
 	 *
-	 * NOTE: this must call ResourceOwnerForget to disassociate it with the
-	 * resource owner.
+	 * This is called for each resource in the resource owner, in the order
+	 * specified by 'release_phase' and 'release_priority' when the whole
+	 * resource owner is been released or when ResourceOwnerReleaseAllOfKind()
+	 * is called.  The resource is implicitly removed from the owner, the
+	 * callback function doesn't need to call ResourceOwnerForget.
 	 */
 	void		(*ReleaseResource) (Datum res);
 
@@ -110,6 +141,8 @@ extern void ResourceOwnerEnlarge(ResourceOwner owner);
 extern void ResourceOwnerRemember(ResourceOwner owner, Datum res, ResourceOwnerFuncs *kind);
 extern void ResourceOwnerForget(ResourceOwner owner, Datum res, ResourceOwnerFuncs *kind);
 
+extern void ResourceOwnerReleaseAllOfKind(ResourceOwner owner, ResourceOwnerFuncs *kind);
+
 extern void RegisterResourceReleaseCallback(ResourceReleaseCallback callback,
 											void *arg);
 extern void UnregisterResourceReleaseCallback(ResourceReleaseCallback callback,
@@ -123,7 +156,4 @@ struct LOCALLOCK;
 extern void ResourceOwnerRememberLock(ResourceOwner owner, struct LOCALLOCK *locallock);
 extern void ResourceOwnerForgetLock(ResourceOwner owner, struct LOCALLOCK *locallock);
 
-/* special function to relase all plancache references */
-extern void ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner);
-
 #endif							/* RESOWNER_H */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index ffd6d2e3bc3..e3cda017a9b 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -8431,7 +8431,7 @@ plpgsql_xact_cb(XactEvent event, void *arg)
 			FreeExecutorState(shared_simple_eval_estate);
 		shared_simple_eval_estate = NULL;
 		if (shared_simple_eval_resowner)
-			ResourceOwnerReleaseAllPlanCacheRefs(shared_simple_eval_resowner);
+			ReleaseAllPlanCacheRefsInOwner(shared_simple_eval_resowner);
 		shared_simple_eval_resowner = NULL;
 	}
 	else if (event == XACT_EVENT_ABORT ||
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index d8994538b76..462ba438d61 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -288,7 +288,7 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
 		/* Be sure to release the procedure resowner if any */
 		if (procedure_resowner)
 		{
-			ResourceOwnerReleaseAllPlanCacheRefs(procedure_resowner);
+			ReleaseAllPlanCacheRefsInOwner(procedure_resowner);
 			ResourceOwnerDelete(procedure_resowner);
 		}
 	}
@@ -393,7 +393,7 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS)
 
 		/* Clean up the private EState and resowner */
 		FreeExecutorState(simple_eval_estate);
-		ResourceOwnerReleaseAllPlanCacheRefs(simple_eval_resowner);
+		ReleaseAllPlanCacheRefsInOwner(simple_eval_resowner);
 		ResourceOwnerDelete(simple_eval_resowner);
 
 		/* Function should now have no remaining use-counts ... */
@@ -410,7 +410,7 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS)
 
 	/* Clean up the private EState and resowner */
 	FreeExecutorState(simple_eval_estate);
-	ResourceOwnerReleaseAllPlanCacheRefs(simple_eval_resowner);
+	ReleaseAllPlanCacheRefsInOwner(simple_eval_resowner);
 	ResourceOwnerDelete(simple_eval_resowner);
 
 	/* Function should now have no remaining use-counts ... */
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index c629cbe3830..cc0abadf750 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -30,6 +30,7 @@ SUBDIRS = \
 		  test_predtest \
 		  test_rbtree \
 		  test_regex \
+		  test_resowner \
 		  test_rls_hooks \
 		  test_shm_mq \
 		  test_slru \
diff --git a/src/test/modules/test_resowner/.gitignore b/src/test/modules/test_resowner/.gitignore
new file mode 100644
index 00000000000..5dcb3ff9723
--- /dev/null
+++ b/src/test/modules/test_resowner/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_resowner/Makefile b/src/test/modules/test_resowner/Makefile
new file mode 100644
index 00000000000..d28da3c2869
--- /dev/null
+++ b/src/test/modules/test_resowner/Makefile
@@ -0,0 +1,24 @@
+# src/test/modules/test_resowner/Makefile
+
+MODULE_big = test_resowner
+OBJS = \
+	$(WIN32RES) \
+	test_resowner_basic.o \
+	test_resowner_many.o
+PGFILEDESC = "test_resowner - test code for ResourceOwners"
+
+EXTENSION = test_resowner
+DATA = test_resowner--1.0.sql
+
+REGRESS = test_resowner
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_resowner
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_resowner/expected/test_resowner.out b/src/test/modules/test_resowner/expected/test_resowner.out
new file mode 100644
index 00000000000..c7865e3ac19
--- /dev/null
+++ b/src/test/modules/test_resowner/expected/test_resowner.out
@@ -0,0 +1,197 @@
+CREATE EXTENSION test_resowner;
+-- This is small enough that everything fits in the small array
+SELECT test_resowner_priorities(2, 3);
+NOTICE:  releasing resources before locks
+NOTICE:  releasing string: child before locks priority 0
+NOTICE:  releasing string: child before locks priority 0
+NOTICE:  releasing string: child before locks priority 1
+NOTICE:  releasing string: parent before locks priority 0
+NOTICE:  releasing string: parent before locks priority 0
+NOTICE:  releasing string: parent before locks priority 1
+NOTICE:  releasing locks
+NOTICE:  releasing resources after locks
+NOTICE:  releasing string: child after locks priority 0
+NOTICE:  releasing string: child after locks priority 0
+NOTICE:  releasing string: child after locks priority 1
+NOTICE:  releasing string: parent after locks priority 0
+NOTICE:  releasing string: parent after locks priority 0
+NOTICE:  releasing string: parent after locks priority 1
+ test_resowner_priorities 
+--------------------------
+ 
+(1 row)
+
+-- Same test with more resources, to exercise the hash table
+SELECT test_resowner_priorities(2, 32);
+NOTICE:  releasing resources before locks
+NOTICE:  releasing string: child before locks priority 0
+NOTICE:  releasing string: child before locks priority 0
+NOTICE:  releasing string: child before locks priority 0
+NOTICE:  releasing string: child before locks priority 0
+NOTICE:  releasing string: child before locks priority 0
+NOTICE:  releasing string: child before locks priority 0
+NOTICE:  releasing string: child before locks priority 0
+NOTICE:  releasing string: child before locks priority 0
+NOTICE:  releasing string: child before locks priority 0
+NOTICE:  releasing string: child before locks priority 0
+NOTICE:  releasing string: child before locks priority 0
+NOTICE:  releasing string: child before locks priority 0
+NOTICE:  releasing string: child before locks priority 0
+NOTICE:  releasing string: child before locks priority 0
+NOTICE:  releasing string: child before locks priority 0
+NOTICE:  releasing string: child before locks priority 0
+NOTICE:  releasing string: child before locks priority 1
+NOTICE:  releasing string: child before locks priority 1
+NOTICE:  releasing string: child before locks priority 1
+NOTICE:  releasing string: child before locks priority 1
+NOTICE:  releasing string: child before locks priority 1
+NOTICE:  releasing string: child before locks priority 1
+NOTICE:  releasing string: child before locks priority 1
+NOTICE:  releasing string: child before locks priority 1
+NOTICE:  releasing string: child before locks priority 1
+NOTICE:  releasing string: child before locks priority 1
+NOTICE:  releasing string: child before locks priority 1
+NOTICE:  releasing string: child before locks priority 1
+NOTICE:  releasing string: child before locks priority 1
+NOTICE:  releasing string: child before locks priority 1
+NOTICE:  releasing string: child before locks priority 1
+NOTICE:  releasing string: child before locks priority 1
+NOTICE:  releasing string: parent before locks priority 0
+NOTICE:  releasing string: parent before locks priority 0
+NOTICE:  releasing string: parent before locks priority 0
+NOTICE:  releasing string: parent before locks priority 0
+NOTICE:  releasing string: parent before locks priority 0
+NOTICE:  releasing string: parent before locks priority 0
+NOTICE:  releasing string: parent before locks priority 0
+NOTICE:  releasing string: parent before locks priority 0
+NOTICE:  releasing string: parent before locks priority 0
+NOTICE:  releasing string: parent before locks priority 0
+NOTICE:  releasing string: parent before locks priority 0
+NOTICE:  releasing string: parent before locks priority 0
+NOTICE:  releasing string: parent before locks priority 0
+NOTICE:  releasing string: parent before locks priority 0
+NOTICE:  releasing string: parent before locks priority 0
+NOTICE:  releasing string: parent before locks priority 0
+NOTICE:  releasing string: parent before locks priority 1
+NOTICE:  releasing string: parent before locks priority 1
+NOTICE:  releasing string: parent before locks priority 1
+NOTICE:  releasing string: parent before locks priority 1
+NOTICE:  releasing string: parent before locks priority 1
+NOTICE:  releasing string: parent before locks priority 1
+NOTICE:  releasing string: parent before locks priority 1
+NOTICE:  releasing string: parent before locks priority 1
+NOTICE:  releasing string: parent before locks priority 1
+NOTICE:  releasing string: parent before locks priority 1
+NOTICE:  releasing string: parent before locks priority 1
+NOTICE:  releasing string: parent before locks priority 1
+NOTICE:  releasing string: parent before locks priority 1
+NOTICE:  releasing string: parent before locks priority 1
+NOTICE:  releasing string: parent before locks priority 1
+NOTICE:  releasing string: parent before locks priority 1
+NOTICE:  releasing locks
+NOTICE:  releasing resources after locks
+NOTICE:  releasing string: child after locks priority 0
+NOTICE:  releasing string: child after locks priority 0
+NOTICE:  releasing string: child after locks priority 0
+NOTICE:  releasing string: child after locks priority 0
+NOTICE:  releasing string: child after locks priority 0
+NOTICE:  releasing string: child after locks priority 0
+NOTICE:  releasing string: child after locks priority 0
+NOTICE:  releasing string: child after locks priority 0
+NOTICE:  releasing string: child after locks priority 0
+NOTICE:  releasing string: child after locks priority 0
+NOTICE:  releasing string: child after locks priority 0
+NOTICE:  releasing string: child after locks priority 0
+NOTICE:  releasing string: child after locks priority 0
+NOTICE:  releasing string: child after locks priority 0
+NOTICE:  releasing string: child after locks priority 0
+NOTICE:  releasing string: child after locks priority 0
+NOTICE:  releasing string: child after locks priority 1
+NOTICE:  releasing string: child after locks priority 1
+NOTICE:  releasing string: child after locks priority 1
+NOTICE:  releasing string: child after locks priority 1
+NOTICE:  releasing string: child after locks priority 1
+NOTICE:  releasing string: child after locks priority 1
+NOTICE:  releasing string: child after locks priority 1
+NOTICE:  releasing string: child after locks priority 1
+NOTICE:  releasing string: child after locks priority 1
+NOTICE:  releasing string: child after locks priority 1
+NOTICE:  releasing string: child after locks priority 1
+NOTICE:  releasing string: child after locks priority 1
+NOTICE:  releasing string: child after locks priority 1
+NOTICE:  releasing string: child after locks priority 1
+NOTICE:  releasing string: child after locks priority 1
+NOTICE:  releasing string: child after locks priority 1
+NOTICE:  releasing string: parent after locks priority 0
+NOTICE:  releasing string: parent after locks priority 0
+NOTICE:  releasing string: parent after locks priority 0
+NOTICE:  releasing string: parent after locks priority 0
+NOTICE:  releasing string: parent after locks priority 0
+NOTICE:  releasing string: parent after locks priority 0
+NOTICE:  releasing string: parent after locks priority 0
+NOTICE:  releasing string: parent after locks priority 0
+NOTICE:  releasing string: parent after locks priority 0
+NOTICE:  releasing string: parent after locks priority 0
+NOTICE:  releasing string: parent after locks priority 0
+NOTICE:  releasing string: parent after locks priority 0
+NOTICE:  releasing string: parent after locks priority 0
+NOTICE:  releasing string: parent after locks priority 0
+NOTICE:  releasing string: parent after locks priority 0
+NOTICE:  releasing string: parent after locks priority 0
+NOTICE:  releasing string: parent after locks priority 1
+NOTICE:  releasing string: parent after locks priority 1
+NOTICE:  releasing string: parent after locks priority 1
+NOTICE:  releasing string: parent after locks priority 1
+NOTICE:  releasing string: parent after locks priority 1
+NOTICE:  releasing string: parent after locks priority 1
+NOTICE:  releasing string: parent after locks priority 1
+NOTICE:  releasing string: parent after locks priority 1
+NOTICE:  releasing string: parent after locks priority 1
+NOTICE:  releasing string: parent after locks priority 1
+NOTICE:  releasing string: parent after locks priority 1
+NOTICE:  releasing string: parent after locks priority 1
+NOTICE:  releasing string: parent after locks priority 1
+NOTICE:  releasing string: parent after locks priority 1
+NOTICE:  releasing string: parent after locks priority 1
+NOTICE:  releasing string: parent after locks priority 1
+ test_resowner_priorities 
+--------------------------
+ 
+(1 row)
+
+-- Basic test with lots more resources, to test extending the hash table
+SELECT test_resowner_many(
+  3,      -- # of different resource kinds
+  100000, -- before-locks resources to remember
+  500,    -- before-locks resources to forget
+  100000, -- after-locks resources to remember
+  500     -- after-locks resources to forget
+);
+NOTICE:  remembering 100000 before-locks resources
+NOTICE:  remembering 100000 after-locks resources
+NOTICE:  forgetting 500 before-locks resources
+NOTICE:  forgetting 500 after-locks resources
+NOTICE:  releasing resources before locks
+NOTICE:  releasing locks
+NOTICE:  releasing resources after locks
+ test_resowner_many 
+--------------------
+ 
+(1 row)
+
+-- Test resource leak warning
+SELECT test_resowner_leak();
+WARNING:  string leak: my string
+NOTICE:  releasing string: my string
+ test_resowner_leak 
+--------------------
+ 
+(1 row)
+
+-- Negative tests, using a resource owner after release-phase has started.
+set client_min_messages='warning'; -- order between ERROR and NOTICE varies
+SELECT test_resowner_remember_between_phases();
+ERROR:  ResourceOwnerEnlarge called after release started
+SELECT test_resowner_forget_between_phases();
+ERROR:  ResourceOwnerForget called for test resource after release started
+reset client_min_messages;
diff --git a/src/test/modules/test_resowner/meson.build b/src/test/modules/test_resowner/meson.build
new file mode 100644
index 00000000000..e92f385a809
--- /dev/null
+++ b/src/test/modules/test_resowner/meson.build
@@ -0,0 +1,37 @@
+# Copyright (c) 2022-2023, PostgreSQL Global Development Group
+
+# FIXME: prevent install during main install, but not during test :/
+
+test_resowner_sources = files(
+  'test_resowner_basic.c',
+  'test_resowner_many.c',
+)
+
+if host_system == 'windows'
+  test_resowner_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_resowner',
+    '--FILEDESC', 'test_resowner - test code for ResourceOwners',])
+endif
+
+test_resowner = shared_module('test_resowner',
+  test_resowner_sources,
+  kwargs: pg_mod_args,
+)
+testprep_targets += test_resowner
+
+install_data(
+  'test_resowner.control',
+  'test_resowner--1.0.sql',
+  kwargs: contrib_data_args,
+)
+
+tests += {
+  'name': 'test_resowner',
+  'sd': meson.current_source_dir(),
+  'bd': meson.current_build_dir(),
+  'regress': {
+    'sql': [
+      'test_resowner',
+    ],
+  },
+}
diff --git a/src/test/modules/test_resowner/test_resowner--1.0.sql b/src/test/modules/test_resowner/test_resowner--1.0.sql
new file mode 100644
index 00000000000..26fed7a010c
--- /dev/null
+++ b/src/test/modules/test_resowner/test_resowner--1.0.sql
@@ -0,0 +1,30 @@
+/* src/test/modules/test_resowner/test_resowner--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_resowner" to load this file. \quit
+
+CREATE FUNCTION test_resowner_priorities(nkinds pg_catalog.int4, nresources pg_catalog.int4)
+	RETURNS pg_catalog.void
+	AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_resowner_leak()
+	RETURNS pg_catalog.void
+	AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_resowner_remember_between_phases()
+	RETURNS pg_catalog.void
+	AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_resowner_forget_between_phases()
+	RETURNS pg_catalog.void
+	AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_resowner_many(
+   nkinds       pg_catalog.int4,
+   nremember_bl pg_catalog.int4,
+   nforget_bl   pg_catalog.int4,
+   nremember_al pg_catalog.int4,
+   nforget_al   pg_catalog.int4
+)
+	RETURNS pg_catalog.void
+	AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_resowner/test_resowner.control b/src/test/modules/test_resowner/test_resowner.control
new file mode 100644
index 00000000000..b56c4ee92bd
--- /dev/null
+++ b/src/test/modules/test_resowner/test_resowner.control
@@ -0,0 +1,4 @@
+comment = 'Test code for ResourceOwners'
+default_version = '1.0'
+module_pathname = '$libdir/test_resowner'
+relocatable = true
diff --git a/src/test/modules/test_resowner/test_resowner_basic.c b/src/test/modules/test_resowner/test_resowner_basic.c
new file mode 100644
index 00000000000..4f4875b1a41
--- /dev/null
+++ b/src/test/modules/test_resowner/test_resowner_basic.c
@@ -0,0 +1,210 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_resowner_basic.c
+ *		Test basic ResourceOwner functionality
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		src/test/modules/test_resowner/test_resowner_basic.c
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "lib/ilist.h"
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+
+PG_MODULE_MAGIC;
+
+static void ReleaseString(Datum res);
+static void PrintStringLeakWarning(Datum res);
+
+static ResourceOwnerFuncs string_funcs = {
+	.name = "test resource",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_FIRST,
+	.ReleaseResource = ReleaseString,
+	.PrintLeakWarning = PrintStringLeakWarning
+};
+
+static void
+ReleaseString(Datum res)
+{
+	elog(NOTICE, "releasing string: %s", DatumGetPointer(res));
+}
+
+static void
+PrintStringLeakWarning(Datum res)
+{
+	elog(WARNING, "string leak: %s", DatumGetPointer(res));
+}
+
+/* demonstrates phases and priorities betwen a parent and child context */
+PG_FUNCTION_INFO_V1(test_resowner_priorities);
+Datum
+test_resowner_priorities(PG_FUNCTION_ARGS)
+{
+	int32		nkinds = PG_GETARG_INT32(0);
+	int32		nresources = PG_GETARG_INT32(1);
+	ResourceOwner parent,
+				child;
+	ResourceOwnerFuncs *before_funcs;
+	ResourceOwnerFuncs *after_funcs;
+
+	if (nkinds <= 0)
+		elog(ERROR, "nkinds must be greater than zero");
+	if (nresources <= 0)
+		elog(ERROR, "nresources must be greater than zero");
+
+	parent = ResourceOwnerCreate(CurrentResourceOwner, "test parent");
+	child = ResourceOwnerCreate(parent, "test child");
+
+	/* FIXME Add a bunch of resources to child, with different priorities */
+	before_funcs = palloc(nkinds * sizeof(ResourceOwnerFuncs));
+	for (int i = 0; i < nkinds; i++)
+	{
+		before_funcs[i].name = psprintf("test resource before locks %d", i);
+		before_funcs[i].release_phase = RESOURCE_RELEASE_BEFORE_LOCKS;
+		before_funcs[i].release_priority = i;
+		before_funcs[i].ReleaseResource = ReleaseString;
+		before_funcs[i].PrintLeakWarning = PrintStringLeakWarning;
+	}
+	after_funcs = palloc(nkinds * sizeof(ResourceOwnerFuncs));
+	for (int i = 0; i < nkinds; i++)
+	{
+		after_funcs[i].name = psprintf("test resource after locks %d", i);
+		after_funcs[i].release_phase = RESOURCE_RELEASE_AFTER_LOCKS;
+		after_funcs[i].release_priority = i;
+		after_funcs[i].ReleaseResource = ReleaseString;
+		after_funcs[i].PrintLeakWarning = PrintStringLeakWarning;
+	}
+
+	ResourceOwnerEnlarge(child);
+
+	for (int i = 0; i < nresources; i++)
+	{
+		ResourceOwnerFuncs *kind = &before_funcs[i % nkinds];
+
+		ResourceOwnerEnlarge(child);
+		ResourceOwnerRemember(child,
+							  CStringGetDatum(psprintf("child before locks priority %d", kind->release_priority)),
+							  kind);
+	}
+
+	for (int i = 0; i < nresources; i++)
+	{
+		ResourceOwnerFuncs *kind = &after_funcs[i % nkinds];
+
+		ResourceOwnerEnlarge(child);
+		ResourceOwnerRemember(child,
+							  CStringGetDatum(psprintf("child after locks priority %d", kind->release_priority)),
+							  kind);
+	}
+
+	for (int i = 0; i < nresources; i++)
+	{
+		ResourceOwnerFuncs *kind = &after_funcs[i % nkinds];
+
+		ResourceOwnerEnlarge(parent);
+		ResourceOwnerRemember(parent,
+							  CStringGetDatum(psprintf("parent after locks priority %d", kind->release_priority)),
+							  kind);
+	}
+	for (int i = 0; i < nresources; i++)
+	{
+		ResourceOwnerFuncs *kind = &before_funcs[i % nkinds];
+
+		ResourceOwnerEnlarge(parent);
+		ResourceOwnerRemember(parent,
+							  CStringGetDatum(psprintf("parent before locks priority %d", kind->release_priority)),
+							  kind);
+	}
+
+	elog(NOTICE, "releasing resources before locks");
+	ResourceOwnerRelease(parent, RESOURCE_RELEASE_BEFORE_LOCKS, false, false);
+	elog(NOTICE, "releasing locks");
+	ResourceOwnerRelease(parent, RESOURCE_RELEASE_LOCKS, false, false);
+	elog(NOTICE, "releasing resources after locks");
+	ResourceOwnerRelease(parent, RESOURCE_RELEASE_AFTER_LOCKS, false, false);
+
+	ResourceOwnerDelete(parent);
+
+	PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(test_resowner_leak);
+Datum
+test_resowner_leak(PG_FUNCTION_ARGS)
+{
+	ResourceOwner resowner;
+
+	resowner = ResourceOwnerCreate(CurrentResourceOwner, "TestOwner");
+
+	ResourceOwnerEnlarge(resowner);
+
+	ResourceOwnerRemember(resowner, CStringGetDatum("my string"), &string_funcs);
+
+	/* don't call ResourceOwnerForget, so that it is leaked */
+
+	ResourceOwnerRelease(resowner, RESOURCE_RELEASE_BEFORE_LOCKS, true, false);
+	ResourceOwnerRelease(resowner, RESOURCE_RELEASE_LOCKS, true, false);
+	ResourceOwnerRelease(resowner, RESOURCE_RELEASE_AFTER_LOCKS, true, false);
+
+	ResourceOwnerDelete(resowner);
+
+	PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(test_resowner_remember_between_phases);
+Datum
+test_resowner_remember_between_phases(PG_FUNCTION_ARGS)
+{
+	ResourceOwner resowner;
+
+	resowner = ResourceOwnerCreate(CurrentResourceOwner, "TestOwner");
+
+	ResourceOwnerRelease(resowner, RESOURCE_RELEASE_BEFORE_LOCKS, true, false);
+
+	/*
+	 * Try to remember a new resource.  Fails because we already called
+	 * ResourceOwnerRelease.
+	 */
+	ResourceOwnerEnlarge(resowner);
+	ResourceOwnerRemember(resowner, CStringGetDatum("my string"), &string_funcs);
+
+	/* unreachable */
+	elog(ERROR, "ResourceOwnerEnlarge should have errored out");
+
+	PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(test_resowner_forget_between_phases);
+Datum
+test_resowner_forget_between_phases(PG_FUNCTION_ARGS)
+{
+	ResourceOwner resowner;
+	Datum		str_resource;
+
+	resowner = ResourceOwnerCreate(CurrentResourceOwner, "TestOwner");
+
+	ResourceOwnerEnlarge(resowner);
+	str_resource = CStringGetDatum("my string");
+	ResourceOwnerRemember(resowner, str_resource, &string_funcs);
+
+	ResourceOwnerRelease(resowner, RESOURCE_RELEASE_BEFORE_LOCKS, true, false);
+
+	/*
+	 * Try to forget the resource that was remembered earlier.  Fails because
+	 * we already called ResourceOwnerRelease.
+	 */
+	ResourceOwnerForget(resowner, str_resource, &string_funcs);
+
+	/* unreachable */
+	elog(ERROR, "ResourceOwnerForget should have errored out");
+
+	PG_RETURN_VOID();
+}
diff --git a/src/test/modules/test_resowner/test_resowner_many.c b/src/test/modules/test_resowner/test_resowner_many.c
new file mode 100644
index 00000000000..5b8a34b37ef
--- /dev/null
+++ b/src/test/modules/test_resowner/test_resowner_many.c
@@ -0,0 +1,287 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_resowner_many.c
+ *		Test ResourceOwner functionality with lots of resources
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		src/test/modules/test_resowner/test_resowner_many.c
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "lib/ilist.h"
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+
+/*
+ * Define a custom resource type to use in the test.  The resource being
+ * tracked is a palloc'd ManyTestResource struct.
+ *
+ * To cross-check that the ResourceOwner calls the callback functions
+ * correctly, we keep track of the remembered resources ourselves in a linked
+ * list, and also keep counters of how many times the callback functions have
+ * been called.
+ */
+typedef struct
+{
+	ResourceOwnerFuncs funcs;
+	int			nremembered;
+	int			nforgotten;
+	int			nreleased;
+	int			nleaked;
+
+	dlist_head	current_resources;
+} ManyTestResourceKind;
+
+typedef struct
+{
+	ManyTestResourceKind *kind;
+	dlist_node	node;
+} ManyTestResource;
+
+/*
+ * Current release phase, and priority of last call to the release callback.
+ * This is used to check that the resources are released in correct order.
+ */
+static ResourceReleasePhase current_release_phase;
+static uint32 last_release_priority = 0;
+
+/* prototypes for local functions */
+static void ReleaseManyTestResource(Datum res);
+static void PrintManyTestLeakWarning(Datum res);
+static void InitManyTestResourceKind(ManyTestResourceKind *kind, char *name,
+									 ResourceReleasePhase phase, uint32 priority);
+static void RememberManyTestResources(ResourceOwner owner,
+									  ManyTestResourceKind *kinds, int nkinds,
+									  int nresources);
+static void ForgetManyTestResources(ResourceOwner owner,
+									ManyTestResourceKind *kinds, int nkinds,
+									int nresources);
+static int GetTotalResourceCount(ManyTestResourceKind *kinds, int nkinds);
+
+/* ResourceOwner callback */
+static void
+ReleaseManyTestResource(Datum res)
+{
+	ManyTestResource *mres = (ManyTestResource *) DatumGetPointer(res);
+
+	elog(DEBUG1, "releasing resource %p from %s", mres, mres->kind->funcs.name);
+	Assert(last_release_priority <= mres->kind->funcs.release_priority);
+
+	dlist_delete(&mres->node);
+	mres->kind->nreleased++;
+	last_release_priority = mres->kind->funcs.release_priority;
+	pfree(mres);
+}
+
+/* ResourceOwner callback */
+static void
+PrintManyTestLeakWarning(Datum res)
+{
+	ManyTestResource *mres = (ManyTestResource *) DatumGetPointer(res);
+
+	mres->kind->nleaked++;
+	elog(DEBUG1, "leaked resource from %s", mres->kind->funcs.name);
+}
+
+static void
+InitManyTestResourceKind(ManyTestResourceKind *kind, char *name,
+						 ResourceReleasePhase phase, uint32 priority)
+{
+	kind->funcs.name = name;
+	kind->funcs.release_phase = phase;
+	kind->funcs.release_priority = priority;
+	kind->funcs.ReleaseResource = ReleaseManyTestResource;
+	kind->funcs.PrintLeakWarning = PrintManyTestLeakWarning;
+	kind->nremembered = 0;
+	kind->nforgotten = 0;
+	kind->nreleased = 0;
+	kind->nleaked = 0;
+	dlist_init(&kind->current_resources);
+}
+
+/*
+ * Remember 'nresources' resources.  The resources are remembered in round
+ * robin fashion with the kinds from 'kinds' array.
+ */
+static void
+RememberManyTestResources(ResourceOwner owner,
+						  ManyTestResourceKind *kinds, int nkinds,
+						  int nresources)
+{
+	int			kind_idx = 0;
+
+	for (int i = 0; i < nresources; i++)
+	{
+		ManyTestResource *mres = palloc(sizeof(ManyTestResource));
+
+		mres->kind = &kinds[kind_idx];
+		dlist_node_init(&mres->node);
+
+		ResourceOwnerEnlarge(owner);
+		ResourceOwnerRemember(owner, PointerGetDatum(mres), &kinds[kind_idx].funcs);
+		kinds[kind_idx].nremembered++;
+		dlist_push_tail(&kinds[kind_idx].current_resources, &mres->node);
+
+		elog(DEBUG1, "remembered resource %p from %s", mres, mres->kind->funcs.name);
+
+		kind_idx = (kind_idx + 1) % nkinds;
+	}
+}
+
+/*
+ * Forget 'nresources' resources, in round robin fashion from 'kinds'.
+ */
+static void
+ForgetManyTestResources(ResourceOwner owner,
+						ManyTestResourceKind *kinds, int nkinds,
+						int nresources)
+{
+	int			kind_idx = 0;
+
+	Assert(GetTotalResourceCount(kinds, nkinds) >= nresources);
+
+	for (int i = 0; i < nresources; i++)
+	{
+		bool		found = false;
+
+		for (int j = 0; j < nkinds; j++)
+		{
+			kind_idx = (kind_idx + 1) % nkinds;
+			if (!dlist_is_empty(&kinds[kind_idx].current_resources))
+			{
+				ManyTestResource *mres = dlist_head_element(ManyTestResource, node, &kinds[kind_idx].current_resources);
+
+				ResourceOwnerForget(owner, PointerGetDatum(mres), &kinds[kind_idx].funcs);
+				kinds[kind_idx].nforgotten++;
+				dlist_delete(&mres->node);
+				pfree(mres);
+
+				found = true;
+				break;
+			}
+		}
+		if (!found)
+			elog(ERROR, "could not find a test resource to forget");
+	}
+}
+
+/*
+ * Get total number of currently active resources among 'kinds'.
+ */
+static int
+GetTotalResourceCount(ManyTestResourceKind *kinds, int nkinds)
+{
+	int			ntotal = 0;
+
+	for (int i = 0; i < nkinds; i++)
+		ntotal += kinds[i].nremembered - kinds[i].nforgotten - kinds[i].nreleased;
+
+	return ntotal;
+}
+
+/*
+ * Remember lots of resources, belonging to 'nkinds' different resource types
+ * with different priorities.  Then forget some of them, and finally, release
+ * the resource owner.  We use a custom resource type that performs various
+ * sanity checks to verify that the all the resources are released, and in the
+ * correct order.
+ */
+PG_FUNCTION_INFO_V1(test_resowner_many);
+Datum
+test_resowner_many(PG_FUNCTION_ARGS)
+{
+	int32		nkinds = PG_GETARG_INT32(0);
+	int32		nremember_bl = PG_GETARG_INT32(1);
+	int32		nforget_bl = PG_GETARG_INT32(2);
+	int32		nremember_al = PG_GETARG_INT32(3);
+	int32		nforget_al = PG_GETARG_INT32(4);
+
+	ResourceOwner resowner;
+
+	ManyTestResourceKind *before_kinds;
+	ManyTestResourceKind *after_kinds;
+
+	/* Sanity check the arguments */
+	if (nkinds < 0)
+		elog(ERROR, "nkinds must be >= 0");
+	if (nremember_bl < 0)
+		elog(ERROR, "nremember_bl must be >= 0");
+	if (nforget_bl < 0 || nforget_bl > nremember_bl)
+		elog(ERROR, "nforget_bl must between 0 and 'nremember_bl'");
+	if (nremember_al < 0)
+		elog(ERROR, "nremember_al must be greater than zero");
+	if (nforget_al < 0 || nforget_al > nremember_al)
+		elog(ERROR, "nforget_al must between 0 and 'nremember_al'");
+
+	/* Initialize all the different resource kinds to use */
+	before_kinds = palloc(nkinds * sizeof(ManyTestResourceKind));
+	for (int i = 0; i < nkinds; i++)
+	{
+		InitManyTestResourceKind(&before_kinds[i],
+							 psprintf("resource before locks %d", i),
+							 RESOURCE_RELEASE_BEFORE_LOCKS, i);
+	}
+	after_kinds = palloc(nkinds * sizeof(ManyTestResourceKind));
+	for (int i = 0; i < nkinds; i++)
+	{
+		InitManyTestResourceKind(&after_kinds[i],
+							 psprintf("resource after locks %d", i),
+							 RESOURCE_RELEASE_AFTER_LOCKS, i);
+	}
+
+	resowner = ResourceOwnerCreate(CurrentResourceOwner, "TestOwner");
+
+	/* Remember a bunch of resources */
+	if (nremember_bl > 0)
+	{
+		elog(NOTICE, "remembering %d before-locks resources", nremember_bl);
+		RememberManyTestResources(resowner, before_kinds, nkinds, nremember_bl);
+	}
+	if (nremember_al > 0)
+	{
+		elog(NOTICE, "remembering %d after-locks resources", nremember_al);
+		RememberManyTestResources(resowner, after_kinds, nkinds, nremember_al);
+	}
+
+	/* Forget what was remembered */
+	if (nforget_bl > 0)
+	{
+		elog(NOTICE, "forgetting %d before-locks resources", nforget_bl);
+		ForgetManyTestResources(resowner, before_kinds, nkinds, nforget_bl);
+	}
+
+	if (nforget_al > 0)
+	{
+		elog(NOTICE, "forgetting %d after-locks resources", nforget_al);
+		ForgetManyTestResources(resowner, after_kinds, nkinds, nforget_al);
+	}
+
+	/* Start releasing */
+	elog(NOTICE, "releasing resources before locks");
+	current_release_phase = RESOURCE_RELEASE_BEFORE_LOCKS;
+	last_release_priority = 0;
+	ResourceOwnerRelease(resowner, RESOURCE_RELEASE_BEFORE_LOCKS, false, false);
+	Assert(GetTotalResourceCount(before_kinds, nkinds) == 0);
+
+	elog(NOTICE, "releasing locks");
+	current_release_phase = RESOURCE_RELEASE_LOCKS;
+	last_release_priority = 0;
+	ResourceOwnerRelease(resowner, RESOURCE_RELEASE_LOCKS, false, false);
+
+	elog(NOTICE, "releasing resources after locks");
+	current_release_phase = RESOURCE_RELEASE_AFTER_LOCKS;
+	last_release_priority = 0;
+	ResourceOwnerRelease(resowner, RESOURCE_RELEASE_AFTER_LOCKS, false, false);
+	Assert(GetTotalResourceCount(before_kinds, nkinds) == 0);
+	Assert(GetTotalResourceCount(after_kinds, nkinds) == 0);
+
+	ResourceOwnerDelete(resowner);
+
+	PG_RETURN_VOID();
+}
-- 
2.30.2

From 437d58f4790a682d2cc67db6a61855da9423f19a Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email-masked]>
Date: Mon, 31 Oct 2022 10:49:06 +0100
Subject: [PATCH v13 4/5] Use a faster hash function in resource owners.

This buys back some of the performance loss that we otherwise saw from the
previous commit.

Reviewed-by: Aleksander Alekseev, Michael Paquier, Hayato Kuroda, Zhihong Yu
Discussion: https://www.postgresql.org/message-id/d746cead-a1ef-7efe-fb47-933311e876a3%40iki.fi
---
 src/backend/utils/resowner/resowner.c | 24 ++++++++++++++++++------
 src/include/common/hashfn.h           | 15 +++++++++++++++
 2 files changed, 33 insertions(+), 6 deletions(-)

diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index 6de09913bd0..1fddaceb74e 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -197,15 +197,27 @@ static void ReleaseAuxProcessResourcesCallback(int code, Datum arg);
  *	  INTERNAL ROUTINES														 *
  *****************************************************************************/
 
+/*
+ * Hash function for value+kind combination.
+ */
 static inline uint32
 hash_resource_elem(Datum value, ResourceOwnerFuncs *kind)
 {
-	Datum		data[2];
-
-	data[0] = value;
-	data[1] = PointerGetDatum(kind);
-
-	return hash_bytes((unsigned char *) &data, 2 * SIZEOF_DATUM);
+	/*
+	 * Most resource kinds store a pointer in 'value', and pointers are unique
+	 * all on their own.  But some resources store plain integers (Files and
+	 * Buffers as of this writing), so we want to incorporate the 'kind' in
+	 * the hash too, otherwise those resources will collide a lot.  But
+	 * because there are only a few resource kinds like that - and only a few
+	 * resource kinds to begin with - we don't need to work too hard to mix
+	 * 'kind' into the hash.  Just add it with hash_combine(), it perturbs the
+	 * result enough for our purposes.
+	 */
+#if SIZEOF_DATUM == 8
+	return hash_combine64(murmurhash64((uint64) value), (uint64) kind);
+#else
+	return hash_combine(murmurhash32((uint32) value), (uint32) kind);
+#endif
 }
 
 /*
diff --git a/src/include/common/hashfn.h b/src/include/common/hashfn.h
index 5e89aef987f..adc1dc1de89 100644
--- a/src/include/common/hashfn.h
+++ b/src/include/common/hashfn.h
@@ -101,4 +101,19 @@ murmurhash32(uint32 data)
 	return h;
 }
 
+/* 64-bit variant */
+static inline uint64
+murmurhash64(uint64 data)
+{
+	uint64		h = data;
+
+	h ^= h >> 33;
+	h *= 0xff51afd7ed558ccd;
+	h ^= h >> 33;
+	h *= 0xc4ceb9fe1a85ec53;
+	h ^= h >> 33;
+
+	return h;
+}
+
 #endif							/* HASHFN_H */
-- 
2.30.2

From 8aac8a741742816521c5cd272ac22f67b1477278 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email-masked]>
Date: Sat, 11 Dec 2021 00:37:47 +0200
Subject: [PATCH v13 5/5] Change pgcrypto to use the new ResourceOwner
 mechanism.

---
 contrib/pgcrypto/openssl.c | 185 ++++++++++++++++---------------------
 1 file changed, 82 insertions(+), 103 deletions(-)

diff --git a/contrib/pgcrypto/openssl.c b/contrib/pgcrypto/openssl.c
index cf315517e0c..d4c3617eb9c 100644
--- a/contrib/pgcrypto/openssl.c
+++ b/contrib/pgcrypto/openssl.c
@@ -50,9 +50,8 @@
  */
 
 /*
- * To make sure we don't leak OpenSSL handles on abort, we keep OSSLDigest
- * objects in a linked list, allocated in TopMemoryContext. We use the
- * ResourceOwner mechanism to free them on abort.
+ * To make sure we don't leak OpenSSL handles, we use the ResourceOwner
+ * mechanism to free them on abort.
  */
 typedef struct OSSLDigest
 {
@@ -60,56 +59,36 @@ typedef struct OSSLDigest
 	EVP_MD_CTX *ctx;
 
 	ResourceOwner owner;
-	struct OSSLDigest *next;
-	struct OSSLDigest *prev;
 } OSSLDigest;
 
-static OSSLDigest *open_digests = NULL;
-static bool digest_resowner_callback_registered = false;
+/* ResourceOwner callbacks to hold OpenSSL digest handles */
+static void ResOwnerReleaseOSSLDigest(Datum res);
+static void ResOwnerPrintOSSLDigestLeakWarning(Datum res);
+
+static ResourceOwnerFuncs ossldigest_resowner_funcs =
+{
+	.name = "OpenSSL digest handle",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_FIRST,
+	.ReleaseResource = ResOwnerReleaseOSSLDigest,
+	.PrintLeakWarning = ResOwnerPrintOSSLDigestLeakWarning
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+#define ResourceOwnerRememberOSSLDigest(owner, digest) \
+	ResourceOwnerRemember(owner, PointerGetDatum(digest), &ossldigest_resowner_funcs)
+#define ResourceOwnerForgetOSSLDigest(owner, digest) \
+	ResourceOwnerForget(owner, PointerGetDatum(digest), &ossldigest_resowner_funcs)
 
 static void
 free_openssl_digest(OSSLDigest *digest)
 {
 	EVP_MD_CTX_destroy(digest->ctx);
-	if (digest->prev)
-		digest->prev->next = digest->next;
-	else
-		open_digests = digest->next;
-	if (digest->next)
-		digest->next->prev = digest->prev;
+	if (digest->owner != NULL)
+		ResourceOwnerForgetOSSLDigest(digest->owner, digest);
 	pfree(digest);
 }
 
-/*
- * Close any open OpenSSL handles on abort.
- */
-static void
-digest_free_callback(ResourceReleasePhase phase,
-					 bool isCommit,
-					 bool isTopLevel,
-					 void *arg)
-{
-	OSSLDigest *curr;
-	OSSLDigest *next;
-
-	if (phase != RESOURCE_RELEASE_AFTER_LOCKS)
-		return;
-
-	next = open_digests;
-	while (next)
-	{
-		curr = next;
-		next = curr->next;
-
-		if (curr->owner == CurrentResourceOwner)
-		{
-			if (isCommit)
-				elog(WARNING, "pgcrypto digest reference leak: digest %p still referenced", curr);
-			free_openssl_digest(curr);
-		}
-	}
-}
-
 static unsigned
 digest_result_size(PX_MD *h)
 {
@@ -188,16 +167,12 @@ px_find_digest(const char *name, PX_MD **res)
 		OpenSSL_add_all_algorithms();
 	}
 
-	if (!digest_resowner_callback_registered)
-	{
-		RegisterResourceReleaseCallback(digest_free_callback, NULL);
-		digest_resowner_callback_registered = true;
-	}
-
 	md = EVP_get_digestbyname(name);
 	if (md == NULL)
 		return PXE_NO_HASH;
 
+	ResourceOwnerEnlarge(CurrentResourceOwner);
+
 	/*
 	 * Create an OSSLDigest object, an OpenSSL MD object, and a PX_MD object.
 	 * The order is crucial, to make sure we don't leak anything on
@@ -221,9 +196,7 @@ px_find_digest(const char *name, PX_MD **res)
 	digest->algo = md;
 	digest->ctx = ctx;
 	digest->owner = CurrentResourceOwner;
-	digest->next = open_digests;
-	digest->prev = NULL;
-	open_digests = digest;
+	ResourceOwnerRememberOSSLDigest(digest->owner, digest);
 
 	/* The PX_MD object is allocated in the current memory context. */
 	h = palloc(sizeof(*h));
@@ -239,6 +212,24 @@ px_find_digest(const char *name, PX_MD **res)
 	return 0;
 }
 
+/* ResourceOwner callbacks for OSSLDigest */
+
+static void
+ResOwnerReleaseOSSLDigest(Datum res)
+{
+	OSSLDigest *digest = (OSSLDigest *) DatumGetPointer(res);
+
+	digest->owner = NULL;
+	free_openssl_digest(digest);
+}
+
+static void
+ResOwnerPrintOSSLDigestLeakWarning(Datum res)
+{
+	elog(WARNING, "pgcrypto digest reference leak: digest %p still referenced",
+		 DatumGetPointer(res));
+}
+
 /*
  * Ciphers
  *
@@ -266,9 +257,8 @@ struct ossl_cipher
  * OSSLCipher contains the state for using a cipher. A separate OSSLCipher
  * object is allocated in each px_find_cipher() call.
  *
- * To make sure we don't leak OpenSSL handles on abort, we keep OSSLCipher
- * objects in a linked list, allocated in TopMemoryContext. We use the
- * ResourceOwner mechanism to free them on abort.
+ * To make sure we don't leak OpenSSL handles, we use the ResourceOwner
+ * mechanism to free them on abort.
  */
 typedef struct OSSLCipher
 {
@@ -281,56 +271,36 @@ typedef struct OSSLCipher
 	const struct ossl_cipher *ciph;
 
 	ResourceOwner owner;
-	struct OSSLCipher *next;
-	struct OSSLCipher *prev;
 } OSSLCipher;
 
-static OSSLCipher *open_ciphers = NULL;
-static bool cipher_resowner_callback_registered = false;
+/* ResourceOwner callbacks to hold OpenSSL cipher state */
+static void ResOwnerReleaseOSSLCipher(Datum res);
+static void ResOwnerPrintOSSLCipherLeakWarning(Datum res);
+
+static ResourceOwnerFuncs osslcipher_resowner_funcs =
+{
+	.name = "OpenSSL cipher handle",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_FIRST,
+	.ReleaseResource = ResOwnerReleaseOSSLCipher,
+	.PrintLeakWarning = ResOwnerPrintOSSLCipherLeakWarning
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+#define ResourceOwnerRememberOSSLCipher(owner, od) \
+	ResourceOwnerRemember(owner, PointerGetDatum(od), &osslcipher_resowner_funcs)
+#define ResourceOwnerForgetOSSLCipher(owner, od) \
+	ResourceOwnerForget(owner, PointerGetDatum(od), &osslcipher_resowner_funcs)
 
 static void
 free_openssl_cipher(OSSLCipher *od)
 {
 	EVP_CIPHER_CTX_free(od->evp_ctx);
-	if (od->prev)
-		od->prev->next = od->next;
-	else
-		open_ciphers = od->next;
-	if (od->next)
-		od->next->prev = od->prev;
+	if (od->owner != NULL)
+		ResourceOwnerForgetOSSLCipher(od->owner, od);
 	pfree(od);
 }
 
-/*
- * Close any open OpenSSL cipher handles on abort.
- */
-static void
-cipher_free_callback(ResourceReleasePhase phase,
-					 bool isCommit,
-					 bool isTopLevel,
-					 void *arg)
-{
-	OSSLCipher *curr;
-	OSSLCipher *next;
-
-	if (phase != RESOURCE_RELEASE_AFTER_LOCKS)
-		return;
-
-	next = open_ciphers;
-	while (next)
-	{
-		curr = next;
-		next = curr->next;
-
-		if (curr->owner == CurrentResourceOwner)
-		{
-			if (isCommit)
-				elog(WARNING, "pgcrypto cipher reference leak: cipher %p still referenced", curr);
-			free_openssl_cipher(curr);
-		}
-	}
-}
-
 /* Common routines for all algorithms */
 
 static unsigned
@@ -782,11 +752,7 @@ px_find_cipher(const char *name, PX_Cipher **res)
 	if (i->name == NULL)
 		return PXE_NO_CIPHER;
 
-	if (!cipher_resowner_callback_registered)
-	{
-		RegisterResourceReleaseCallback(cipher_free_callback, NULL);
-		cipher_resowner_callback_registered = true;
-	}
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	/*
 	 * Create an OSSLCipher object, an EVP_CIPHER_CTX object and a PX_Cipher.
@@ -806,9 +772,7 @@ px_find_cipher(const char *name, PX_Cipher **res)
 
 	od->evp_ctx = ctx;
 	od->owner = CurrentResourceOwner;
-	od->next = open_ciphers;
-	od->prev = NULL;
-	open_ciphers = od;
+	ResourceOwnerRememberOSSLCipher(od->owner, od);
 
 	if (i->ciph->cipher_func)
 		od->evp_ciph = i->ciph->cipher_func();
@@ -827,3 +791,18 @@ px_find_cipher(const char *name, PX_Cipher **res)
 	*res = c;
 	return 0;
 }
+
+/* ResourceOwner callbacks for OSSLCipher */
+
+static void
+ResOwnerReleaseOSSLCipher(Datum res)
+{
+	free_openssl_cipher((OSSLCipher *) DatumGetPointer(res));
+}
+
+static void
+ResOwnerPrintOSSLCipherLeakWarning(Datum res)
+{
+	elog(WARNING, "pgcrypto cipher reference leak: cipher %p still referenced",
+		 DatumGetPointer(res));
+}
-- 
2.30.2