ResourceOwner refactoring

Started by Heikki Linnakangasabout 5 years ago91 messages
#1Heikki Linnakangas
hlinnaka@iki.fi
1 attachment(s)

Two recent patches that I have reviewed have reminded me that the
ResourceOwner interface is a bit clunky. There are two issues:

1. Performance. It's fast enough in most use, but when I was testing
Kyotaro's catcache patches [1]/messages/by-id/20201106.172958.1103727352904717607.horikyota.ntt@gmail.com, the Resowner functions to track catcache
references nevertheless showed up, accounting for about 20% of the CPU
time used by catcache lookups.

2. It's difficult for extensions to use. There is a callback mechanism
for extensions, but it's much less convenient to use than the built-in
functions. The pgcrypto code uses the callbacks currently, and Michael's
patch [2]/messages/by-id/20201113031429.GB1631@paquier.xyz would move that support for tracking OpenSSL contexts to the
core, which makes it a lot more convenient for pgcrypto. Wouldn't it be
nice if extensions could have the same ergonomics as built-in code, if
they need to track external resources?

Attached patch refactors the ResourceOwner internals to do that.

The current code in HEAD has a separate array for each "kind" of object
that can be tracked. The number of different kinds of objects has grown
over the years, currently there is support for tracking buffers, files,
catcache, relcache and plancache references, tupledescs, snapshots, DSM
segments and LLVM JIT contexts. And locks, which are a bit special.

In the patch, I'm using the same array to hold all kinds of objects, and
carry another field with each tracked object, to tell what kind of an
object it is. An extension can define a new object kind, by defining
Release and PrintLeakWarning callback functions for it. The code in
resowner.c is now much more generic, as it doesn't need to know about
all the different object kinds anymore (with a couple of exceptions). In
the new scheme, there is one small fixed-size array to hold a few most
recently-added references, that is linear-searched, and older entries
are moved to a hash table.

I haven't done thorough performance testing of this, but with some quick
testing with Kyotaro's "catcachebench" to stress-test syscache lookups,
this performs a little better. In that test, all the activity happens in
the small array portion, but I believe that's true for most use cases.

Thoughts? Can anyone suggest test scenarios to verify the performance of
this?

[1]: /messages/by-id/20201106.172958.1103727352904717607.horikyota.ntt@gmail.com
/messages/by-id/20201106.172958.1103727352904717607.horikyota.ntt@gmail.com

[2]: /messages/by-id/20201113031429.GB1631@paquier.xyz

- Heikki

Attachments:

v1-resowner-refactor.patchtext/x-patch; charset=UTF-8; name=v1-resowner-refactor.patchDownload
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 30c30cf3a2e..c099f04f551 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -29,9 +29,21 @@
 #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
+};
 
 /*
  * CreateTemplateTupleDesc
@@ -376,9 +388,10 @@ IncrTupleDescRefCount(TupleDesc tupdesc)
 {
 	Assert(tupdesc->tdrefcount >= 0);
 
-	ResourceOwnerEnlargeTupleDescs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	tupdesc->tdrefcount++;
-	ResourceOwnerRememberTupleDesc(CurrentResourceOwner, tupdesc);
+	ResourceOwnerRemember(CurrentResourceOwner, PointerGetDatum(tupdesc),
+						  &tupdesc_resowner_funcs);
 }
 
 /*
@@ -394,7 +407,8 @@ DecrTupleDescRefCount(TupleDesc tupdesc)
 {
 	Assert(tupdesc->tdrefcount > 0);
 
-	ResourceOwnerForgetTupleDesc(CurrentResourceOwner, tupdesc);
+	ResourceOwnerForget(CurrentResourceOwner, PointerGetDatum(tupdesc),
+						&tupdesc_resowner_funcs);
 	if (--tupdesc->tdrefcount == 0)
 		FreeTupleDesc(tupdesc);
 }
@@ -925,3 +939,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 5ca3f922fed..c44080b9742 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 40a439326c6..edea0388c04 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,8 +121,20 @@ static LLVMOrcLLJITRef llvm_create_jit_instance(LLVMTargetMachineRef tm);
 static char *llvm_error_message(LLVMErrorRef error);
 #endif							/* LLVM_VERSION_MAJOR > 11 */
 
-PG_MODULE_MAGIC;
+/* ResourceOwner callbacks to hold JitContexts  */
+static void ResOwnerReleaseJitContext(Datum res);
+static void ResOwnerPrintJitContextLeakWarning(Datum res);
+
+static ResourceOwnerFuncs jit_funcs =
+{
+	/* relcache references */
+	.name = "LLVM JIT context",
+	.phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.ReleaseResource = ResOwnerReleaseJitContext,
+	.PrintLeakWarning = ResOwnerPrintJitContextLeakWarning
+};
 
+PG_MODULE_MAGIC;
 
 /*
  * Initialize LLVM JIT provider.
@@ -151,7 +163,7 @@ llvm_create_context(int jitFlags)
 
 	llvm_session_initialize();
 
-	ResourceOwnerEnlargeJIT(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	context = MemoryContextAllocZero(TopMemoryContext,
 									 sizeof(LLVMJitContext));
@@ -159,7 +171,7 @@ llvm_create_context(int jitFlags)
 
 	/* ensure cleanup */
 	context->base.resowner = CurrentResourceOwner;
-	ResourceOwnerRememberJIT(CurrentResourceOwner, PointerGetDatum(context));
+	ResourceOwnerRemember(CurrentResourceOwner, PointerGetDatum(context), &jit_funcs);
 
 	return context;
 }
@@ -221,6 +233,8 @@ llvm_release_context(JitContext *context)
 
 		pfree(jit_handle);
 	}
+
+	ResourceOwnerForget(context->resowner, PointerGetDatum(context), &jit_funcs);
 }
 
 /*
@@ -1210,3 +1224,21 @@ llvm_error_message(LLVMErrorRef error)
 }
 
 #endif							/* LLVM_VERSION_MAJOR > 11 */
+
+/*
+ * ResourceOwner callbacks
+ */
+static void
+ResOwnerReleaseJitContext(Datum res)
+{
+	jit_release_context((JitContext *) PointerGetDatum(res));
+}
+
+static void
+ResOwnerPrintJitContextLeakWarning(Datum res)
+{
+	/* XXX: We used to not print these. Was that intentional? */
+	JitContext *context = (JitContext *) PointerGetDatum(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 ad0d1a9abc0..11a42cbdea6 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -52,7 +52,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"
 
 
@@ -198,6 +198,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
@@ -727,7 +739,7 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 	*hit = false;
 
 	/* Make sure we will have room to remember the buffer pin */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	isExtend = (blockNum == P_NEW);
 
@@ -1846,7 +1858,7 @@ BufferSync(int flags)
 	WritebackContext wb_context;
 
 	/* Make sure we can handle the pin inside SyncOneBuffer */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	/*
 	 * Unless this is a shutdown checkpoint or we have been explicitly told,
@@ -2323,7 +2335,7 @@ BgBufferSync(WritebackContext *wb_context)
 	 */
 
 	/* Make sure we can handle the pin inside SyncOneBuffer */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	num_to_scan = bufs_to_lap;
 	num_written = 0;
@@ -3301,7 +3313,7 @@ FlushRelationBuffers(Relation rel)
 	}
 
 	/* Make sure we can handle the pin inside the loop */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	for (i = 0; i < NBuffers; i++)
 	{
@@ -3374,7 +3386,7 @@ FlushRelationsAllBuffers(SMgrRelation *smgrs, int nrels)
 		pg_qsort(srels, nrels, sizeof(SMgrSortArray), rnode_comparator);
 
 	/* Make sure we can handle the pin inside the loop */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	for (i = 0; i < NBuffers; i++)
 	{
@@ -3453,7 +3465,7 @@ FlushDatabaseBuffers(Oid dbid)
 	BufferDesc *bufHdr;
 
 	/* Make sure we can handle the pin inside the loop */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	for (i = 0; i < NBuffers; i++)
 	{
@@ -3551,7 +3563,7 @@ void
 IncrBufferRefCount(Buffer buffer)
 {
 	Assert(BufferIsPinned(buffer));
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	if (BufferIsLocal(buffer))
 		LocalRefCount[-buffer - 1]++;
 	else
@@ -4585,3 +4597,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 6ffd7b33062..cbf9819cba5 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -22,7 +22,7 @@
 #include "storage/bufmgr.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 
 
 /*#define LBDEBUG*/
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 05abcf72d68..ba6262c83da 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -96,7 +96,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)
@@ -339,6 +339,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
  */
@@ -1402,7 +1420,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
@@ -1584,7 +1602,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
@@ -1714,7 +1732,7 @@ PathNameCreateTemporaryFile(const char *path, bool error_on_failure)
 {
 	File		file;
 
-	ResourceOwnerEnlargeFiles(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	/*
 	 * Open the file.  Note: we don't use O_EXCL, in case there is an orphaned
@@ -1752,7 +1770,7 @@ PathNameOpenTemporaryFile(const char *path, int mode)
 {
 	File		file;
 
-	ResourceOwnerEnlargeFiles(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	file = PathNameOpenFile(path, mode | PG_BINARY);
 
@@ -3604,3 +3622,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 dffbd8e82a2..c665bc6cecf 100644
--- a/src/backend/storage/ipc/dsm.c
+++ b/src/backend/storage/ipc/dsm.c
@@ -37,13 +37,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
 
@@ -139,6 +141,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.
  *
@@ -895,7 +916,7 @@ void
 dsm_unpin_mapping(dsm_segment *seg)
 {
 	Assert(seg->resowner == NULL);
-	ResourceOwnerEnlargeDSMs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	seg->resowner = CurrentResourceOwner;
 	ResourceOwnerRememberDSM(seg->resowner, seg);
 }
@@ -1162,7 +1183,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);
@@ -1241,3 +1262,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 d86566f4554..98b4725c406 100644
--- a/src/backend/storage/lmgr/lock.c
+++ b/src/backend/storage/lmgr/lock.c
@@ -47,7 +47,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 3613ae5f44d..c9ebf9effe1 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,66 @@ 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_funcs =
+{
+	/* catcache references */
+	.name = "catcache reference",
+	.phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.ReleaseResource = ResOwnerReleaseCatCache,
+	.PrintLeakWarning = ResOwnerPrintCatCacheLeakWarning
+};
+
+static ResourceOwnerFuncs catlistref_funcs =
+{
+	/* catcache-list pins */
+	.name = "catcache list reference",
+	.phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.ReleaseResource = ResOwnerReleaseCatCacheList,
+	.PrintLeakWarning = ResOwnerPrintCatCacheListLeakWarning
+};
+
+/* support for catcache refcount management */
+static inline void
+ResourceOwnerEnlargeCatCacheRefs(ResourceOwner owner)
+{
+	ResourceOwnerEnlarge(owner);
+}
+static inline void
+ResourceOwnerRememberCatCacheRef(ResourceOwner owner, HeapTuple tuple)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(tuple), &catcache_funcs);
+}
+static inline void
+ResourceOwnerForgetCatCacheRef(ResourceOwner owner, HeapTuple tuple)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(tuple), &catcache_funcs);
+}
+
+static inline void
+ResourceOwnerEnlargeCatCacheListRefs(ResourceOwner owner)
+{
+	ResourceOwnerEnlarge(owner);
+}
+static inline void
+ResourceOwnerRememberCatCacheListRef(ResourceOwner owner, CatCList *list)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(list), &catlistref_funcs);
+}
+
+static inline void
+ResourceOwnerForgetCatCacheListRef(ResourceOwner owner, CatCList *list)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(list), &catlistref_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
@@ -1270,7 +1331,7 @@ SearchCatCacheInternal(CatCache *cache,
 		 */
 		if (!ct->negative)
 		{
-			ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner);
+			ResourceOwnerEnlarge(CurrentResourceOwner);
 			ct->refcount++;
 			ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple);
 
@@ -1371,7 +1432,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 */
@@ -1583,7 +1644,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);
 
@@ -1607,7 +1668,7 @@ SearchCatCacheList(CatCache *cache,
 	 * block to ensure we can undo those refcounts if we get an error before
 	 * we finish constructing the CatCList.
 	 */
-	ResourceOwnerEnlargeCatCacheListRefs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	ctlist = NIL;
 
@@ -2062,14 +2123,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 +2149,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 50d6ad28b4c..8bce9d16ed5 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,31 @@ 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
+};
+
+static inline void
+ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(plan), &planref_resowner_funcs);
+}
+static inline void
+ResourceOwnerForgetPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(plan), &planref_resowner_funcs);
+}
+
+
 /* GUC parameter */
 int			plan_cache_mode;
 
@@ -1229,7 +1254,7 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 
 	/* Flag the plan as in use by caller */
 	if (useResOwner)
-		ResourceOwnerEnlargePlanCacheRefs(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 	plan->refcount++;
 	if (useResOwner)
 		ResourceOwnerRememberPlanCacheRef(CurrentResourceOwner, plan);
@@ -1392,7 +1417,7 @@ CachedPlanAllowsSimpleValidityCheck(CachedPlanSource *plansource,
 	/* Bump refcount if requested. */
 	if (owner)
 	{
-		ResourceOwnerEnlargePlanCacheRefs(owner);
+		ResourceOwnerEnlarge(owner);
 		plan->refcount++;
 		ResourceOwnerRememberPlanCacheRef(owner, plan);
 	}
@@ -1451,7 +1476,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);
 	}
@@ -2205,3 +2230,20 @@ ResetPlanCache(void)
 		cexpr->is_valid = false;
 	}
 }
+
+/*
+ * ResourceOwner callbacks
+ */
+
+static void
+ResOwnerReleaseCachedPlan(Datum res)
+{
+	ReleaseCachedPlan((CachedPlan *) DatumGetPointer(res), true);
+}
+
+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 66393becfb6..efff30e9238 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -78,13 +78,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"
 
@@ -2066,6 +2067,18 @@ 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
+};
+
 /*
  * RelationIncrementReferenceCount
  *		Increments relation reference count.
@@ -2077,10 +2090,10 @@ RelationIdGetRelation(Oid relationId)
 void
 RelationIncrementReferenceCount(Relation rel)
 {
-	ResourceOwnerEnlargeRelationRefs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	rel->rd_refcnt += 1;
 	if (!IsBootstrapProcessingMode())
-		ResourceOwnerRememberRelationRef(CurrentResourceOwner, rel);
+		ResourceOwnerRemember(CurrentResourceOwner, PointerGetDatum(rel), &relref_resowner_funcs);
 }
 
 /*
@@ -2093,7 +2106,7 @@ RelationDecrementReferenceCount(Relation rel)
 	Assert(rel->rd_refcnt > 0);
 	rel->rd_refcnt -= 1;
 	if (!IsBootstrapProcessingMode())
-		ResourceOwnerForgetRelationRef(CurrentResourceOwner, rel);
+		ResourceOwnerForget(CurrentResourceOwner, PointerGetDatum(rel), &relref_resowner_funcs);
 }
 
 /*
@@ -6406,3 +6419,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/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 809b27a038f..a3c4063e655 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -982,7 +982,7 @@ static const struct cachedesc cacheinfo[] = {
 	}
 };
 
-static CatCache *SysCache[SysCacheSize];
+ CatCache *SysCache[SysCacheSize];
 
 static bool CacheInitialized = false;
 
diff --git a/src/backend/utils/resowner/README b/src/backend/utils/resowner/README
index 2998f6bb362..890db7d1e66 100644
--- a/src/backend/utils/resowner/README
+++ b/src/backend/utils/resowner/README
@@ -60,13 +60,18 @@ subtransaction or portal.  Therefore, the "release" operation on a child
 ResourceOwner transfers lock ownership to the parent instead of actually
 releasing the lock, if isCommit is true.
 
-Currently, ResourceOwners contain direct support for recording ownership of
-buffer pins, lmgr locks, and catcache, relcache, plancache, tupdesc, and
-snapshot references.  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 objects.
+As of this writing, it's used internally for buffer pins, lmgr locks, and
+catcache, relcache, plancache, tupdesc, snapshot, DSM and JIT context references.
+ResourceOwner treates all objects the same, but to register a new kind of
+an object with it, you need to fill in a few callback functions, see
+ResourceOwnerFuncs.
+
+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. This used to be the only way to register new kinds
+of objects with a resource owner; nowadays it easier to write custom
+ResourceOwnerFuncs callabacks.
 
 Whenever we are inside a transaction, the global variable
 CurrentResourceOwner shows which resource owner should be assigned
diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index 8bc2c4e9ea3..2e1ae4f8e0c 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -21,73 +21,44 @@
 #include "postgres.h"
 
 #include "common/hashfn.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"
-
+#include "utils/plancache.h"
+#include "utils/resowner.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))
-
-/*
- * 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.
+ * ResourceElem represents a reference associated with a resource owner.
  *
- * 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.
- *
- * 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;
+} 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 8
 
 /*
- * 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 32
 
 /*
- * 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
@@ -117,22 +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 */
+	/*
+	 * 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;
 
 
 /*****************************************************************************
@@ -144,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
  */
@@ -158,44 +152,36 @@ 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 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);
+
+
 
 
 /*****************************************************************************
  *	  INTERNAL ROUTINES														 *
  *****************************************************************************/
 
-
-/*
- * Initialize a ResourceArray
- */
 static void
-ResourceArrayInit(ResourceArray *resarr, Datum invalidval)
+ResourceArrayAddToHash(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;
+
+	idx = DatumGetUInt32(hash_any((void *) &value, sizeof(value))) & mask;
+	for (;;)
+	{
+		if (owner->hash[idx].kind == NULL)
+			break;
+		idx = (idx + 1) & mask;
+	}
+	owner->hash[idx].item = value;
+	owner->hash[idx].kind = kind;
+	owner->nhash++;
 }
 
 /*
@@ -204,205 +190,222 @@ ResourceArrayInit(ResourceArray *resarr, Datum invalidval)
  * 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.
  */
-static void
-ResourceArrayEnlarge(ResourceArray *resarr)
+void
+ResourceOwnerEnlarge(ResourceOwner owner)
 {
-	uint32		i,
-				oldcap,
-				newcap;
-	Datum	   *olditemsarr;
-	Datum	   *newitemsarr;
-
-	if (resarr->nitems < resarr->maxitems)
+	if (owner->narr < RESOWNER_ARRAY_SIZE)
 		return;					/* no work needed */
 
-	olditemsarr = resarr->itemsarr;
-	oldcap = resarr->capacity;
+	/* Is there space in the hash? If not, enlarge it. */
 
 	/* 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;
-
-	/* 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 (olditemsarr != NULL)
+	if (owner->narr + owner->nhash >= owner->grow_at)
 	{
-		/*
-		 * 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.
-		 */
-		for (i = 0; i < oldcap; i++)
+		uint32		i,
+					oldcap,
+					newcap;
+		ResourceElem *oldhash;
+		ResourceElem *newhash;
+
+		oldhash = owner->hash;
+		oldcap = owner->capacity;
+
+		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 FIXME */
+		owner->hash = newhash;
+		owner->capacity = newcap;
+		owner->grow_at = RESOWNER_HASH_MAX_ITEMS(newcap);
+		owner->nhash = 0;
+
+		if (oldhash != NULL)
 		{
-			if (olditemsarr[i] != resarr->invalidval)
-				ResourceArrayAdd(resarr, olditemsarr[i]);
+			/*
+			 * 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.
+			 */
+			for (i = 0; i < oldcap; i++)
+			{
+				if (oldhash[i].kind != NULL)
+					ResourceArrayAddToHash(owner, oldhash[i].item, oldhash[i].kind);
+			}
+
+			/* And release old array. */
+			pfree(oldhash);
 		}
+	}
 
-		/* And release old array. */
-		pfree(olditemsarr);
+	/* Move items from the array to the hash */
+	for (int i = 0; i < owner->narr; i++)
+	{
+		ResourceArrayAddToHash(owner, owner->arr[i].item, owner->arr[i].kind);
 	}
+	owner->narr = 0;
 
-	Assert(resarr->nitems < resarr->maxitems);
+	Assert(owner->nhash < owner->grow_at);
 }
 
 /*
- * Add a resource to ResourceArray
+ * Remember that an object is owner by a ReourceOwner
  *
- * Caller must have previously done ResourceArrayEnlarge()
+ * Caller must have previously done ResourceOwnerEnlarge()
  */
-static void
-ResourceArrayAdd(ResourceArray *resarr, Datum value)
+void
+ResourceOwnerRemember(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind)
 {
 	uint32		idx;
 
-	Assert(value != resarr->invalidval);
-	Assert(resarr->nitems < resarr->maxitems);
+#ifdef RESOWNER_TRACE
+	elog(LOG, "REMEMBER %d: owner %p value " UINT64_FORMAT ", kind: %s",
+		 resowner_trace_counter++, owner, DatumGetUInt64(value), kind->name);
+#endif
 
-	if (RESARRAY_IS_ARRAY(resarr))
-	{
-		/* Append to linear array. */
-		idx = resarr->nitems;
-	}
-	else
-	{
-		/* Insert into first free slot at or after hash location. */
-		uint32		mask = resarr->capacity - 1;
+	Assert(owner->narr < RESOWNER_ARRAY_SIZE);
 
-		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++;
+	/* Append to linear array. */
+	idx = owner->narr;
+	owner->arr[idx].item = value;
+	owner->arr[idx].kind = kind;
+	owner->narr++;
 }
 
 /*
- * Remove a resource from ResourceArray
+ * Forget that an object is owned by a ResourceOwner
  *
- * Returns true on success, false if resource was not found.
+ * Returns true on success. If the resource was not found, returns false,
+ * and calls kind->ForgetError callback.
  *
- * Note: if same resource ID appears more than once, one instance is removed.
+ * Note: if same resource ID is associated with the ResourceOwner more than once,
+ * one instance is removed.
  */
-static bool
-ResourceArrayRemove(ResourceArray *resarr, Datum value)
+void
+ResourceOwnerForget(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind)
 {
 	uint32		i,
-				idx,
-				lastidx = resarr->lastidx;
+				idx;
 
-	Assert(value != resarr->invalidval);
+#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 try lastidx first. */
-	if (RESARRAY_IS_ARRAY(resarr))
+	/* Search through all items, but check the array first. */
+	for (i = 0; i < owner->narr; i++)
 	{
-		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++)
+		if (owner->arr[i].item == value &&
+			owner->arr[i].kind == kind)
 		{
-			if (resarr->itemsarr[i] == value)
-			{
-				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;
-			}
+			owner->arr[i] = owner->arr[owner->narr - 1];
+			owner->narr--;
+
+#ifdef RESOWNER_STATS
+			narray_lookups++;
+#endif
+
+			return;
 		}
 	}
-	else
+
+	/* Search hash */
+	if (owner->nhash > 0)
 	{
-		uint32		mask = resarr->capacity - 1;
+		uint32		mask = owner->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++)
+		for (i = 0; i < owner->capacity; i++)
 		{
-			if (resarr->itemsarr[idx] == value)
+			if (owner->hash[idx].item == value &&
+				owner->hash[idx].kind == kind)
 			{
-				resarr->itemsarr[idx] = resarr->invalidval;
-				resarr->nitems--;
-				return true;
+				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 false;
+	/*
+	 * 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), ResourceOwnerGetName(owner));
 }
 
 /*
- * 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.
- *
- * Returns true if we found an element, or false if the array is empty.
+ * Call the ReleaseResource callback on entries with given 'phase'.
  */
-static bool
-ResourceArrayGetAny(ResourceArray *resarr, Datum *value)
+static void
+ResourceOwnerReleaseAll(ResourceOwner owner, ResourceReleasePhase phase,
+						bool printLeakWarnings)
 {
-	if (resarr->nitems == 0)
-		return false;
+	bool		found;
 
-	if (RESARRAY_IS_ARRAY(resarr))
-	{
-		/* Linear array: just return the first element. */
-		resarr->lastidx = 0;
-	}
-	else
+	/* First handle all the entries in the array. */
+	do
 	{
-		/* Hash: search forward from wherever we were last. */
-		uint32		mask = resarr->capacity - 1;
+		found = false;
 
-		for (;;)
+		for (int i = 0; i < owner->narr; i++)
 		{
-			resarr->lastidx &= mask;
-			if (resarr->itemsarr[resarr->lastidx] != resarr->invalidval)
-				break;
-			resarr->lastidx++;
+			if (owner->arr[i].kind->phase == phase)
+			{
+				Datum		value = owner->arr[i].item;
+				ResourceOwnerFuncs *kind = owner->arr[i].kind;
+
+				if (printLeakWarnings)
+					kind->PrintLeakWarning(value);
+				kind->ReleaseResource(value);
+				found = true;
+			}
 		}
-	}
 
-	*value = resarr->itemsarr[resarr->lastidx];
-	return true;
-}
+		/*
+		 * If any resources were released, check again because some of the
+		 * elements might have moved by the callbacks. We don't want to
+		 * miss them.
+		 */
+	} while (found && owner->narr > 0);
 
-/*
- * Trash a ResourceArray (we don't care about its state after this)
- */
-static void
-ResourceArrayFree(ResourceArray *resarr)
-{
-	if (resarr->itemsarr)
-		pfree(resarr->itemsarr);
+	/* Ok, the array has now been handled. Then the hash */
+	do
+	{
+		found = false;
+
+		for (int idx = 0; idx < owner->capacity; idx++)
+		{
+			if (owner->hash[idx].kind != NULL &&
+				owner->hash[idx].kind->phase == phase)
+			{
+				/* found one */
+				Datum		value = owner->hash[idx].item;
+				ResourceOwnerFuncs *kind = owner->hash[idx].kind;
+
+				if (printLeakWarnings)
+					kind->PrintLeakWarning(value);
+				kind->ReleaseResource(value);
+				found = true;
+			}
+		}
+		/*
+		 * Like with the array, we must check again after we reach the
+		 * end, if any callbacks were called. XXX: We could probably
+		 * stipulate that the callbacks may not do certain thing, like
+		 * remember more references in the same resource owner, to avoid
+		 * that.
+		 */
+	}
+	while (found && owner->nhash > 0);
 }
 
 
@@ -434,16 +437,10 @@ ResourceOwnerCreate(ResourceOwner parent, const char *name)
 		parent->firstchild = owner;
 	}
 
-	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));
+#ifdef RESOWNER_TRACE
+	elog(LOG, "CREATE %d: %p %s",
+		 resowner_trace_counter++, owner, name);
+#endif
 
 	return owner;
 }
@@ -482,6 +479,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
@@ -493,7 +499,6 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 	ResourceOwner child;
 	ResourceOwner save;
 	ResourceReleaseCallbackItem *item;
-	Datum		foundres;
 
 	/* Recurse to handle descendants */
 	for (child = owner->firstchild; child != NULL; child = child->nextchild)
@@ -509,50 +514,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 references that need to be released before the locks.
 		 *
-		 * During a commit, there shouldn't be any remaining pins --- that
+		 * During a commit, there shouldn't be any remaining references --- 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 *) PointerGetDatum(foundres);
-
-			jit_release_context(context);
-		}
+		ResourceOwnerReleaseAll(owner, phase, isCommit);
 	}
 	else if (phase == RESOURCE_RELEASE_LOCKS)
 	{
@@ -561,7 +529,7 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 			/*
 			 * For a top-level xact we are going to release all locks (or at
 			 * least all non-session locks), so just do a single lmgr call at
-			 * the top of the recursion.
+			 * the top of the recursion
 			 */
 			if (owner == TopTransactionResourceOwner)
 			{
@@ -605,70 +573,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 references 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, true);
-		}
-
-		/* 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 */
@@ -689,16 +596,42 @@ void
 ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner)
 {
 	ResourceOwner save;
-	Datum		foundres;
 
 	save = CurrentResourceOwner;
 	CurrentResourceOwner = owner;
-	while (ResourceArrayGetAny(&(owner->planrefarr), &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 'false' because we already removed the entry from the resowner */
+			ReleaseCachedPlan(planref, false);
+		}
+	}
+
+	/* 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, true);
+			/* pass 'false' because we already removed the entry from the resowner */
+			ReleaseCachedPlan(planref, false);
+		}
 	}
+
 	CurrentResourceOwner = save;
 }
 
@@ -715,18 +648,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->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.
@@ -742,17 +672,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));
-
+	if (owner->hash)
+		pfree(owner->hash);
 	pfree(owner);
 }
 
@@ -807,6 +728,15 @@ ResourceOwnerNewParent(ResourceOwner owner,
 	}
 }
 
+/*
+ * Get the name of a resource owner, for debugging purposes
+ */
+const char *
+ResourceOwnerGetName(ResourceOwner owner)
+{
+	return owner->name;
+}
+
 /*
  * Register or deregister callback functions for resource cleanup
  *
@@ -905,44 +835,6 @@ ReleaseAuxProcessResourcesCallback(int code, Datum arg)
 	ReleaseAuxProcessResources(isCommit);
 }
 
-
-/*
- * Make sure there is room for at least one more entry in a ResourceOwner's
- * buffer 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
-ResourceOwnerEnlargeBuffers(ResourceOwner owner)
-{
-	/* 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
  *
@@ -994,379 +886,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);
-}
diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c
index 8c41483e87c..b3ea3ce7650 100644
--- a/src/backend/utils/time/snapmgr.c
+++ b/src/backend/utils/time/snapmgr.c
@@ -66,7 +66,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"
@@ -174,6 +174,18 @@ 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
+};
+
 /*
  * Snapshot fields to be serialized.
  *
@@ -831,9 +843,10 @@ 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);
+	ResourceOwnerRemember(owner, PointerGetDatum(snap),
+						  &snapshot_resowner_funcs);
 
 	if (snap->regd_count == 1)
 		pairingheap_add(&RegisteredSnapshots, &snap->ph_node);
@@ -870,7 +883,8 @@ UnregisterSnapshotFromOwner(Snapshot snapshot, ResourceOwner owner)
 	Assert(snapshot->regd_count > 0);
 	Assert(!pairingheap_is_empty(&RegisteredSnapshots));
 
-	ResourceOwnerForgetSnapshot(owner, snapshot);
+	ResourceOwnerForget(owner, PointerGetDatum(snapshot),
+						&snapshot_resowner_funcs);
 
 	snapshot->regd_count--;
 	if (snapshot->regd_count == 0)
@@ -2345,3 +2359,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/include/storage/buf_internals.h b/src/include/storage/buf_internals.h
index 3377fa56768..c4aa7d7e31e 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -296,6 +296,21 @@ typedef struct CkptSortItem
 
 extern CkptSortItem *CkptBufferIds;
 
+/* ResourceOwner callbacks to hold buffer pins */
+extern ResourceOwnerFuncs buffer_resowner_funcs;
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer)
+{
+	ResourceOwnerRemember(owner, Int32GetDatum(buffer), &buffer_resowner_funcs);
+}
+static inline void
+ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer 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 f4aa316604e..dbb10dfdd16 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 4901568553c..2e0d5006719 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 878f39ccf14..87a2bf0558e 100644
--- a/src/include/utils/resowner.h
+++ b/src/include/utils/resowner.h
@@ -50,6 +50,32 @@ typedef enum
 	RESOURCE_RELEASE_AFTER_LOCKS
 } ResourceReleasePhase;
 
+/*
+ * In order to track an object, resowner.c needs a few callbacks for it.
+ * The callbacks for an object of a specific kind are encapsulated in
+ * ResourceOwnerFuncs.
+ */
+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 +97,30 @@ 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 const char *ResourceOwnerGetName(ResourceOwner owner);
+
+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 a781a7a2aa6..00000000000
--- a/src/include/utils/resowner_private.h
+++ /dev/null
@@ -1,98 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * resowner_private.h
- *	  POSTGRES resource owner private definitions.
- *
- * See utils/resowner/README for more info.
- *
- *
- * Portions Copyright (c) 1996-2020, 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);
-
-#endif							/* RESOWNER_PRIVATE_H */
#2Michael Paquier
michael@paquier.xyz
In reply to: Heikki Linnakangas (#1)
Re: ResourceOwner refactoring

On Tue, Nov 17, 2020 at 04:21:29PM +0200, Heikki Linnakangas wrote:

2. It's difficult for extensions to use. There is a callback mechanism for
extensions, but it's much less convenient to use than the built-in
functions. The pgcrypto code uses the callbacks currently, and Michael's
patch [2] would move that support for tracking OpenSSL contexts to the core,
which makes it a lot more convenient for pgcrypto. Wouldn't it be nice if
extensions could have the same ergonomics as built-in code, if they need to
track external resources?

+1. True that the current interface is a bit hard to grasp, one can
easily miss that showing reference leaks is very important if an
allocation happens out of the in-core palloc machinery at commit time.
(The patch you are referring to is not really popular, but that does
not prevent this thread to move on on its own.)

Attached patch refactors the ResourceOwner internals to do that.

+ * Size of the small fixed-size array to hold most-recently remembered resources.
  */
-#define RESARRAY_INIT_SIZE 16
+#define RESOWNER_ARRAY_SIZE 8
Why did you choose this size for the initial array?

+extern const char *ResourceOwnerGetName(ResourceOwner owner);
This is only used in resowner.c, at one place.

@@ -140,7 +139,6 @@ jit_release_context(JitContext *context)
if (provider_successfully_loaded)
provider.release_context(context);

-   ResourceOwnerForgetJIT(context->resowner, PointerGetDatum(context));
[...]
+   ResourceOwnerForget(context->resowner, PointerGetDatum(context), &jit_funcs);
This moves the JIT context release from jit.c to llvm.c.  I think
that's indeed more consistent, and correct.  It would be better to
check this one with Andres, though.

I haven't done thorough performance testing of this, but with some quick
testing with Kyotaro's "catcachebench" to stress-test syscache lookups, this
performs a little better. In that test, all the activity happens in the
small array portion, but I believe that's true for most use cases.

Thoughts? Can anyone suggest test scenarios to verify the performance of
this?

Playing with catcache.c is the first thing that came to my mind.
After that some micro-benchmarking with DSM or snapshots? I am not
sure if we would notice a difference in a real-life scenario, say even
a pgbench -S with prepared queries.
--
Michael

#3Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Michael Paquier (#2)
Re: ResourceOwner refactoring

On 18/11/2020 10:06, Michael Paquier wrote:

On Tue, Nov 17, 2020 at 04:21:29PM +0200, Heikki Linnakangas wrote:

Attached patch refactors the ResourceOwner internals to do that.

+ * Size of the small fixed-size array to hold most-recently remembered resources.
*/
-#define RESARRAY_INIT_SIZE 16
+#define RESOWNER_ARRAY_SIZE 8
Why did you choose this size for the initial array?

Just a guess. The old init size was 16 Datums. The entries in the new
array are twice as large, Datum+pointer.

The "RESOWNER_STATS" #ifdef blocks can be enabled to check how many
lookups fit in the array. With pgbench, RESOWNER_ARRAY_SIZE 8:

RESOWNER STATS: lookups: array 235, hash 32

If RESOWNER_ARRAY_STATS is increased to 16, all the lookups fit in the
array. But I haven't done any benchmarking to see which is faster.

BTW, I think there would be an easy win in the hashing codepath, by
changing to a cheaper hash function. Currently, with or without this
patch, we use hash_any(). Changing that to murmurhash32() or something
similar would be a drop-in replacement.

- Heikki

#4Michael Paquier
michael@paquier.xyz
In reply to: Heikki Linnakangas (#3)
Re: ResourceOwner refactoring

On Wed, Nov 18, 2020 at 10:50:08AM +0200, Heikki Linnakangas wrote:

If RESOWNER_ARRAY_STATS is increased to 16, all the lookups fit in the
array. But I haven't done any benchmarking to see which is faster.

My gut tells me that your guess is right, but it would be better to be
sure.

BTW, I think there would be an easy win in the hashing codepath, by changing
to a cheaper hash function. Currently, with or without this patch, we use
hash_any(). Changing that to murmurhash32() or something similar would be a
drop-in replacement.

Good idea.
--
Michael

#5Julien Rouhaud
rjuju123@gmail.com
In reply to: Michael Paquier (#4)
Re: ResourceOwner refactoring

On Thu, Nov 19, 2020 at 10:16 AM Michael Paquier <michael@paquier.xyz> wrote:

On Wed, Nov 18, 2020 at 10:50:08AM +0200, Heikki Linnakangas wrote:

If RESOWNER_ARRAY_STATS is increased to 16, all the lookups fit in the
array. But I haven't done any benchmarking to see which is faster.

My gut tells me that your guess is right, but it would be better to be
sure.

BTW, I think there would be an easy win in the hashing codepath, by changing
to a cheaper hash function. Currently, with or without this patch, we use
hash_any(). Changing that to murmurhash32() or something similar would be a
drop-in replacement.

Good idea.

+1, and +1 for this refactoring.

I just saw a minor issue in a comment while reviewing the patch:

[...]
+ /* Is there space in the hash? If not, enlarge it. */

/* Double the capacity of the array (capacity must stay a power of 2!) */
- newcap = (oldcap > 0) ? oldcap * 2 : RESARRAY_INIT_SIZE;
[...]

The existing comment is kept as-is, but it should mention that it's
now the hash capacity that is increased.

#6Craig Ringer
craig.ringer@enterprisedb.com
In reply to: Heikki Linnakangas (#1)
1 attachment(s)
Re: ResourceOwner refactoring

[off-list for now]

On Tue, Nov 17, 2020 at 10:21 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

2. It's difficult for extensions to use. There is a callback mechanism
for extensions, but it's much less convenient to use than the built-in
functions. The pgcrypto code uses the callbacks currently, and Michael's
patch [2] would move that support for tracking OpenSSL contexts to the
core, which makes it a lot more convenient for pgcrypto. Wouldn't it be
nice if extensions could have the same ergonomics as built-in code, if
they need to track external resources?

Yes, in general I'm a big fan of making life easier for extensions (see
postscript), and this looks helpful. It's certainly fairly clear to read.
I'm in-principle in favour, though I haven't read the old resowner code
closely enough to have a strong opinion.

I haven't done thorough performance testing of this, but with some quick

testing with Kyotaro's "catcachebench" to stress-test syscache lookups,
this performs a little better. In that test, all the activity happens in
the small array portion, but I believe that's true for most use cases.

Thoughts? Can anyone suggest test scenarios to verify the performance of
this?

I didn't think of much really.

I have tossed together a patch on top of yours that adds some
systemtap/dtrace tracepoints and provides a demo systemtap script that
shows some basic stats collection done using them. My intention was to make
it easier to see where the work in the resource owner code was being done.
But after I did the initial version I realised that in this case it
probably doesn't add a lot of value over using targeted profiling of only
functions in resowner.c and their callees which is easy enough using perf
and regular DWARF debuginfo. So I didn't land up polishing it and adding
stats for the other counters. It's attached anyway, in case it's
interesting or useful to anyone.

P.S. At some point I want to tackle some things similar to what you've done
here for other kinds of backend resources. In particular, to allow
extensions to register their own heavyweight lock types - with the ability
to handle lock timeouts, and add callbacks in the deadlock detector to
allow extensions to register dependency edges that are not natively visible
to the deadlock detector and to choose victim priorities. Also some
enhancements to how pg_depend tracking can be used by extensions. And I
want to help get the proposed custom ProcSignal changes in too. I think
there's a whole lot to be done to make extensions easier and safer to
author in general, like providing a simple way to do error trapping and
recovery in an extension mainloop without copy/pasting a bunch of
PostgresMain code, better default signal handlers, startup/shutdown that
shares more with user backends, etc. Right now it's quite tricky to get
bgworkers to behave well. </wildhandwaving>

Attachments:

0001-Poc-of-systemtap-probes-for-resowner-timings.patchtext/x-patch; charset=US-ASCII; name=0001-Poc-of-systemtap-probes-for-resowner-timings.patchDownload
From 9f3e6978b1bbfeb8ca3f1cac42e86c4731143404 Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig.ringer@2ndquadrant.com>
Date: Wed, 18 Nov 2020 14:30:08 +0800
Subject: [PATCH] Poc of systemtap probes for resowner timings

---
 resowner.stp                          | 106 ++++++++++++++++++++++++++
 src/backend/utils/probes.d            |  22 ++++++
 src/backend/utils/resowner/resowner.c |  96 +++++++++++++++++++++--
 3 files changed, 216 insertions(+), 8 deletions(-)
 create mode 100644 resowner.stp

diff --git a/resowner.stp b/resowner.stp
new file mode 100644
index 0000000000..24b37b39e6
--- /dev/null
+++ b/resowner.stp
@@ -0,0 +1,106 @@
+# PATH="$(dirname $(realpath $(find build/ -name postgres -type f))):$PATH" /usr/local/systemtap/bin/stap -v ./resowner.stp
+
+/*
+.mark("resowner__created") $arg1:long $arg2:long
+.mark("resowner__delete__done") $arg1:long $arg2:long
+.mark("resowner__delete__start") $arg1:long $arg2:long
+.mark("resowner__enlarge__done") $arg1:long $arg2:long $arg3:long
+.mark("resowner__enlarge__skipped") $arg1:long $arg2:long
+.mark("resowner__enlarge__start") $arg1:long $arg2:long $arg3:long $arg4:long
+.mark("resowner__forget__done") $arg1:long $arg2:long $arg3:long $arg4:long $arg5:long
+.mark("resowner__forget__start") $arg1:long $arg2:long $arg3:long $arg4:long
+.mark("resowner__new__parent__done") $arg1:long
+.mark("resowner__new__parent__start") $arg1:long $arg2:long $arg3:long
+.mark("resowner__release__all__done") $arg1:long $arg2:long $arg3:long
+.mark("resowner__release__all__hash")
+.mark("resowner__release__all__start") $arg1:long $arg2:long $arg3:long $arg4:long $arg5:long
+.mark("resowner__release__done") $arg1:long $arg2:long
+.mark("resowner__release__plancacherefs__done") $arg1:long $arg2:long
+.mark("resowner__release__plancacherefs__start") $arg1:long $arg2:long
+.mark("resowner__release__start") $arg1:long $arg2:long $arg3:long $arg4:long $arg5:long
+.mark("resowner__remembered") $arg1:long $arg2:long $arg3:long $arg4:long
+*/
+
+@define probe_prefix %( sprintf("[%6d] %20s %20s %8x %20s", pid(), application_names[pid()], $$name, owner, owner_name) %)
+
+probe pg = process("postgres") {}
+
+# stats
+global operation_stats;
+
+# backend info
+private application_names;
+
+# in-progress operations
+private track_resowner_deletes;
+
+probe pg.begin {
+	if (@defined(@var("application_name@guc.c","postgres"))) {
+		application_names[pid()] = user_string(@var("application_name@guc.c","postgres"),"<?>")
+	} else {
+		application_names[pid()] = "<?>"
+	}
+	printf("[%6d] started %s\n", pid(), application_names[pid()])
+}
+
+probe pg.end {
+	delete application_names[pid()];
+	delete track_resowner_deletes[pid(),*];
+}
+
+probe pg.mark("resowner__created") {
+	owner = $arg1
+	owner_name = user_string($arg2)
+	printf("%s\n", @probe_prefix)
+}
+
+probe pg.mark("resowner__delete__start") {
+	owner = $arg1
+	owner_name = user_string($arg2)
+	track_resowner_deletes[pid(),owner] = gettimeofday_us()
+}
+
+probe pg.mark("resowner__delete__done") {
+	owner = $arg1
+	owner_name = user_string($arg2)
+	starttime_us = track_resowner_deletes[pid(),owner]
+	if (starttime_us != 0) {
+		elapsed = gettimeofday_us() - starttime_us
+		operation_stats["resowner_delete"] <<< elapsed
+		printf("%s: %s\n", @probe_prefix, usecs_to_string(elapsed));
+	}
+	delete track_resowner_deletes[pid(),owner]
+}
+
+function print_stat(statname) {
+	count = @count(operation_stats[statname])
+	if (count > 0) {
+		min = @min(operation_stats[statname])
+		max = @max(operation_stats[statname])
+		printf("-- %s:\n"
+		       "--    count:  %6d\n"
+		       "--    mean:   %6d\n"
+		       "--    stddev: %6d\n"
+		       "--    min:    %6d\n"
+		       "--    max:    %6d\n",
+		       statname,
+		       count,
+		       @avg(operation_stats[statname]),
+		       @variance(operation_stats[statname])/count,
+		       min, max
+		)
+		println(@hist_log(operation_stats[statname]))
+	}
+}
+
+function print_stats() {
+	print_stat("resowner_delete")
+}
+
+probe timer.ms(5000) {
+	print_stats()
+}
+
+probe end {
+	print_stats()
+ }
diff --git a/src/backend/utils/probes.d b/src/backend/utils/probes.d
index a0b0458108..7be815f9de 100644
--- a/src/backend/utils/probes.d
+++ b/src/backend/utils/probes.d
@@ -21,6 +21,9 @@
 #define Oid unsigned int
 #define ForkNumber int
 #define bool unsigned char
+#define Datum long int
+#define ResourceOwnerData void
+#define ResourceReleasePhase int
 
 provider postgresql {
 
@@ -91,4 +94,23 @@ provider postgresql {
 	probe wal__switch();
 	probe wal__buffer__write__dirty__start();
 	probe wal__buffer__write__dirty__done();
+
+	probe resowner__created(ResourceOwnerData*, const char *);
+	probe resowner__remembered(ResourceOwnerData*, const char *, Datum, const char *);
+	probe resowner__enlarge__skipped(ResourceOwnerData*, const char*);
+	probe resowner__enlarge__start(ResourceOwnerData*, const char *, int, int);
+	probe resowner__enlarge__done(ResourceOwnerData*, const char *, int);
+	probe resowner__forget__start(ResourceOwnerData*, const char *, Datum, const char *);
+	probe resowner__forget__done(ResourceOwnerData*, const char *, Datum, int, int);
+	probe resowner__release__all__start(ResourceOwnerData*, const char *, ResourceReleasePhase, int, int);
+	probe resowner__release__all__hash();
+	probe resowner__release__all__done(ResourceOwnerData*, const char *, ResourceReleasePhase);
+	probe resowner__release__start(ResourceOwnerData*, const char *, int, bool, bool);
+	probe resowner__release__done(ResourceOwnerData*, const char *);
+	probe resowner__delete__start(ResourceOwnerData*, const char *);
+	probe resowner__delete__done(long int, const char *);
+	probe resowner__release__plancacherefs__start(ResourceOwnerData*, const char *);
+	probe resowner__release__plancacherefs__done(ResourceOwnerData*, const char *);
+	probe resowner__new__parent__start(ResourceOwnerData*, ResourceOwnerData*, ResourceOwnerData*);
+	probe resowner__new__parent__done(ResourceOwnerData*);
 };
diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index 2e1ae4f8e0..fcd2c6f61d 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -26,6 +26,7 @@
 #include "storage/proc.h"
 #include "utils/memutils.h"
 #include "utils/plancache.h"
+#include "utils/probes.h"
 #include "utils/resowner.h"
 
 /*
@@ -152,10 +153,18 @@ static ResourceReleaseCallbackItem *ResourceRelease_callbacks = NULL;
 
 
 /* Internal routines */
-static void ResourceOwnerReleaseInternal(ResourceOwner owner,
+static void ResourceOwnerReleaseRecurse(ResourceOwner owner,
 										 ResourceReleasePhase phase,
 										 bool isCommit,
 										 bool isTopLevel);
+static void ResourceOwnerReleaseSelf(ResourceOwner owner,
+										 ResourceReleasePhase phase,
+										 bool isCommit,
+										 bool isTopLevel);
+static void ResourceOwnerReleaseCallbacks(ResourceOwner owner,
+                               ResourceReleasePhase phase,
+                               bool isCommit,
+                               bool isTopLevel);
 static void ReleaseAuxProcessResourcesCallback(int code, Datum arg);
 
 
@@ -194,9 +203,13 @@ void
 ResourceOwnerEnlarge(ResourceOwner owner)
 {
 	if (owner->narr < RESOWNER_ARRAY_SIZE)
+	{
+		TRACE_POSTGRESQL_RESOWNER_ENLARGE_SKIPPED(owner, owner->name);
 		return;					/* no work needed */
+	}
 
 	/* Is there space in the hash? If not, enlarge it. */
+	TRACE_POSTGRESQL_RESOWNER_ENLARGE_START(owner, owner->name, owner->nhash, owner->narr);
 
 	/* Double the capacity of the array (capacity must stay a power of 2!) */
 	if (owner->narr + owner->nhash >= owner->grow_at)
@@ -246,6 +259,8 @@ ResourceOwnerEnlarge(ResourceOwner owner)
 	owner->narr = 0;
 
 	Assert(owner->nhash < owner->grow_at);
+
+	TRACE_POSTGRESQL_RESOWNER_ENLARGE_DONE(owner, owner->name, owner->nhash);
 }
 
 /*
@@ -270,6 +285,8 @@ ResourceOwnerRemember(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind
 	owner->arr[idx].item = value;
 	owner->arr[idx].kind = kind;
 	owner->narr++;
+
+	TRACE_POSTGRESQL_RESOWNER_REMEMBERED(owner, owner->name, value, kind->name);
 }
 
 /*
@@ -292,6 +309,8 @@ ResourceOwnerForget(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind)
 		 resowner_trace_counter++, owner, DatumGetUInt64(value), kind->name);
 #endif
 
+	TRACE_POSTGRESQL_RESOWNER_FORGET_START(owner, owner->name, value, kind->name);
+
 	/* Search through all items, but check the array first. */
 	for (i = 0; i < owner->narr; i++)
 	{
@@ -305,6 +324,7 @@ ResourceOwnerForget(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind)
 			narray_lookups++;
 #endif
 
+			TRACE_POSTGRESQL_RESOWNER_FORGET_DONE(owner, owner->name, value, i, 0);
 			return;
 		}
 	}
@@ -327,6 +347,7 @@ ResourceOwnerForget(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind)
 #ifdef RESOWNER_STATS
 				nhash_lookups++;
 #endif
+				TRACE_POSTGRESQL_RESOWNER_FORGET_DONE(owner, owner->name, value, owner->narr, i);
 				return;
 			}
 			idx = (idx + 1) & mask;
@@ -351,6 +372,9 @@ ResourceOwnerReleaseAll(ResourceOwner owner, ResourceReleasePhase phase,
 {
 	bool		found;
 
+	TRACE_POSTGRESQL_RESOWNER_RELEASE_ALL_START(owner, owner->name, phase,
+					owner->narr, owner->nhash);
+
 	/* First handle all the entries in the array. */
 	do
 	{
@@ -377,6 +401,9 @@ ResourceOwnerReleaseAll(ResourceOwner owner, ResourceReleasePhase phase,
 		 */
 	} while (found && owner->narr > 0);
 
+	/* For probe/trace tools to time array and hash release separately */
+	TRACE_POSTGRESQL_RESOWNER_RELEASE_ALL_HASH();
+
 	/* Ok, the array has now been handled. Then the hash */
 	do
 	{
@@ -406,6 +433,8 @@ ResourceOwnerReleaseAll(ResourceOwner owner, ResourceReleasePhase phase,
 		 */
 	}
 	while (found && owner->nhash > 0);
+
+	TRACE_POSTGRESQL_RESOWNER_RELEASE_ALL_DONE(owner, owner->name, phase);
 }
 
 
@@ -420,6 +449,9 @@ ResourceOwnerReleaseAll(ResourceOwner owner, ResourceReleasePhase phase,
  *
  * All ResourceOwner objects are kept in TopMemoryContext, since they should
  * only be freed explicitly.
+ *
+ * The owner name is not copied. It should generally be a string constant. Otherwise
+ * the 
  */
 ResourceOwner
 ResourceOwnerCreate(ResourceOwner parent, const char *name)
@@ -442,6 +474,8 @@ ResourceOwnerCreate(ResourceOwner parent, const char *name)
 		 resowner_trace_counter++, owner, name);
 #endif
 
+	TRACE_POSTGRESQL_RESOWNER_CREATED(owner, owner->name);
+
 	return owner;
 }
 
@@ -478,7 +512,7 @@ ResourceOwnerRelease(ResourceOwner owner,
 					 bool isTopLevel)
 {
 	/* There's not currently any setup needed before recursing */
-	ResourceOwnerReleaseInternal(owner, phase, isCommit, isTopLevel);
+	ResourceOwnerReleaseRecurse(owner, phase, isCommit, isTopLevel);
 
 #ifdef RESOWNER_STATS
 	if (isTopLevel)
@@ -491,18 +525,19 @@ ResourceOwnerRelease(ResourceOwner owner,
 }
 
 static void
-ResourceOwnerReleaseInternal(ResourceOwner owner,
+ResourceOwnerReleaseRecurse(ResourceOwner owner,
 							 ResourceReleasePhase phase,
 							 bool isCommit,
 							 bool isTopLevel)
 {
 	ResourceOwner child;
 	ResourceOwner save;
-	ResourceReleaseCallbackItem *item;
+
+	TRACE_POSTGRESQL_RESOWNER_RELEASE_START(owner, owner->name, phase, isCommit, isTopLevel);
 
 	/* Recurse to handle descendants */
 	for (child = owner->firstchild; child != NULL; child = child->nextchild)
-		ResourceOwnerReleaseInternal(child, phase, isCommit, isTopLevel);
+		ResourceOwnerReleaseRecurse(child, phase, isCommit, isTopLevel);
 
 	/*
 	 * Make CurrentResourceOwner point to me, so that ReleaseBuffer etc don't
@@ -511,6 +546,27 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 	save = CurrentResourceOwner;
 	CurrentResourceOwner = owner;
 
+	/* Release directly managed resources */
+	ResourceOwnerReleaseSelf(owner, phase, isCommit, isTopLevel);
+
+	/* And run any release callbacks */
+	ResourceOwnerReleaseCallbacks(owner, phase, isCommit, isTopLevel);
+
+	CurrentResourceOwner = save;
+
+	TRACE_POSTGRESQL_RESOWNER_RELEASE_DONE(owner, owner->name);
+}
+
+/*
+ * Non-recursive part of ResourceOwnerRelease for a single resowner's
+ * resources.
+ */
+static void
+ResourceOwnerReleaseSelf(ResourceOwner owner,
+                               ResourceReleasePhase phase,
+                               bool isCommit,
+                               bool isTopLevel)
+{
 	if (phase == RESOURCE_RELEASE_BEFORE_LOCKS)
 	{
 		/*
@@ -577,12 +633,18 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 		 */
 		ResourceOwnerReleaseAll(owner, phase, isCommit);
 	}
+}
+
+static void
+ResourceOwnerReleaseCallbacks(ResourceOwner owner,
+                               ResourceReleasePhase phase,
+                               bool isCommit,
+                               bool isTopLevel)
+{
+	ResourceReleaseCallbackItem *item;
 
-	/* Let add-on modules get a chance too */
 	for (item = ResourceRelease_callbacks; item; item = item->next)
 		item->callback(phase, isCommit, isTopLevel, item->arg);
-
-	CurrentResourceOwner = save;
 }
 
 /*
@@ -600,6 +662,8 @@ ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner)
 	save = CurrentResourceOwner;
 	CurrentResourceOwner = owner;
 
+	TRACE_POSTGRESQL_RESOWNER_RELEASE_PLANCACHEREFS_START(owner, owner->name);
+
 	/* array first */
 	for (int i = 0; i < owner->narr; i++)
 	{
@@ -633,6 +697,8 @@ ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner)
 	}
 
 	CurrentResourceOwner = save;
+
+	TRACE_POSTGRESQL_RESOWNER_RELEASE_PLANCACHEREFS_DONE(owner, owner->name);
 }
 
 /*
@@ -644,6 +710,8 @@ ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner)
 void
 ResourceOwnerDelete(ResourceOwner owner)
 {
+	const char * const name = owner->name;
+
 	/* We had better not be deleting CurrentResourceOwner ... */
 	Assert(owner != CurrentResourceOwner);
 
@@ -657,6 +725,8 @@ ResourceOwnerDelete(ResourceOwner owner)
 		 resowner_trace_counter++, owner, owner->name);
 #endif
 
+	TRACE_POSTGRESQL_RESOWNER_DELETE_START(owner, owner->name);
+
 	/*
 	 * Delete children.  The recursive call will delink the child from me, so
 	 * just iterate as long as there is a child.
@@ -675,6 +745,12 @@ ResourceOwnerDelete(ResourceOwner owner)
 	if (owner->hash)
 		pfree(owner->hash);
 	pfree(owner);
+
+	/*
+	 * Passing the free'd owner pointer here is deliberate, it serves to
+	 * identify the freed owner when concurrently tracing many backends.
+	 */
+	TRACE_POSTGRESQL_RESOWNER_DELETE_DONE((long int)owner, name);
 }
 
 /*
@@ -695,6 +771,8 @@ ResourceOwnerNewParent(ResourceOwner owner,
 {
 	ResourceOwner oldparent = owner->parent;
 
+	TRACE_POSTGRESQL_RESOWNER_NEW_PARENT_START(owner, oldparent, newparent);
+
 	if (oldparent)
 	{
 		if (owner == oldparent->firstchild)
@@ -726,6 +804,8 @@ ResourceOwnerNewParent(ResourceOwner owner,
 		owner->parent = NULL;
 		owner->nextchild = NULL;
 	}
+
+	TRACE_POSTGRESQL_RESOWNER_NEW_PARENT_DONE(owner);
 }
 
 /*
-- 
2.26.2

#7Michael Paquier
michael@paquier.xyz
In reply to: Craig Ringer (#6)
Re: ResourceOwner refactoring

On Thu, Nov 19, 2020 at 06:40:19PM +0800, Craig Ringer wrote:

[off-list for now]

Looks like you have missed something here.
--
Michael

#8Kyotaro Horiguchi
horikyota.ntt@gmail.com
In reply to: Julien Rouhaud (#5)
Re: ResourceOwner refactoring

At Thu, 19 Nov 2020 17:27:18 +0800, Julien Rouhaud <rjuju123@gmail.com> wrote in

On Thu, Nov 19, 2020 at 10:16 AM Michael Paquier <michael@paquier.xyz> wrote:

On Wed, Nov 18, 2020 at 10:50:08AM +0200, Heikki Linnakangas wrote:

If RESOWNER_ARRAY_STATS is increased to 16, all the lookups fit in the
array. But I haven't done any benchmarking to see which is faster.

My gut tells me that your guess is right, but it would be better to be
sure.

BTW, I think there would be an easy win in the hashing codepath, by changing
to a cheaper hash function. Currently, with or without this patch, we use
hash_any(). Changing that to murmurhash32() or something similar would be a
drop-in replacement.

Good idea.

+1, and +1 for this refactoring.

+1 for making the interface more generic. I thought a similar thing
when I added an resowner array for WaitEventSet (not committed yet).
About performance, though I'm not sure about, there's no reason not to
do this as far as the resowner mechanism doesn't get slower, and +2 if
gets faster.

I just saw a minor issue in a comment while reviewing the patch:

[...]
+ /* Is there space in the hash? If not, enlarge it. */

/* Double the capacity of the array (capacity must stay a power of 2!) */
- newcap = (oldcap > 0) ? oldcap * 2 : RESARRAY_INIT_SIZE;
[...]

The existing comment is kept as-is, but it should mention that it's
now the hash capacity that is increased.

+ /* And release old array. */
+ pfree(oldhash);

:p

+		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;
+
+				if (printLeakWarnings)
+					kind->PrintLeakWarning(value);
+				kind->ReleaseResource(value);
+				found = true;
+			}
+		/*
+		 * If any resources were released, check again because some of the
+		 * elements might have moved by the callbacks. We don't want to
+		 * miss them.
+		 */
+	} while (found && owner->narr > 0);

Coundn't that missing be avoided by just not incrementing i if found?

+		/*
+		 * Like with the array, we must check again after we reach the
+		 * end, if any callbacks were called. XXX: We could probably
+		 * stipulate that the callbacks may not do certain thing, like
+		 * remember more references in the same resource owner, to avoid
+		 * that.
+		 */

If I read this patch correctly, ResourceOwnerForget doesn't seem to do
such a thing for hash?

regards.

--
Kyotaro Horiguchi
NTT Open Source Software Center

#9Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Kyotaro Horiguchi (#8)
1 attachment(s)
Re: ResourceOwner refactoring

On 26/11/2020 10:52, Kyotaro Horiguchi wrote:

+		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;
+
+				if (printLeakWarnings)
+					kind->PrintLeakWarning(value);
+				kind->ReleaseResource(value);
+				found = true;
+			}
+		/*
+		 * If any resources were released, check again because some of the
+		 * elements might have moved by the callbacks. We don't want to
+		 * miss them.
+		 */
+	} while (found && owner->narr > 0);

Coundn't that missing be avoided by just not incrementing i if found?

Hmm, perhaps. ResourceOwnerForget() can move an entry from the end of
the array to the beginning, but if it's removing the entry that we're
just looking at, it probably can't move anything before that entry. I'm
not very comfortable with that, though. What if the callback releases
something else as a side effect?

This isn't super-critical for performance, and given that the array is
very small, it's very cheap to loop through it. So I'm inclined to keep
it safe.

+		/*
+		 * Like with the array, we must check again after we reach the
+		 * end, if any callbacks were called. XXX: We could probably
+		 * stipulate that the callbacks may not do certain thing, like
+		 * remember more references in the same resource owner, to avoid
+		 * that.
+		 */

If I read this patch correctly, ResourceOwnerForget doesn't seem to do
such a thing for hash?

Hmm, true. I tried removing the loop and hit another issue, however: if
the same resource has been remembered twice in the same resource owner,
the callback might remove different reference to it than what we're
looking at. So we need to loop anyway to handle that. Also, what if the
callback remembers some other resource in the same resowner, causing the
hash to grow? I'm not sure if any of the callbacks currently do that,
but better safe than sorry. I updated the code and the comments accordingly.

Here's an updated version of this patch. I fixed the bit rot, and
addressed all the other comment issues and such that people pointed out
(thanks!).

TODO:

1. Performance testing. We discussed this a little bit, but I haven't
done any more testing.

2. Before this patch, the ResourceOwnerEnlarge() calls enlarged the
array for the particular "kind" of resource, but now they are all stored
in the same structure. That could lead to trouble if we do something
like this:

ResourceOwnerEnlargeAAA()
ResourceOwnerEnlargeBBB()
ResourceOwnerRememberAAA()
ResourceOwnerRememberBBB()

Previously, this was OK, because resources AAA and BBB were kept in
separate arrays. But after this patch, it's not guaranteed that the
ResourceOwnerRememberBBB() will find an empty slot.

I don't think we do that currently, but to be sure, I'm planning to grep
ResourceOwnerRemember and look at each call carefullly. And perhaps we
can add an assertion for this, although I'm not sure where.

- Heikki

Attachments:

v2-0001-Make-resowners-more-easily-extensible.patchtext/x-patch; charset=UTF-8; name=v2-0001-Make-resowners-more-easily-extensible.patchDownload
From 71a372b9a82a5b50a40386b403d256b4f6fac794 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 16 Dec 2020 17:26:03 +0200
Subject: [PATCH v2 1/1] Make resowners more easily extensible.

Use a single array and hash, instead of one for each object kind.
---
 src/backend/access/common/tupdesc.c   |   42 +-
 src/backend/jit/jit.c                 |    2 -
 src/backend/jit/llvm/llvmjit.c        |   40 +-
 src/backend/storage/buffer/bufmgr.c   |   43 +-
 src/backend/storage/buffer/localbuf.c |    2 +-
 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    |   98 ++-
 src/backend/utils/cache/plancache.c   |   50 +-
 src/backend/utils/cache/relcache.c    |   39 +-
 src/backend/utils/cache/syscache.c    |    2 +-
 src/backend/utils/resowner/README     |   19 +-
 src/backend/utils/resowner/resowner.c | 1139 +++++++------------------
 src/backend/utils/time/snapmgr.c      |   38 +-
 src/common/cryptohash_openssl.c       |   45 +-
 src/include/storage/buf_internals.h   |   15 +
 src/include/utils/catcache.h          |    3 -
 src/include/utils/plancache.h         |    2 +
 src/include/utils/resowner.h          |   41 +-
 src/include/utils/resowner_private.h  |  105 ---
 21 files changed, 795 insertions(+), 1020 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 30c30cf3a2e..c099f04f551 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -29,9 +29,21 @@
 #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
+};
 
 /*
  * CreateTemplateTupleDesc
@@ -376,9 +388,10 @@ IncrTupleDescRefCount(TupleDesc tupdesc)
 {
 	Assert(tupdesc->tdrefcount >= 0);
 
-	ResourceOwnerEnlargeTupleDescs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	tupdesc->tdrefcount++;
-	ResourceOwnerRememberTupleDesc(CurrentResourceOwner, tupdesc);
+	ResourceOwnerRemember(CurrentResourceOwner, PointerGetDatum(tupdesc),
+						  &tupdesc_resowner_funcs);
 }
 
 /*
@@ -394,7 +407,8 @@ DecrTupleDescRefCount(TupleDesc tupdesc)
 {
 	Assert(tupdesc->tdrefcount > 0);
 
-	ResourceOwnerForgetTupleDesc(CurrentResourceOwner, tupdesc);
+	ResourceOwnerForget(CurrentResourceOwner, PointerGetDatum(tupdesc),
+						&tupdesc_resowner_funcs);
 	if (--tupdesc->tdrefcount == 0)
 		FreeTupleDesc(tupdesc);
 }
@@ -925,3 +939,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 5ca3f922fed..c44080b9742 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 9c4fc75f656..d01a315b20f 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,8 +121,20 @@ static LLVMOrcLLJITRef llvm_create_jit_instance(LLVMTargetMachineRef tm);
 static char *llvm_error_message(LLVMErrorRef error);
 #endif							/* LLVM_VERSION_MAJOR > 11 */
 
-PG_MODULE_MAGIC;
+/* ResourceOwner callbacks to hold JitContexts  */
+static void ResOwnerReleaseJitContext(Datum res);
+static void ResOwnerPrintJitContextLeakWarning(Datum res);
+
+static ResourceOwnerFuncs jit_funcs =
+{
+	/* relcache references */
+	.name = "LLVM JIT context",
+	.phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.ReleaseResource = ResOwnerReleaseJitContext,
+	.PrintLeakWarning = ResOwnerPrintJitContextLeakWarning
+};
 
+PG_MODULE_MAGIC;
 
 /*
  * Initialize LLVM JIT provider.
@@ -151,7 +163,7 @@ llvm_create_context(int jitFlags)
 
 	llvm_session_initialize();
 
-	ResourceOwnerEnlargeJIT(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	context = MemoryContextAllocZero(TopMemoryContext,
 									 sizeof(LLVMJitContext));
@@ -159,7 +171,7 @@ llvm_create_context(int jitFlags)
 
 	/* ensure cleanup */
 	context->base.resowner = CurrentResourceOwner;
-	ResourceOwnerRememberJIT(CurrentResourceOwner, PointerGetDatum(context));
+	ResourceOwnerRemember(CurrentResourceOwner, PointerGetDatum(context), &jit_funcs);
 
 	return context;
 }
@@ -221,6 +233,8 @@ llvm_release_context(JitContext *context)
 
 		pfree(jit_handle);
 	}
+
+	ResourceOwnerForget(context->resowner, PointerGetDatum(context), &jit_funcs);
 }
 
 /*
@@ -1231,3 +1245,21 @@ llvm_error_message(LLVMErrorRef error)
 }
 
 #endif							/* LLVM_VERSION_MAJOR > 11 */
+
+/*
+ * ResourceOwner callbacks
+ */
+static void
+ResOwnerReleaseJitContext(Datum res)
+{
+	jit_release_context((JitContext *) PointerGetDatum(res));
+}
+
+static void
+ResOwnerPrintJitContextLeakWarning(Datum res)
+{
+	/* XXX: We used to not print these. Was that intentional? */
+	JitContext *context = (JitContext *) PointerGetDatum(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 c5e87071517..7c6f174c489 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -52,7 +52,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"
 
 
@@ -198,6 +198,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
@@ -727,7 +739,7 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 	*hit = false;
 
 	/* Make sure we will have room to remember the buffer pin */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	isExtend = (blockNum == P_NEW);
 
@@ -1846,7 +1858,7 @@ BufferSync(int flags)
 	WritebackContext wb_context;
 
 	/* Make sure we can handle the pin inside SyncOneBuffer */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	/*
 	 * Unless this is a shutdown checkpoint or we have been explicitly told,
@@ -2323,7 +2335,7 @@ BgBufferSync(WritebackContext *wb_context)
 	 */
 
 	/* Make sure we can handle the pin inside SyncOneBuffer */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	num_to_scan = bufs_to_lap;
 	num_written = 0;
@@ -3300,7 +3312,7 @@ FlushRelationBuffers(Relation rel)
 	}
 
 	/* Make sure we can handle the pin inside the loop */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	for (i = 0; i < NBuffers; i++)
 	{
@@ -3373,7 +3385,7 @@ FlushRelationsAllBuffers(SMgrRelation *smgrs, int nrels)
 		pg_qsort(srels, nrels, sizeof(SMgrSortArray), rnode_comparator);
 
 	/* Make sure we can handle the pin inside the loop */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	for (i = 0; i < NBuffers; i++)
 	{
@@ -3452,7 +3464,7 @@ FlushDatabaseBuffers(Oid dbid)
 	BufferDesc *bufHdr;
 
 	/* Make sure we can handle the pin inside the loop */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	for (i = 0; i < NBuffers; i++)
 	{
@@ -3550,7 +3562,7 @@ void
 IncrBufferRefCount(Buffer buffer)
 {
 	Assert(BufferIsPinned(buffer));
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	if (BufferIsLocal(buffer))
 		LocalRefCount[-buffer - 1]++;
 	else
@@ -4584,3 +4596,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 cd3475e9e1d..4eeed7d66f9 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -22,7 +22,7 @@
 #include "storage/bufmgr.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 
 
 /*#define LBDEBUG*/
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index f07b5325aa5..5d85bd8d1a7 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -96,7 +96,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)
@@ -339,6 +339,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
  */
@@ -1429,7 +1447,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
@@ -1611,7 +1629,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
@@ -1741,7 +1759,7 @@ PathNameCreateTemporaryFile(const char *path, bool error_on_failure)
 {
 	File		file;
 
-	ResourceOwnerEnlargeFiles(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	/*
 	 * Open the file.  Note: we don't use O_EXCL, in case there is an orphaned
@@ -1779,7 +1797,7 @@ PathNameOpenTemporaryFile(const char *path, int mode)
 {
 	File		file;
 
-	ResourceOwnerEnlargeFiles(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	file = PathNameOpenFile(path, mode | PG_BINARY);
 
@@ -3635,3 +3653,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 dffbd8e82a2..c665bc6cecf 100644
--- a/src/backend/storage/ipc/dsm.c
+++ b/src/backend/storage/ipc/dsm.c
@@ -37,13 +37,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
 
@@ -139,6 +141,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.
  *
@@ -895,7 +916,7 @@ void
 dsm_unpin_mapping(dsm_segment *seg)
 {
 	Assert(seg->resowner == NULL);
-	ResourceOwnerEnlargeDSMs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	seg->resowner = CurrentResourceOwner;
 	ResourceOwnerRememberDSM(seg->resowner, seg);
 }
@@ -1162,7 +1183,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);
@@ -1241,3 +1262,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 53472dd21ec..25e025f4f57 100644
--- a/src/backend/storage/lmgr/lock.c
+++ b/src/backend/storage/lmgr/lock.c
@@ -47,7 +47,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 3613ae5f44d..c9ebf9effe1 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,66 @@ 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_funcs =
+{
+	/* catcache references */
+	.name = "catcache reference",
+	.phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.ReleaseResource = ResOwnerReleaseCatCache,
+	.PrintLeakWarning = ResOwnerPrintCatCacheLeakWarning
+};
+
+static ResourceOwnerFuncs catlistref_funcs =
+{
+	/* catcache-list pins */
+	.name = "catcache list reference",
+	.phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.ReleaseResource = ResOwnerReleaseCatCacheList,
+	.PrintLeakWarning = ResOwnerPrintCatCacheListLeakWarning
+};
+
+/* support for catcache refcount management */
+static inline void
+ResourceOwnerEnlargeCatCacheRefs(ResourceOwner owner)
+{
+	ResourceOwnerEnlarge(owner);
+}
+static inline void
+ResourceOwnerRememberCatCacheRef(ResourceOwner owner, HeapTuple tuple)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(tuple), &catcache_funcs);
+}
+static inline void
+ResourceOwnerForgetCatCacheRef(ResourceOwner owner, HeapTuple tuple)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(tuple), &catcache_funcs);
+}
+
+static inline void
+ResourceOwnerEnlargeCatCacheListRefs(ResourceOwner owner)
+{
+	ResourceOwnerEnlarge(owner);
+}
+static inline void
+ResourceOwnerRememberCatCacheListRef(ResourceOwner owner, CatCList *list)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(list), &catlistref_funcs);
+}
+
+static inline void
+ResourceOwnerForgetCatCacheListRef(ResourceOwner owner, CatCList *list)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(list), &catlistref_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
@@ -1270,7 +1331,7 @@ SearchCatCacheInternal(CatCache *cache,
 		 */
 		if (!ct->negative)
 		{
-			ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner);
+			ResourceOwnerEnlarge(CurrentResourceOwner);
 			ct->refcount++;
 			ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple);
 
@@ -1371,7 +1432,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 */
@@ -1583,7 +1644,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);
 
@@ -1607,7 +1668,7 @@ SearchCatCacheList(CatCache *cache,
 	 * block to ensure we can undo those refcounts if we get an error before
 	 * we finish constructing the CatCList.
 	 */
-	ResourceOwnerEnlargeCatCacheListRefs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	ctlist = NIL;
 
@@ -2062,14 +2123,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 +2149,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 50d6ad28b4c..8bce9d16ed5 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,31 @@ 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
+};
+
+static inline void
+ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(plan), &planref_resowner_funcs);
+}
+static inline void
+ResourceOwnerForgetPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(plan), &planref_resowner_funcs);
+}
+
+
 /* GUC parameter */
 int			plan_cache_mode;
 
@@ -1229,7 +1254,7 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 
 	/* Flag the plan as in use by caller */
 	if (useResOwner)
-		ResourceOwnerEnlargePlanCacheRefs(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 	plan->refcount++;
 	if (useResOwner)
 		ResourceOwnerRememberPlanCacheRef(CurrentResourceOwner, plan);
@@ -1392,7 +1417,7 @@ CachedPlanAllowsSimpleValidityCheck(CachedPlanSource *plansource,
 	/* Bump refcount if requested. */
 	if (owner)
 	{
-		ResourceOwnerEnlargePlanCacheRefs(owner);
+		ResourceOwnerEnlarge(owner);
 		plan->refcount++;
 		ResourceOwnerRememberPlanCacheRef(owner, plan);
 	}
@@ -1451,7 +1476,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);
 	}
@@ -2205,3 +2230,20 @@ ResetPlanCache(void)
 		cexpr->is_valid = false;
 	}
 }
+
+/*
+ * ResourceOwner callbacks
+ */
+
+static void
+ResOwnerReleaseCachedPlan(Datum res)
+{
+	ReleaseCachedPlan((CachedPlan *) DatumGetPointer(res), true);
+}
+
+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 3bd5e180425..534e33a23c3 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -78,13 +78,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"
 
@@ -2065,6 +2066,18 @@ 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
+};
+
 /*
  * RelationIncrementReferenceCount
  *		Increments relation reference count.
@@ -2076,10 +2089,10 @@ RelationIdGetRelation(Oid relationId)
 void
 RelationIncrementReferenceCount(Relation rel)
 {
-	ResourceOwnerEnlargeRelationRefs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	rel->rd_refcnt += 1;
 	if (!IsBootstrapProcessingMode())
-		ResourceOwnerRememberRelationRef(CurrentResourceOwner, rel);
+		ResourceOwnerRemember(CurrentResourceOwner, PointerGetDatum(rel), &relref_resowner_funcs);
 }
 
 /*
@@ -2092,7 +2105,7 @@ RelationDecrementReferenceCount(Relation rel)
 	Assert(rel->rd_refcnt > 0);
 	rel->rd_refcnt -= 1;
 	if (!IsBootstrapProcessingMode())
-		ResourceOwnerForgetRelationRef(CurrentResourceOwner, rel);
+		ResourceOwnerForget(CurrentResourceOwner, PointerGetDatum(rel), &relref_resowner_funcs);
 }
 
 /*
@@ -6404,3 +6417,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/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 809b27a038f..a3c4063e655 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -982,7 +982,7 @@ static const struct cachedesc cacheinfo[] = {
 	}
 };
 
-static CatCache *SysCache[SysCacheSize];
+ CatCache *SysCache[SysCacheSize];
 
 static bool CacheInitialized = false;
 
diff --git a/src/backend/utils/resowner/README b/src/backend/utils/resowner/README
index 2998f6bb362..890db7d1e66 100644
--- a/src/backend/utils/resowner/README
+++ b/src/backend/utils/resowner/README
@@ -60,13 +60,18 @@ subtransaction or portal.  Therefore, the "release" operation on a child
 ResourceOwner transfers lock ownership to the parent instead of actually
 releasing the lock, if isCommit is true.
 
-Currently, ResourceOwners contain direct support for recording ownership of
-buffer pins, lmgr locks, and catcache, relcache, plancache, tupdesc, and
-snapshot references.  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 objects.
+As of this writing, it's used internally for buffer pins, lmgr locks, and
+catcache, relcache, plancache, tupdesc, snapshot, DSM and JIT context references.
+ResourceOwner treates all objects the same, but to register a new kind of
+an object with it, you need to fill in a few callback functions, see
+ResourceOwnerFuncs.
+
+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. This used to be the only way to register new kinds
+of objects with a resource owner; nowadays it easier to write custom
+ResourceOwnerFuncs callabacks.
 
 Whenever we are inside a transaction, the global variable
 CurrentResourceOwner shows which resource owner should be assigned
diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index 546ad8d1c5f..9806793e03b 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -22,73 +22,44 @@
 
 #include "common/cryptohash.h"
 #include "common/hashfn.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"
-
+#include "utils/plancache.h"
+#include "utils/resowner.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))
-
-/*
- * 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.
+ * ResourceElem represents a reference associated with a resource owner.
  *
- * 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.
- *
- * 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;
+} 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 8
 
 /*
- * 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 32
 
 /*
- * 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
@@ -118,23 +89,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 */
+	/*
+	 * 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;
 
 
 /*****************************************************************************
@@ -146,6 +127,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
  */
@@ -160,45 +153,34 @@ 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 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);
 
 
 /*****************************************************************************
  *	  INTERNAL ROUTINES														 *
  *****************************************************************************/
 
-
-/*
- * Initialize a ResourceArray
- */
 static void
-ResourceArrayInit(ResourceArray *resarr, Datum invalidval)
+ResourceArrayAddToHash(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;
+
+	idx = DatumGetUInt32(hash_any((void *) &value, sizeof(value))) & mask;
+	for (;;)
+	{
+		if (owner->hash[idx].kind == NULL)
+			break;
+		idx = (idx + 1) & mask;
+	}
+	owner->hash[idx].item = value;
+	owner->hash[idx].kind = kind;
+	owner->nhash++;
 }
 
 /*
@@ -207,205 +189,227 @@ ResourceArrayInit(ResourceArray *resarr, Datum invalidval)
  * 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.
  */
-static void
-ResourceArrayEnlarge(ResourceArray *resarr)
+void
+ResourceOwnerEnlarge(ResourceOwner owner)
 {
-	uint32		i,
-				oldcap,
-				newcap;
-	Datum	   *olditemsarr;
-	Datum	   *newitemsarr;
-
-	if (resarr->nitems < resarr->maxitems)
+	if (owner->narr < RESOWNER_ARRAY_SIZE)
 		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;
-
-	/* 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 (olditemsarr != NULL)
+	/* Is there space in the hash? If not, enlarge it. */
+	if (owner->narr + owner->nhash >= owner->grow_at)
 	{
-		/*
-		 * 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.
-		 */
-		for (i = 0; i < oldcap; 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 FIXME */
+		owner->hash = newhash;
+		owner->capacity = newcap;
+		owner->grow_at = RESOWNER_HASH_MAX_ITEMS(newcap);
+		owner->nhash = 0;
+
+		if (oldhash != NULL)
 		{
-			if (olditemsarr[i] != resarr->invalidval)
-				ResourceArrayAdd(resarr, olditemsarr[i]);
+			/*
+			 * 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++)
+			{
+				if (oldhash[i].kind != NULL)
+					ResourceArrayAddToHash(owner, oldhash[i].item, oldhash[i].kind);
+			}
+
+			/* And release old hash table. */
+			pfree(oldhash);
 		}
+	}
 
-		/* And release old array. */
-		pfree(olditemsarr);
+	/* Move items from the array to the hash */
+	for (int i = 0; i < owner->narr; i++)
+	{
+		ResourceArrayAddToHash(owner, owner->arr[i].item, owner->arr[i].kind);
 	}
+	owner->narr = 0;
 
-	Assert(resarr->nitems < resarr->maxitems);
+	Assert(owner->nhash < owner->grow_at);
 }
 
 /*
- * Add a resource to ResourceArray
+ * Remember that an object is owner by a ReourceOwner
  *
- * Caller must have previously done ResourceArrayEnlarge()
+ * Caller must have previously done ResourceOwnerEnlarge()
  */
-static void
-ResourceArrayAdd(ResourceArray *resarr, Datum value)
+void
+ResourceOwnerRemember(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind)
 {
 	uint32		idx;
 
-	Assert(value != resarr->invalidval);
-	Assert(resarr->nitems < resarr->maxitems);
+#ifdef RESOWNER_TRACE
+	elog(LOG, "REMEMBER %d: owner %p value " UINT64_FORMAT ", kind: %s",
+		 resowner_trace_counter++, owner, DatumGetUInt64(value), kind->name);
+#endif
 
-	if (RESARRAY_IS_ARRAY(resarr))
-	{
-		/* Append to linear array. */
-		idx = resarr->nitems;
-	}
-	else
-	{
-		/* Insert into first free slot at or after hash location. */
-		uint32		mask = resarr->capacity - 1;
+	Assert(owner->narr < RESOWNER_ARRAY_SIZE);
 
-		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++;
+	/* Append to linear array. */
+	idx = owner->narr;
+	owner->arr[idx].item = value;
+	owner->arr[idx].kind = kind;
+	owner->narr++;
 }
 
 /*
- * Remove a resource from ResourceArray
+ * Forget that an object is owned by a ResourceOwner
  *
- * Returns true on success, false if resource was not found.
+ * Returns true on success. If the resource was not found, returns false,
+ * and calls kind->ForgetError callback.
  *
- * Note: if same resource ID appears more than once, one instance is removed.
+ * Note: if same resource ID is associated with the ResourceOwner more than once,
+ * one instance is removed.
  */
-static bool
-ResourceArrayRemove(ResourceArray *resarr, Datum value)
+void
+ResourceOwnerForget(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind)
 {
 	uint32		i,
-				idx,
-				lastidx = resarr->lastidx;
+				idx;
 
-	Assert(value != resarr->invalidval);
+#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 try lastidx first. */
-	if (RESARRAY_IS_ARRAY(resarr))
+	/* Search through all items, but check the array first. */
+	for (i = 0; i < owner->narr; i++)
 	{
-		if (lastidx < resarr->nitems &&
-			resarr->itemsarr[lastidx] == value)
+		if (owner->arr[i].item == value &&
+			owner->arr[i].kind == kind)
 		{
-			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++)
-		{
-			if (resarr->itemsarr[i] == value)
-			{
-				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;
-			}
+			owner->arr[i] = owner->arr[owner->narr - 1];
+			owner->narr--;
+
+#ifdef RESOWNER_STATS
+			narray_lookups++;
+#endif
+
+			return;
 		}
 	}
-	else
+
+	/* Search hash */
+	if (owner->nhash > 0)
 	{
-		uint32		mask = resarr->capacity - 1;
+		uint32		mask = owner->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++)
+		for (i = 0; i < owner->capacity; i++)
 		{
-			if (resarr->itemsarr[idx] == value)
+			if (owner->hash[idx].item == value &&
+				owner->hash[idx].kind == kind)
 			{
-				resarr->itemsarr[idx] = resarr->invalidval;
-				resarr->nitems--;
-				return true;
+				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 false;
+	/*
+	 * 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);
 }
 
 /*
- * 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.
- *
- * Returns true if we found an element, or false if the array is empty.
+ * Call the ReleaseResource callback on entries with given 'phase'.
  */
-static bool
-ResourceArrayGetAny(ResourceArray *resarr, Datum *value)
+static void
+ResourceOwnerReleaseAll(ResourceOwner owner, ResourceReleasePhase phase,
+						bool printLeakWarnings)
 {
-	if (resarr->nitems == 0)
-		return false;
+	bool		found;
+	int			capacity;
 
-	if (RESARRAY_IS_ARRAY(resarr))
-	{
-		/* Linear array: just return the first element. */
-		resarr->lastidx = 0;
-	}
-	else
+	/* First handle all the entries in the array. */
+	do
 	{
-		/* Hash: search forward from wherever we were last. */
-		uint32		mask = resarr->capacity - 1;
-
-		for (;;)
+		found = false;
+		for (int i = 0; i < owner->narr; i++)
 		{
-			resarr->lastidx &= mask;
-			if (resarr->itemsarr[resarr->lastidx] != resarr->invalidval)
-				break;
-			resarr->lastidx++;
+			if (owner->arr[i].kind->phase == phase)
+			{
+				Datum		value = owner->arr[i].item;
+				ResourceOwnerFuncs *kind = owner->arr[i].kind;
+
+				if (printLeakWarnings)
+					kind->PrintLeakWarning(value);
+				kind->ReleaseResource(value);
+				found = true;
+			}
 		}
-	}
 
-	*value = resarr->itemsarr[resarr->lastidx];
-	return true;
-}
+		/*
+		 * 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.
+		 */
+	} while (found && owner->narr > 0);
 
-/*
- * Trash a ResourceArray (we don't care about its state after this)
- */
-static void
-ResourceArrayFree(ResourceArray *resarr)
-{
-	if (resarr->itemsarr)
-		pfree(resarr->itemsarr);
+	/* Ok, the array has now been handled. Then the hash */
+	do
+	{
+		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 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.
+				 */
+			}
+		}
+
+		/*
+		 * 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);
 }
 
 
@@ -437,17 +441,10 @@ ResourceOwnerCreate(ResourceOwner parent, const char *name)
 		parent->firstchild = owner;
 	}
 
-	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));
+#ifdef RESOWNER_TRACE
+	elog(LOG, "CREATE %d: %p %s",
+		 resowner_trace_counter++, owner, name);
+#endif
 
 	return owner;
 }
@@ -486,6 +483,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
@@ -497,7 +503,6 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 	ResourceOwner child;
 	ResourceOwner save;
 	ResourceReleaseCallbackItem *item;
-	Datum		foundres;
 
 	/* Recurse to handle descendants */
 	for (child = owner->firstchild; child != NULL; child = child->nextchild)
@@ -513,61 +518,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 references that need to be released before the locks.
 		 *
-		 * During a commit, there shouldn't be any remaining pins --- that
+		 * During a commit, there shouldn't be any remaining references --- 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 *) PointerGetDatum(foundres);
-
-			jit_release_context(context);
-		}
-
-		/* Ditto for cryptohash contexts */
-		while (ResourceArrayGetAny(&(owner->cryptohasharr), &foundres))
-		{
-			pg_cryptohash_ctx *context =
-			(pg_cryptohash_ctx *) PointerGetDatum(foundres);
-
-			if (isCommit)
-				PrintCryptoHashLeakWarning(foundres);
-			pg_cryptohash_free(context);
-		}
+		ResourceOwnerReleaseAll(owner, phase, isCommit);
 	}
 	else if (phase == RESOURCE_RELEASE_LOCKS)
 	{
@@ -576,7 +533,7 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 			/*
 			 * For a top-level xact we are going to release all locks (or at
 			 * least all non-session locks), so just do a single lmgr call at
-			 * the top of the recursion.
+			 * the top of the recursion
 			 */
 			if (owner == TopTransactionResourceOwner)
 			{
@@ -620,70 +577,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 references 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, true);
-		}
-
-		/* 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 */
@@ -704,16 +600,42 @@ void
 ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner)
 {
 	ResourceOwner save;
-	Datum		foundres;
 
 	save = CurrentResourceOwner;
 	CurrentResourceOwner = owner;
-	while (ResourceArrayGetAny(&(owner->planrefarr), &foundres))
+
+	/* array first */
+	for (int i = 0; i < owner->narr; i++)
 	{
-		CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres);
+		if (owner->arr[i].kind == &planref_resowner_funcs)
+		{
+			CachedPlan *planref = (CachedPlan *) DatumGetPointer(owner->arr[i].item);
 
-		ReleaseCachedPlan(res, true);
+			owner->arr[i] = owner->arr[owner->narr - 1];
+			owner->narr--;
+			i--;
+
+			/* pass 'false' because we already removed the entry from the resowner */
+			ReleaseCachedPlan(planref, false);
+		}
 	}
+
+	/* Then hash */
+	for (int i = 0; i < owner->capacity; i++)
+	{
+		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--;
+
+			/* pass 'false' because we already removed the entry from the resowner */
+			ReleaseCachedPlan(planref, false);
+		}
+	}
+
 	CurrentResourceOwner = save;
 }
 
@@ -730,19 +652,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->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.
@@ -758,18 +676,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));
-
+	if (owner->hash)
+		pfree(owner->hash);
 	pfree(owner);
 }
 
@@ -922,44 +830,6 @@ ReleaseAuxProcessResourcesCallback(int code, Datum arg)
 	ReleaseAuxProcessResources(isCommit);
 }
 
-
-/*
- * Make sure there is room for at least one more entry in a ResourceOwner's
- * buffer 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
-ResourceOwnerEnlargeBuffers(ResourceOwner owner)
-{
-	/* 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
  *
@@ -1011,424 +881,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));
-}
diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c
index 8c41483e87c..b3ea3ce7650 100644
--- a/src/backend/utils/time/snapmgr.c
+++ b/src/backend/utils/time/snapmgr.c
@@ -66,7 +66,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"
@@ -174,6 +174,18 @@ 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
+};
+
 /*
  * Snapshot fields to be serialized.
  *
@@ -831,9 +843,10 @@ 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);
+	ResourceOwnerRemember(owner, PointerGetDatum(snap),
+						  &snapshot_resowner_funcs);
 
 	if (snap->regd_count == 1)
 		pairingheap_add(&RegisteredSnapshots, &snap->ph_node);
@@ -870,7 +883,8 @@ UnregisterSnapshotFromOwner(Snapshot snapshot, ResourceOwner owner)
 	Assert(snapshot->regd_count > 0);
 	Assert(!pairingheap_is_empty(&RegisteredSnapshots));
 
-	ResourceOwnerForgetSnapshot(owner, snapshot);
+	ResourceOwnerForget(owner, PointerGetDatum(snapshot),
+						&snapshot_resowner_funcs);
 
 	snapshot->regd_count--;
 	if (snapshot->regd_count == 0)
@@ -2345,3 +2359,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 118651c4153..d3c610a86b0 100644
--- a/src/common/cryptohash_openssl.c
+++ b/src/common/cryptohash_openssl.c
@@ -27,7 +27,6 @@
 #ifndef FRONTEND
 #include "utils/memutils.h"
 #include "utils/resowner.h"
-#include "utils/resowner_private.h"
 #endif
 
 /*
@@ -57,6 +56,21 @@ typedef struct pg_cryptohash_state
 #endif
 } pg_cryptohash_state;
 
+/* ResourceOwner callbacks to hold JitContexts  */
+#ifndef FRONTEND
+static void ResOwnerReleaseCryptoHash(Datum res);
+static void ResOwnerPrintCryptoHashLeakWarning(Datum res);
+
+static ResourceOwnerFuncs cryptohash_funcs =
+{
+	/* relcache references */
+	.name = "LLVM JIT context",
+	.phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.ReleaseResource = ResOwnerReleaseCryptoHash,
+	.PrintLeakWarning = ResOwnerPrintCryptoHashLeakWarning,
+};
+#endif
+
 /*
  * pg_cryptohash_create
  *
@@ -85,7 +99,7 @@ pg_cryptohash_create(pg_cryptohash_type type)
 	ctx->type = type;
 
 #ifndef FRONTEND
-	ResourceOwnerEnlargeCryptoHash(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 #endif
 
 	/*
@@ -110,8 +124,8 @@ pg_cryptohash_create(pg_cryptohash_type type)
 
 #ifndef FRONTEND
 	state->resowner = CurrentResourceOwner;
-	ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
-									PointerGetDatum(ctx));
+	ResourceOwnerRemember(CurrentResourceOwner, PointerGetDatum(ctx),
+						  &cryptohash_funcs);
 #endif
 
 	return ctx;
@@ -221,8 +235,8 @@ pg_cryptohash_free(pg_cryptohash_ctx *ctx)
 	EVP_MD_CTX_destroy(state->evpctx);
 
 #ifndef FRONTEND
-	ResourceOwnerForgetCryptoHash(state->resowner,
-								  PointerGetDatum(ctx));
+	ResourceOwnerForget(state->resowner, PointerGetDatum(ctx),
+						&cryptohash_funcs);
 #endif
 
 	explicit_bzero(state, sizeof(pg_cryptohash_state));
@@ -230,3 +244,22 @@ pg_cryptohash_free(pg_cryptohash_ctx *ctx)
 	FREE(state);
 	FREE(ctx);
 }
+
+
+/*
+ * 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/include/storage/buf_internals.h b/src/include/storage/buf_internals.h
index 3377fa56768..c4aa7d7e31e 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -296,6 +296,21 @@ typedef struct CkptSortItem
 
 extern CkptSortItem *CkptBufferIds;
 
+/* ResourceOwner callbacks to hold buffer pins */
+extern ResourceOwnerFuncs buffer_resowner_funcs;
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer)
+{
+	ResourceOwnerRemember(owner, Int32GetDatum(buffer), &buffer_resowner_funcs);
+}
+static inline void
+ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer 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 f4aa316604e..dbb10dfdd16 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 4901568553c..2e0d5006719 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 878f39ccf14..7fa1c1e50ac 100644
--- a/src/include/utils/resowner.h
+++ b/src/include/utils/resowner.h
@@ -50,6 +50,32 @@ typedef enum
 	RESOURCE_RELEASE_AFTER_LOCKS
 } ResourceReleasePhase;
 
+/*
+ * In order to track an object, resowner.c needs a few callbacks for it.
+ * The callbacks for an object of a specific kind are encapsulated in
+ * ResourceOwnerFuncs.
+ */
+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 +97,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 c373788bc13..00000000000
--- a/src/include/utils/resowner_private.h
+++ /dev/null
@@ -1,105 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * resowner_private.h
- *	  POSTGRES resource owner private definitions.
- *
- * See utils/resowner/README for more info.
- *
- *
- * Portions Copyright (c) 1996-2020, 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);
-
-#endif							/* RESOWNER_PRIVATE_H */
-- 
2.20.1

#10kuroda.hayato@fujitsu.com
kuroda.hayato@fujitsu.com
In reply to: Heikki Linnakangas (#9)
RE: ResourceOwner refactoring

Dear Heikki,

I'm also interested in this patch, but it cannot be applied to the current HEAD...

$ git apply ~/v2-0001-Make-resowners-more-easily-extensible.patch
error: patch failed: src/common/cryptohash_openssl.c:57
error: src/common/cryptohash_openssl.c: patch does not apply
error: patch failed: src/include/utils/resowner_private.h:1
error: src/include/utils/resowner_private.h: patch does not apply

Best Regards,
Hayato Kuroda
FUJITSU LIMITED

#11Heikki Linnakangas
hlinnaka@iki.fi
In reply to: kuroda.hayato@fujitsu.com (#10)
1 attachment(s)
Re: ResourceOwner refactoring

On 13/01/2021 03:55, kuroda.hayato@fujitsu.com wrote:

Dear Heikki,

I'm also interested in this patch, but it cannot be applied to the current HEAD...

$ git apply ~/v2-0001-Make-resowners-more-easily-extensible.patch
error: patch failed: src/common/cryptohash_openssl.c:57
error: src/common/cryptohash_openssl.c: patch does not apply
error: patch failed: src/include/utils/resowner_private.h:1
error: src/include/utils/resowner_private.h: patch does not apply

Here's a rebased version. Thanks!

- Heikki

Attachments:

v3-0001-Make-resowners-more-easily-extensible.patchtext/x-patch; charset=UTF-8; name=v3-0001-Make-resowners-more-easily-extensible.patchDownload
From 31b1b4661823cf38b2d4c5931f96c477b6441271 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 16 Dec 2020 17:26:03 +0200
Subject: [PATCH v3 1/1] Make resowners more easily extensible.

Use a single array and hash, instead of one for each object kind.
---
 src/backend/access/common/tupdesc.c   |   42 +-
 src/backend/jit/jit.c                 |    2 -
 src/backend/jit/llvm/llvmjit.c        |   40 +-
 src/backend/storage/buffer/bufmgr.c   |   43 +-
 src/backend/storage/buffer/localbuf.c |    2 +-
 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    |   98 ++-
 src/backend/utils/cache/plancache.c   |   50 +-
 src/backend/utils/cache/relcache.c    |   39 +-
 src/backend/utils/cache/syscache.c    |    2 +-
 src/backend/utils/resowner/README     |   19 +-
 src/backend/utils/resowner/resowner.c | 1139 +++++++------------------
 src/backend/utils/time/snapmgr.c      |   38 +-
 src/common/cryptohash_openssl.c       |   45 +-
 src/include/storage/buf_internals.h   |   15 +
 src/include/utils/catcache.h          |    3 -
 src/include/utils/plancache.h         |    2 +
 src/include/utils/resowner.h          |   41 +-
 src/include/utils/resowner_private.h  |  105 ---
 21 files changed, 795 insertions(+), 1020 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 902f59440cd..7fa44d5f7ee 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -29,9 +29,21 @@
 #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
+};
 
 /*
  * CreateTemplateTupleDesc
@@ -376,9 +388,10 @@ IncrTupleDescRefCount(TupleDesc tupdesc)
 {
 	Assert(tupdesc->tdrefcount >= 0);
 
-	ResourceOwnerEnlargeTupleDescs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	tupdesc->tdrefcount++;
-	ResourceOwnerRememberTupleDesc(CurrentResourceOwner, tupdesc);
+	ResourceOwnerRemember(CurrentResourceOwner, PointerGetDatum(tupdesc),
+						  &tupdesc_resowner_funcs);
 }
 
 /*
@@ -394,7 +407,8 @@ DecrTupleDescRefCount(TupleDesc tupdesc)
 {
 	Assert(tupdesc->tdrefcount > 0);
 
-	ResourceOwnerForgetTupleDesc(CurrentResourceOwner, tupdesc);
+	ResourceOwnerForget(CurrentResourceOwner, PointerGetDatum(tupdesc),
+						&tupdesc_resowner_funcs);
 	if (--tupdesc->tdrefcount == 0)
 		FreeTupleDesc(tupdesc);
 }
@@ -925,3 +939,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 2da300e000d..1aa04d173b4 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 b0789a5fb80..64c7fd92bc7 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,8 +121,20 @@ static LLVMOrcLLJITRef llvm_create_jit_instance(LLVMTargetMachineRef tm);
 static char *llvm_error_message(LLVMErrorRef error);
 #endif							/* LLVM_VERSION_MAJOR > 11 */
 
-PG_MODULE_MAGIC;
+/* ResourceOwner callbacks to hold JitContexts  */
+static void ResOwnerReleaseJitContext(Datum res);
+static void ResOwnerPrintJitContextLeakWarning(Datum res);
+
+static ResourceOwnerFuncs jit_funcs =
+{
+	/* relcache references */
+	.name = "LLVM JIT context",
+	.phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.ReleaseResource = ResOwnerReleaseJitContext,
+	.PrintLeakWarning = ResOwnerPrintJitContextLeakWarning
+};
 
+PG_MODULE_MAGIC;
 
 /*
  * Initialize LLVM JIT provider.
@@ -151,7 +163,7 @@ llvm_create_context(int jitFlags)
 
 	llvm_session_initialize();
 
-	ResourceOwnerEnlargeJIT(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	context = MemoryContextAllocZero(TopMemoryContext,
 									 sizeof(LLVMJitContext));
@@ -159,7 +171,7 @@ llvm_create_context(int jitFlags)
 
 	/* ensure cleanup */
 	context->base.resowner = CurrentResourceOwner;
-	ResourceOwnerRememberJIT(CurrentResourceOwner, PointerGetDatum(context));
+	ResourceOwnerRemember(CurrentResourceOwner, PointerGetDatum(context), &jit_funcs);
 
 	return context;
 }
@@ -221,6 +233,8 @@ llvm_release_context(JitContext *context)
 
 		pfree(jit_handle);
 	}
+
+	ResourceOwnerForget(context->resowner, PointerGetDatum(context), &jit_funcs);
 }
 
 /*
@@ -1231,3 +1245,21 @@ llvm_error_message(LLVMErrorRef error)
 }
 
 #endif							/* LLVM_VERSION_MAJOR > 11 */
+
+/*
+ * ResourceOwner callbacks
+ */
+static void
+ResOwnerReleaseJitContext(Datum res)
+{
+	jit_release_context((JitContext *) PointerGetDatum(res));
+}
+
+static void
+ResOwnerPrintJitContextLeakWarning(Datum res)
+{
+	/* XXX: We used to not print these. Was that intentional? */
+	JitContext *context = (JitContext *) PointerGetDatum(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 c46b8abad12..8b77fb68941 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -52,7 +52,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"
 
 
@@ -206,6 +206,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
@@ -739,7 +751,7 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 	*hit = false;
 
 	/* Make sure we will have room to remember the buffer pin */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	isExtend = (blockNum == P_NEW);
 
@@ -1858,7 +1870,7 @@ BufferSync(int flags)
 	WritebackContext wb_context;
 
 	/* Make sure we can handle the pin inside SyncOneBuffer */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	/*
 	 * Unless this is a shutdown checkpoint or we have been explicitly told,
@@ -2335,7 +2347,7 @@ BgBufferSync(WritebackContext *wb_context)
 	 */
 
 	/* Make sure we can handle the pin inside SyncOneBuffer */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	num_to_scan = bufs_to_lap;
 	num_written = 0;
@@ -3488,7 +3500,7 @@ FlushRelationBuffers(Relation rel)
 	}
 
 	/* Make sure we can handle the pin inside the loop */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	for (i = 0; i < NBuffers; i++)
 	{
@@ -3561,7 +3573,7 @@ FlushRelationsAllBuffers(SMgrRelation *smgrs, int nrels)
 		pg_qsort(srels, nrels, sizeof(SMgrSortArray), rnode_comparator);
 
 	/* Make sure we can handle the pin inside the loop */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	for (i = 0; i < NBuffers; i++)
 	{
@@ -3640,7 +3652,7 @@ FlushDatabaseBuffers(Oid dbid)
 	BufferDesc *bufHdr;
 
 	/* Make sure we can handle the pin inside the loop */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	for (i = 0; i < NBuffers; i++)
 	{
@@ -3738,7 +3750,7 @@ void
 IncrBufferRefCount(Buffer buffer)
 {
 	Assert(BufferIsPinned(buffer));
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	if (BufferIsLocal(buffer))
 		LocalRefCount[-buffer - 1]++;
 	else
@@ -4802,3 +4814,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 04b3558ea33..811e0e3a45e 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -22,7 +22,7 @@
 #include "storage/bufmgr.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 
 
 /*#define LBDEBUG*/
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 931ed679307..5d2394e6780 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -96,7 +96,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)
@@ -339,6 +339,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
  */
@@ -1429,7 +1447,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
@@ -1611,7 +1629,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
@@ -1741,7 +1759,7 @@ PathNameCreateTemporaryFile(const char *path, bool error_on_failure)
 {
 	File		file;
 
-	ResourceOwnerEnlargeFiles(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	/*
 	 * Open the file.  Note: we don't use O_EXCL, in case there is an orphaned
@@ -1779,7 +1797,7 @@ PathNameOpenTemporaryFile(const char *path, int mode)
 {
 	File		file;
 
-	ResourceOwnerEnlargeFiles(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	file = PathNameOpenFile(path, mode | PG_BINARY);
 
@@ -3635,3 +3653,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 ae82b4bdc0e..31e8860fb53 100644
--- a/src/backend/storage/ipc/dsm.c
+++ b/src/backend/storage/ipc/dsm.c
@@ -37,13 +37,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
 
@@ -139,6 +141,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.
  *
@@ -895,7 +916,7 @@ void
 dsm_unpin_mapping(dsm_segment *seg)
 {
 	Assert(seg->resowner == NULL);
-	ResourceOwnerEnlargeDSMs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	seg->resowner = CurrentResourceOwner;
 	ResourceOwnerRememberDSM(seg->resowner, seg);
 }
@@ -1162,7 +1183,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);
@@ -1241,3 +1262,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 20e50247ea4..de39c844d49 100644
--- a/src/backend/storage/lmgr/lock.c
+++ b/src/backend/storage/lmgr/lock.c
@@ -47,7 +47,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 fa2b49c676e..24751c0d3f4 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,66 @@ 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_funcs =
+{
+	/* catcache references */
+	.name = "catcache reference",
+	.phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.ReleaseResource = ResOwnerReleaseCatCache,
+	.PrintLeakWarning = ResOwnerPrintCatCacheLeakWarning
+};
+
+static ResourceOwnerFuncs catlistref_funcs =
+{
+	/* catcache-list pins */
+	.name = "catcache list reference",
+	.phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.ReleaseResource = ResOwnerReleaseCatCacheList,
+	.PrintLeakWarning = ResOwnerPrintCatCacheListLeakWarning
+};
+
+/* support for catcache refcount management */
+static inline void
+ResourceOwnerEnlargeCatCacheRefs(ResourceOwner owner)
+{
+	ResourceOwnerEnlarge(owner);
+}
+static inline void
+ResourceOwnerRememberCatCacheRef(ResourceOwner owner, HeapTuple tuple)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(tuple), &catcache_funcs);
+}
+static inline void
+ResourceOwnerForgetCatCacheRef(ResourceOwner owner, HeapTuple tuple)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(tuple), &catcache_funcs);
+}
+
+static inline void
+ResourceOwnerEnlargeCatCacheListRefs(ResourceOwner owner)
+{
+	ResourceOwnerEnlarge(owner);
+}
+static inline void
+ResourceOwnerRememberCatCacheListRef(ResourceOwner owner, CatCList *list)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(list), &catlistref_funcs);
+}
+
+static inline void
+ResourceOwnerForgetCatCacheListRef(ResourceOwner owner, CatCList *list)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(list), &catlistref_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
@@ -1270,7 +1331,7 @@ SearchCatCacheInternal(CatCache *cache,
 		 */
 		if (!ct->negative)
 		{
-			ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner);
+			ResourceOwnerEnlarge(CurrentResourceOwner);
 			ct->refcount++;
 			ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple);
 
@@ -1371,7 +1432,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 */
@@ -1583,7 +1644,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);
 
@@ -1607,7 +1668,7 @@ SearchCatCacheList(CatCache *cache,
 	 * block to ensure we can undo those refcounts if we get an error before
 	 * we finish constructing the CatCList.
 	 */
-	ResourceOwnerEnlargeCatCacheListRefs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	ctlist = NIL;
 
@@ -2062,14 +2123,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 +2149,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 cc04b5b4bef..484f14f5524 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,31 @@ 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
+};
+
+static inline void
+ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(plan), &planref_resowner_funcs);
+}
+static inline void
+ResourceOwnerForgetPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(plan), &planref_resowner_funcs);
+}
+
+
 /* GUC parameter */
 int			plan_cache_mode;
 
@@ -1229,7 +1254,7 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 
 	/* Flag the plan as in use by caller */
 	if (useResOwner)
-		ResourceOwnerEnlargePlanCacheRefs(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 	plan->refcount++;
 	if (useResOwner)
 		ResourceOwnerRememberPlanCacheRef(CurrentResourceOwner, plan);
@@ -1392,7 +1417,7 @@ CachedPlanAllowsSimpleValidityCheck(CachedPlanSource *plansource,
 	/* Bump refcount if requested. */
 	if (owner)
 	{
-		ResourceOwnerEnlargePlanCacheRefs(owner);
+		ResourceOwnerEnlarge(owner);
 		plan->refcount++;
 		ResourceOwnerRememberPlanCacheRef(owner, plan);
 	}
@@ -1451,7 +1476,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);
 	}
@@ -2205,3 +2230,20 @@ ResetPlanCache(void)
 		cexpr->is_valid = false;
 	}
 }
+
+/*
+ * ResourceOwner callbacks
+ */
+
+static void
+ResOwnerReleaseCachedPlan(Datum res)
+{
+	ReleaseCachedPlan((CachedPlan *) DatumGetPointer(res), true);
+}
+
+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 7ef510cd01b..255d19a88b9 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -78,13 +78,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"
 
@@ -2078,6 +2079,18 @@ 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
+};
+
 /*
  * RelationIncrementReferenceCount
  *		Increments relation reference count.
@@ -2089,10 +2102,10 @@ RelationIdGetRelation(Oid relationId)
 void
 RelationIncrementReferenceCount(Relation rel)
 {
-	ResourceOwnerEnlargeRelationRefs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	rel->rd_refcnt += 1;
 	if (!IsBootstrapProcessingMode())
-		ResourceOwnerRememberRelationRef(CurrentResourceOwner, rel);
+		ResourceOwnerRemember(CurrentResourceOwner, PointerGetDatum(rel), &relref_resowner_funcs);
 }
 
 /*
@@ -2105,7 +2118,7 @@ RelationDecrementReferenceCount(Relation rel)
 	Assert(rel->rd_refcnt > 0);
 	rel->rd_refcnt -= 1;
 	if (!IsBootstrapProcessingMode())
-		ResourceOwnerForgetRelationRef(CurrentResourceOwner, rel);
+		ResourceOwnerForget(CurrentResourceOwner, PointerGetDatum(rel), &relref_resowner_funcs);
 }
 
 /*
@@ -6417,3 +6430,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/cache/syscache.c b/src/backend/utils/cache/syscache.c
index e4dc4ee34ee..d6fe6a5bd5a 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -994,7 +994,7 @@ static const struct cachedesc cacheinfo[] = {
 	}
 };
 
-static CatCache *SysCache[SysCacheSize];
+ CatCache *SysCache[SysCacheSize];
 
 static bool CacheInitialized = false;
 
diff --git a/src/backend/utils/resowner/README b/src/backend/utils/resowner/README
index 2998f6bb362..890db7d1e66 100644
--- a/src/backend/utils/resowner/README
+++ b/src/backend/utils/resowner/README
@@ -60,13 +60,18 @@ subtransaction or portal.  Therefore, the "release" operation on a child
 ResourceOwner transfers lock ownership to the parent instead of actually
 releasing the lock, if isCommit is true.
 
-Currently, ResourceOwners contain direct support for recording ownership of
-buffer pins, lmgr locks, and catcache, relcache, plancache, tupdesc, and
-snapshot references.  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 objects.
+As of this writing, it's used internally for buffer pins, lmgr locks, and
+catcache, relcache, plancache, tupdesc, snapshot, DSM and JIT context references.
+ResourceOwner treates all objects the same, but to register a new kind of
+an object with it, you need to fill in a few callback functions, see
+ResourceOwnerFuncs.
+
+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. This used to be the only way to register new kinds
+of objects with a resource owner; nowadays it easier to write custom
+ResourceOwnerFuncs callabacks.
 
 Whenever we are inside a transaction, the global variable
 CurrentResourceOwner shows which resource owner should be assigned
diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index 10f15f6a357..c49758cb20d 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -22,73 +22,44 @@
 
 #include "common/cryptohash.h"
 #include "common/hashfn.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"
-
+#include "utils/plancache.h"
+#include "utils/resowner.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))
-
-/*
- * 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.
+ * ResourceElem represents a reference associated with a resource owner.
  *
- * 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.
- *
- * 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;
+} 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 8
 
 /*
- * 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 32
 
 /*
- * 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
@@ -118,23 +89,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 */
+	/*
+	 * 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;
 
 
 /*****************************************************************************
@@ -146,6 +127,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
  */
@@ -160,45 +153,34 @@ 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 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);
 
 
 /*****************************************************************************
  *	  INTERNAL ROUTINES														 *
  *****************************************************************************/
 
-
-/*
- * Initialize a ResourceArray
- */
 static void
-ResourceArrayInit(ResourceArray *resarr, Datum invalidval)
+ResourceArrayAddToHash(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;
+
+	idx = DatumGetUInt32(hash_any((void *) &value, sizeof(value))) & mask;
+	for (;;)
+	{
+		if (owner->hash[idx].kind == NULL)
+			break;
+		idx = (idx + 1) & mask;
+	}
+	owner->hash[idx].item = value;
+	owner->hash[idx].kind = kind;
+	owner->nhash++;
 }
 
 /*
@@ -207,205 +189,227 @@ ResourceArrayInit(ResourceArray *resarr, Datum invalidval)
  * 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.
  */
-static void
-ResourceArrayEnlarge(ResourceArray *resarr)
+void
+ResourceOwnerEnlarge(ResourceOwner owner)
 {
-	uint32		i,
-				oldcap,
-				newcap;
-	Datum	   *olditemsarr;
-	Datum	   *newitemsarr;
-
-	if (resarr->nitems < resarr->maxitems)
+	if (owner->narr < RESOWNER_ARRAY_SIZE)
 		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;
-
-	/* 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 (olditemsarr != NULL)
+	/* Is there space in the hash? If not, enlarge it. */
+	if (owner->narr + owner->nhash >= owner->grow_at)
 	{
-		/*
-		 * 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.
-		 */
-		for (i = 0; i < oldcap; 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 FIXME */
+		owner->hash = newhash;
+		owner->capacity = newcap;
+		owner->grow_at = RESOWNER_HASH_MAX_ITEMS(newcap);
+		owner->nhash = 0;
+
+		if (oldhash != NULL)
 		{
-			if (olditemsarr[i] != resarr->invalidval)
-				ResourceArrayAdd(resarr, olditemsarr[i]);
+			/*
+			 * 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++)
+			{
+				if (oldhash[i].kind != NULL)
+					ResourceArrayAddToHash(owner, oldhash[i].item, oldhash[i].kind);
+			}
+
+			/* And release old hash table. */
+			pfree(oldhash);
 		}
+	}
 
-		/* And release old array. */
-		pfree(olditemsarr);
+	/* Move items from the array to the hash */
+	for (int i = 0; i < owner->narr; i++)
+	{
+		ResourceArrayAddToHash(owner, owner->arr[i].item, owner->arr[i].kind);
 	}
+	owner->narr = 0;
 
-	Assert(resarr->nitems < resarr->maxitems);
+	Assert(owner->nhash < owner->grow_at);
 }
 
 /*
- * Add a resource to ResourceArray
+ * Remember that an object is owner by a ReourceOwner
  *
- * Caller must have previously done ResourceArrayEnlarge()
+ * Caller must have previously done ResourceOwnerEnlarge()
  */
-static void
-ResourceArrayAdd(ResourceArray *resarr, Datum value)
+void
+ResourceOwnerRemember(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind)
 {
 	uint32		idx;
 
-	Assert(value != resarr->invalidval);
-	Assert(resarr->nitems < resarr->maxitems);
+#ifdef RESOWNER_TRACE
+	elog(LOG, "REMEMBER %d: owner %p value " UINT64_FORMAT ", kind: %s",
+		 resowner_trace_counter++, owner, DatumGetUInt64(value), kind->name);
+#endif
 
-	if (RESARRAY_IS_ARRAY(resarr))
-	{
-		/* Append to linear array. */
-		idx = resarr->nitems;
-	}
-	else
-	{
-		/* Insert into first free slot at or after hash location. */
-		uint32		mask = resarr->capacity - 1;
+	Assert(owner->narr < RESOWNER_ARRAY_SIZE);
 
-		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++;
+	/* Append to linear array. */
+	idx = owner->narr;
+	owner->arr[idx].item = value;
+	owner->arr[idx].kind = kind;
+	owner->narr++;
 }
 
 /*
- * Remove a resource from ResourceArray
+ * Forget that an object is owned by a ResourceOwner
  *
- * Returns true on success, false if resource was not found.
+ * Returns true on success. If the resource was not found, returns false,
+ * and calls kind->ForgetError callback.
  *
- * Note: if same resource ID appears more than once, one instance is removed.
+ * Note: if same resource ID is associated with the ResourceOwner more than once,
+ * one instance is removed.
  */
-static bool
-ResourceArrayRemove(ResourceArray *resarr, Datum value)
+void
+ResourceOwnerForget(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind)
 {
 	uint32		i,
-				idx,
-				lastidx = resarr->lastidx;
+				idx;
 
-	Assert(value != resarr->invalidval);
+#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 try lastidx first. */
-	if (RESARRAY_IS_ARRAY(resarr))
+	/* Search through all items, but check the array first. */
+	for (i = 0; i < owner->narr; i++)
 	{
-		if (lastidx < resarr->nitems &&
-			resarr->itemsarr[lastidx] == value)
+		if (owner->arr[i].item == value &&
+			owner->arr[i].kind == kind)
 		{
-			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++)
-		{
-			if (resarr->itemsarr[i] == value)
-			{
-				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;
-			}
+			owner->arr[i] = owner->arr[owner->narr - 1];
+			owner->narr--;
+
+#ifdef RESOWNER_STATS
+			narray_lookups++;
+#endif
+
+			return;
 		}
 	}
-	else
+
+	/* Search hash */
+	if (owner->nhash > 0)
 	{
-		uint32		mask = resarr->capacity - 1;
+		uint32		mask = owner->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++)
+		for (i = 0; i < owner->capacity; i++)
 		{
-			if (resarr->itemsarr[idx] == value)
+			if (owner->hash[idx].item == value &&
+				owner->hash[idx].kind == kind)
 			{
-				resarr->itemsarr[idx] = resarr->invalidval;
-				resarr->nitems--;
-				return true;
+				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 false;
+	/*
+	 * 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);
 }
 
 /*
- * 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.
- *
- * Returns true if we found an element, or false if the array is empty.
+ * Call the ReleaseResource callback on entries with given 'phase'.
  */
-static bool
-ResourceArrayGetAny(ResourceArray *resarr, Datum *value)
+static void
+ResourceOwnerReleaseAll(ResourceOwner owner, ResourceReleasePhase phase,
+						bool printLeakWarnings)
 {
-	if (resarr->nitems == 0)
-		return false;
+	bool		found;
+	int			capacity;
 
-	if (RESARRAY_IS_ARRAY(resarr))
-	{
-		/* Linear array: just return the first element. */
-		resarr->lastidx = 0;
-	}
-	else
+	/* First handle all the entries in the array. */
+	do
 	{
-		/* Hash: search forward from wherever we were last. */
-		uint32		mask = resarr->capacity - 1;
-
-		for (;;)
+		found = false;
+		for (int i = 0; i < owner->narr; i++)
 		{
-			resarr->lastidx &= mask;
-			if (resarr->itemsarr[resarr->lastidx] != resarr->invalidval)
-				break;
-			resarr->lastidx++;
+			if (owner->arr[i].kind->phase == phase)
+			{
+				Datum		value = owner->arr[i].item;
+				ResourceOwnerFuncs *kind = owner->arr[i].kind;
+
+				if (printLeakWarnings)
+					kind->PrintLeakWarning(value);
+				kind->ReleaseResource(value);
+				found = true;
+			}
 		}
-	}
 
-	*value = resarr->itemsarr[resarr->lastidx];
-	return true;
-}
+		/*
+		 * 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.
+		 */
+	} while (found && owner->narr > 0);
 
-/*
- * Trash a ResourceArray (we don't care about its state after this)
- */
-static void
-ResourceArrayFree(ResourceArray *resarr)
-{
-	if (resarr->itemsarr)
-		pfree(resarr->itemsarr);
+	/* Ok, the array has now been handled. Then the hash */
+	do
+	{
+		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 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.
+				 */
+			}
+		}
+
+		/*
+		 * 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);
 }
 
 
@@ -437,17 +441,10 @@ ResourceOwnerCreate(ResourceOwner parent, const char *name)
 		parent->firstchild = owner;
 	}
 
-	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));
+#ifdef RESOWNER_TRACE
+	elog(LOG, "CREATE %d: %p %s",
+		 resowner_trace_counter++, owner, name);
+#endif
 
 	return owner;
 }
@@ -486,6 +483,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
@@ -497,7 +503,6 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 	ResourceOwner child;
 	ResourceOwner save;
 	ResourceReleaseCallbackItem *item;
-	Datum		foundres;
 
 	/* Recurse to handle descendants */
 	for (child = owner->firstchild; child != NULL; child = child->nextchild)
@@ -513,61 +518,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 references that need to be released before the locks.
 		 *
-		 * During a commit, there shouldn't be any remaining pins --- that
+		 * During a commit, there shouldn't be any remaining references --- 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 *) PointerGetDatum(foundres);
-
-			jit_release_context(context);
-		}
-
-		/* Ditto for cryptohash contexts */
-		while (ResourceArrayGetAny(&(owner->cryptohasharr), &foundres))
-		{
-			pg_cryptohash_ctx *context =
-			(pg_cryptohash_ctx *) PointerGetDatum(foundres);
-
-			if (isCommit)
-				PrintCryptoHashLeakWarning(foundres);
-			pg_cryptohash_free(context);
-		}
+		ResourceOwnerReleaseAll(owner, phase, isCommit);
 	}
 	else if (phase == RESOURCE_RELEASE_LOCKS)
 	{
@@ -576,7 +533,7 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 			/*
 			 * For a top-level xact we are going to release all locks (or at
 			 * least all non-session locks), so just do a single lmgr call at
-			 * the top of the recursion.
+			 * the top of the recursion
 			 */
 			if (owner == TopTransactionResourceOwner)
 			{
@@ -620,70 +577,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 references 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, true);
-		}
-
-		/* 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 */
@@ -704,16 +600,42 @@ void
 ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner)
 {
 	ResourceOwner save;
-	Datum		foundres;
 
 	save = CurrentResourceOwner;
 	CurrentResourceOwner = owner;
-	while (ResourceArrayGetAny(&(owner->planrefarr), &foundres))
+
+	/* array first */
+	for (int i = 0; i < owner->narr; i++)
 	{
-		CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres);
+		if (owner->arr[i].kind == &planref_resowner_funcs)
+		{
+			CachedPlan *planref = (CachedPlan *) DatumGetPointer(owner->arr[i].item);
 
-		ReleaseCachedPlan(res, true);
+			owner->arr[i] = owner->arr[owner->narr - 1];
+			owner->narr--;
+			i--;
+
+			/* pass 'false' because we already removed the entry from the resowner */
+			ReleaseCachedPlan(planref, false);
+		}
 	}
+
+	/* Then hash */
+	for (int i = 0; i < owner->capacity; i++)
+	{
+		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--;
+
+			/* pass 'false' because we already removed the entry from the resowner */
+			ReleaseCachedPlan(planref, false);
+		}
+	}
+
 	CurrentResourceOwner = save;
 }
 
@@ -730,19 +652,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->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.
@@ -758,18 +676,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));
-
+	if (owner->hash)
+		pfree(owner->hash);
 	pfree(owner);
 }
 
@@ -922,44 +830,6 @@ ReleaseAuxProcessResourcesCallback(int code, Datum arg)
 	ReleaseAuxProcessResources(isCommit);
 }
 
-
-/*
- * Make sure there is room for at least one more entry in a ResourceOwner's
- * buffer 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
-ResourceOwnerEnlargeBuffers(ResourceOwner owner)
-{
-	/* 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
  *
@@ -1011,424 +881,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));
-}
diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c
index ae16c3ed7d6..10cec8d39d0 100644
--- a/src/backend/utils/time/snapmgr.c
+++ b/src/backend/utils/time/snapmgr.c
@@ -66,7 +66,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"
@@ -174,6 +174,18 @@ 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
+};
+
 /*
  * Snapshot fields to be serialized.
  *
@@ -831,9 +843,10 @@ 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);
+	ResourceOwnerRemember(owner, PointerGetDatum(snap),
+						  &snapshot_resowner_funcs);
 
 	if (snap->regd_count == 1)
 		pairingheap_add(&RegisteredSnapshots, &snap->ph_node);
@@ -870,7 +883,8 @@ UnregisterSnapshotFromOwner(Snapshot snapshot, ResourceOwner owner)
 	Assert(snapshot->regd_count > 0);
 	Assert(!pairingheap_is_empty(&RegisteredSnapshots));
 
-	ResourceOwnerForgetSnapshot(owner, snapshot);
+	ResourceOwnerForget(owner, PointerGetDatum(snapshot),
+						&snapshot_resowner_funcs);
 
 	snapshot->regd_count--;
 	if (snapshot->regd_count == 0)
@@ -2345,3 +2359,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 551ec392b60..1ed1580c644 100644
--- a/src/common/cryptohash_openssl.c
+++ b/src/common/cryptohash_openssl.c
@@ -27,7 +27,6 @@
 #ifndef FRONTEND
 #include "utils/memutils.h"
 #include "utils/resowner.h"
-#include "utils/resowner_private.h"
 #endif
 
 /*
@@ -60,6 +59,21 @@ struct pg_cryptohash_ctx
 #endif
 };
 
+/* ResourceOwner callbacks to hold JitContexts  */
+#ifndef FRONTEND
+static void ResOwnerReleaseCryptoHash(Datum res);
+static void ResOwnerPrintCryptoHashLeakWarning(Datum res);
+
+static ResourceOwnerFuncs cryptohash_funcs =
+{
+	/* relcache references */
+	.name = "LLVM JIT context",
+	.phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.ReleaseResource = ResOwnerReleaseCryptoHash,
+	.PrintLeakWarning = ResOwnerPrintCryptoHashLeakWarning,
+};
+#endif
+
 /*
  * pg_cryptohash_create
  *
@@ -77,7 +91,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));
@@ -106,8 +120,8 @@ pg_cryptohash_create(pg_cryptohash_type type)
 
 #ifndef FRONTEND
 	ctx->resowner = CurrentResourceOwner;
-	ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
-									PointerGetDatum(ctx));
+	ResourceOwnerRemember(CurrentResourceOwner, PointerGetDatum(ctx),
+						  &cryptohash_funcs);
 #endif
 
 	return ctx;
@@ -207,10 +221,29 @@ pg_cryptohash_free(pg_cryptohash_ctx *ctx)
 	EVP_MD_CTX_destroy(ctx->evpctx);
 
 #ifndef FRONTEND
-	ResourceOwnerForgetCryptoHash(ctx->resowner,
-								  PointerGetDatum(ctx));
+	ResourceOwnerForget(ctx->resowner, PointerGetDatum(ctx),
+						&cryptohash_funcs);
 #endif
 
 	explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
 	FREE(ctx);
 }
+
+
+/*
+ * 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/include/storage/buf_internals.h b/src/include/storage/buf_internals.h
index f6b57829653..281f85586fa 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -296,6 +296,21 @@ typedef struct CkptSortItem
 
 extern CkptSortItem *CkptBufferIds;
 
+/* ResourceOwner callbacks to hold buffer pins */
+extern ResourceOwnerFuncs buffer_resowner_funcs;
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer)
+{
+	ResourceOwnerRemember(owner, Int32GetDatum(buffer), &buffer_resowner_funcs);
+}
+static inline void
+ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer 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 ddc2762eb3f..2bf95c22e84 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 79d96e5ed03..964af79b30d 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 109ac31b248..c3f1f060fa9 100644
--- a/src/include/utils/resowner.h
+++ b/src/include/utils/resowner.h
@@ -50,6 +50,32 @@ typedef enum
 	RESOURCE_RELEASE_AFTER_LOCKS
 } ResourceReleasePhase;
 
+/*
+ * In order to track an object, resowner.c needs a few callbacks for it.
+ * The callbacks for an object of a specific kind are encapsulated in
+ * ResourceOwnerFuncs.
+ */
+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 +97,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 c480a1a24be..00000000000
--- a/src/include/utils/resowner_private.h
+++ /dev/null
@@ -1,105 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * resowner_private.h
- *	  POSTGRES resource owner private definitions.
- *
- * See utils/resowner/README for more info.
- *
- *
- * Portions Copyright (c) 1996-2021, 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);
-
-#endif							/* RESOWNER_PRIVATE_H */
-- 
2.29.2

#12Michael Paquier
michael@paquier.xyz
In reply to: Heikki Linnakangas (#11)
Re: ResourceOwner refactoring

On Wed, Jan 13, 2021 at 09:18:57AM +0200, Heikki Linnakangas wrote:

--- a/src/common/cryptohash_openssl.c
+++ b/src/common/cryptohash_openssl.c
+static ResourceOwnerFuncs cryptohash_funcs =
+{
+	/* relcache references */
+	.name = "LLVM JIT context",
+	.phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.ReleaseResource = ResOwnerReleaseCryptoHash,
+	.PrintLeakWarning = ResOwnerPrintCryptoHashLeakWarning,
+};
+#endif

Looks like a copy-paste error here.
--
Michael

#13kuroda.hayato@fujitsu.com
kuroda.hayato@fujitsu.com
In reply to: Heikki Linnakangas (#11)
RE: ResourceOwner refactoring

Dear Heikki,

Thank you for rebasing it, I confirmed it can be applied.
I will check the source.

Now I put the very elementary comment.
ResourceOwnerEnlarge(), ResourceOwnerRemember(), and ResourceOwnerForget()
are exported routines.
They should put below L418.

Best regards,
Hayato Kuroda
FUJITSU LIMITED

#14kuroda.hayato@fujitsu.com
kuroda.hayato@fujitsu.com
In reply to: kuroda.hayato@fujitsu.com (#13)
RE: ResourceOwner refactoring

Hi,

I put some comments.

Throughout, some components don’t have helper functions.
(e.g. catcache has ResOwnerReleaseCatCache, but tupdesc doesn't.)
I think it should be unified.

[catcache.c]

+/* support for catcache refcount management */
+static inline void
+ResourceOwnerEnlargeCatCacheRefs(ResourceOwner owner)
+{
+       ResourceOwnerEnlarge(owner);
+}

This function is not needed.

[syscache.c]

-static CatCache *SysCache[SysCacheSize];
+ CatCache *SysCache[SysCacheSize];

Is it right? Compilation is done even if this variable is static...

[fd.c, dsm.c]
In these files helper functions are implemented as the define directive.
Could you explain the reason? For the performance?

Previously, this was OK, because resources AAA and BBB were kept in
separate arrays. But after this patch, it's not guaranteed that the
ResourceOwnerRememberBBB() will find an empty slot.

I don't think we do that currently, but to be sure, I'm planning to grep
ResourceOwnerRemember and look at each call carefullly. And perhaps we
can add an assertion for this, although I'm not sure where.

Indeed, but I think this line works well, isn't it?

Assert(owner->narr < RESOWNER_ARRAY_SIZE);

Best regards,
Hayato Kuroda
FUJITSU LIMITED

#15Heikki Linnakangas
hlinnaka@iki.fi
In reply to: kuroda.hayato@fujitsu.com (#14)
2 attachment(s)
Re: ResourceOwner refactoring

On 14/01/2021 12:15, kuroda.hayato@fujitsu.com wrote:

I put some comments.

Thanks for the review!

Throughout, some components don’t have helper functions.
(e.g. catcache has ResOwnerReleaseCatCache, but tupdesc doesn't.)
I think it should be unified.

Hmm. ResOwnerReleaseTupleDesc() does exist, those functions are needed
for the callbacks. I think you meant the wrappers around
ResourceOwnerRemember and ResourceOwnerForget, like
ResourceOwnerRememberCatCacheRef(). I admit those are not fully
consistent: I didn't bother with the wrapper functions when there is
only one caller.

[catcache.c]

+/* support for catcache refcount management */
+static inline void
+ResourceOwnerEnlargeCatCacheRefs(ResourceOwner owner)
+{
+       ResourceOwnerEnlarge(owner);
+}

This function is not needed.

Removed.

[syscache.c]

-static CatCache *SysCache[SysCacheSize];
+ CatCache *SysCache[SysCacheSize];

Is it right? Compilation is done even if this variable is static...

Fixed. (It was a leftover from when I was playing with Kyotaro's
"catcachebench" tool from another thread).

[fd.c, dsm.c]
In these files helper functions are implemented as the define directive.
Could you explain the reason? For the performance?

No particular reason. I turned them all into macros for consistency.

Previously, this was OK, because resources AAA and BBB were kept in
separate arrays. But after this patch, it's not guaranteed that the
ResourceOwnerRememberBBB() will find an empty slot.

I don't think we do that currently, but to be sure, I'm planning to grep
ResourceOwnerRemember and look at each call carefullly. And perhaps we
can add an assertion for this, although I'm not sure where.

Indeed, but I think this line works well, isn't it?

Assert(owner->narr < RESOWNER_ARRAY_SIZE);

That catches cases where you actually overrun the array, but it doesn't
catch unsafe patterns when there happens to be enough space left in the
array. For example, if you have code like this:

/* Make sure there's room for one more entry, but remember *two* things */
ResourceOwnerEnlarge();
ResourceOwnerRemember(foo);
ResourceOwnerRemember(bar);

That is not safe, but it would only fail the assertion if the first
ResourceOwnerRemember() call happens to consume the last remaining slot
in the array.

I checked all the callers of ResourceOwnerEnlarge() to see if they're
safe. A couple of places seemed a bit suspicious. I fixed them by moving
the ResourceOwnerEnlarge() calls closer to the ResourceOwnerRemember()
calls, so that it's now easier to see that they are correct. See first
attached patch.

The second patch is an updated version of the main patch, fixing all the
little things you and Michael pointed out since the last patch version.

I've been working on performance testing too. I'll post more numbers
later, but preliminary result from some micro-benchmarking suggests that
the new code is somewhat slower, except in the common case that the
object to remember and forget fits in the array. When running the
regression test suite, about 96% of ResourceOwnerForget() calls fit in
the array. I think that's acceptable.

- Heikki

Attachments:

v4-0001-Move-a-few-ResourceOwnerEnlarge-calls-for-safety-.patchtext/x-patch; charset=UTF-8; name=v4-0001-Move-a-few-ResourceOwnerEnlarge-calls-for-safety-.patchDownload
From 915ef70c8e227fe1c9d403bf6414d54b6673ddae Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 13 Jan 2021 12:21:28 +0200
Subject: [PATCH v4 1/2] Move a few ResourceOwnerEnlarge() calls for safety and
 clarity.

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
inbetween, 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.
---
 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 561c212092f..cde869e7d64 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -738,9 +738,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,
@@ -1093,9 +1090,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 resoure owner has room to remember the
+		 * pin.
 		 */
 		ReservePrivateRefCountEntry();
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
 
 		/*
 		 * Select a victim buffer.  The buffer is returned with its header
@@ -1595,8 +1594,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.
  */
@@ -1607,6 +1604,8 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
 	bool		result;
 	PrivateRefCountEntry *ref;
 
+	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+
 	ref = GetPrivateRefCountEntry(b, true);
 
 	if (ref == NULL)
@@ -1687,7 +1686,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.
@@ -1857,9 +1857,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
@@ -2334,9 +2331,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;
@@ -2418,8 +2412,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)
@@ -2429,7 +2421,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.
@@ -3487,9 +3481,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;
@@ -3503,7 +3494,9 @@ FlushRelationBuffers(Relation rel)
 		if (!RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node))
 			continue;
 
+		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
@@ -3560,9 +3553,6 @@ FlushRelationsAllBuffers(SMgrRelation *smgrs, int nrels)
 	if (use_bsearch)
 		pg_qsort(srels, nrels, sizeof(SMgrSortArray), rnode_comparator);
 
-	/* Make sure we can handle the pin inside the loop */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
-
 	for (i = 0; i < NBuffers; i++)
 	{
 		SMgrSortArray *srelent = NULL;
@@ -3599,7 +3589,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 (RelFileNodeEquals(bufHdr->tag.rnode, srelent->rnode) &&
@@ -3639,9 +3631,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;
@@ -3655,7 +3644,9 @@ FlushDatabaseBuffers(Oid dbid)
 		if (bufHdr->tag.rnode.dbNode != dbid)
 			continue;
 
+		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (bufHdr->tag.rnode.dbNode == dbid &&
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index 04b3558ea33..f7c15ea8a44 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -123,6 +123,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, (void *) &newTag, HASH_FIND, NULL);
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index fa2b49c676e..86fdfc454d2 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1607,8 +1607,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();
@@ -1696,13 +1694,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.29.2

v4-0002-Make-resowners-more-easily-extensible.patchtext/x-patch; charset=UTF-8; name=v4-0002-Make-resowners-more-easily-extensible.patchDownload
From 7d5f0a5f8661071a60ef88284c013babc96c91de Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 16 Dec 2020 17:26:03 +0200
Subject: [PATCH v4 2/2] Make resowners more easily extensible.

Use a single array and hash, instead of one for each object kind.
---
 src/backend/access/common/tupdesc.c   |   42 +-
 src/backend/jit/jit.c                 |    2 -
 src/backend/jit/llvm/llvmjit.c        |   40 +-
 src/backend/storage/buffer/bufmgr.c   |   45 +-
 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    |   39 +-
 src/backend/utils/resowner/README     |   19 +-
 src/backend/utils/resowner/resowner.c | 1212 +++++++------------------
 src/backend/utils/time/snapmgr.c      |   38 +-
 src/common/cryptohash_openssl.c       |   45 +-
 src/include/storage/buf_internals.h   |    9 +
 src/include/utils/catcache.h          |    3 -
 src/include/utils/plancache.h         |    2 +
 src/include/utils/resowner.h          |   41 +-
 src/include/utils/resowner_private.h  |  105 ---
 20 files changed, 803 insertions(+), 1052 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 902f59440cd..7fa44d5f7ee 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -29,9 +29,21 @@
 #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
+};
 
 /*
  * CreateTemplateTupleDesc
@@ -376,9 +388,10 @@ IncrTupleDescRefCount(TupleDesc tupdesc)
 {
 	Assert(tupdesc->tdrefcount >= 0);
 
-	ResourceOwnerEnlargeTupleDescs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	tupdesc->tdrefcount++;
-	ResourceOwnerRememberTupleDesc(CurrentResourceOwner, tupdesc);
+	ResourceOwnerRemember(CurrentResourceOwner, PointerGetDatum(tupdesc),
+						  &tupdesc_resowner_funcs);
 }
 
 /*
@@ -394,7 +407,8 @@ DecrTupleDescRefCount(TupleDesc tupdesc)
 {
 	Assert(tupdesc->tdrefcount > 0);
 
-	ResourceOwnerForgetTupleDesc(CurrentResourceOwner, tupdesc);
+	ResourceOwnerForget(CurrentResourceOwner, PointerGetDatum(tupdesc),
+						&tupdesc_resowner_funcs);
 	if (--tupdesc->tdrefcount == 0)
 		FreeTupleDesc(tupdesc);
 }
@@ -925,3 +939,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 2da300e000d..1aa04d173b4 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 b0789a5fb80..64c7fd92bc7 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,8 +121,20 @@ static LLVMOrcLLJITRef llvm_create_jit_instance(LLVMTargetMachineRef tm);
 static char *llvm_error_message(LLVMErrorRef error);
 #endif							/* LLVM_VERSION_MAJOR > 11 */
 
-PG_MODULE_MAGIC;
+/* ResourceOwner callbacks to hold JitContexts  */
+static void ResOwnerReleaseJitContext(Datum res);
+static void ResOwnerPrintJitContextLeakWarning(Datum res);
+
+static ResourceOwnerFuncs jit_funcs =
+{
+	/* relcache references */
+	.name = "LLVM JIT context",
+	.phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.ReleaseResource = ResOwnerReleaseJitContext,
+	.PrintLeakWarning = ResOwnerPrintJitContextLeakWarning
+};
 
+PG_MODULE_MAGIC;
 
 /*
  * Initialize LLVM JIT provider.
@@ -151,7 +163,7 @@ llvm_create_context(int jitFlags)
 
 	llvm_session_initialize();
 
-	ResourceOwnerEnlargeJIT(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	context = MemoryContextAllocZero(TopMemoryContext,
 									 sizeof(LLVMJitContext));
@@ -159,7 +171,7 @@ llvm_create_context(int jitFlags)
 
 	/* ensure cleanup */
 	context->base.resowner = CurrentResourceOwner;
-	ResourceOwnerRememberJIT(CurrentResourceOwner, PointerGetDatum(context));
+	ResourceOwnerRemember(CurrentResourceOwner, PointerGetDatum(context), &jit_funcs);
 
 	return context;
 }
@@ -221,6 +233,8 @@ llvm_release_context(JitContext *context)
 
 		pfree(jit_handle);
 	}
+
+	ResourceOwnerForget(context->resowner, PointerGetDatum(context), &jit_funcs);
 }
 
 /*
@@ -1231,3 +1245,21 @@ llvm_error_message(LLVMErrorRef error)
 }
 
 #endif							/* LLVM_VERSION_MAJOR > 11 */
+
+/*
+ * ResourceOwner callbacks
+ */
+static void
+ResOwnerReleaseJitContext(Datum res)
+{
+	jit_release_context((JitContext *) PointerGetDatum(res));
+}
+
+static void
+ResOwnerPrintJitContextLeakWarning(Datum res)
+{
+	/* XXX: We used to not print these. Was that intentional? */
+	JitContext *context = (JitContext *) PointerGetDatum(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 cde869e7d64..82e39de19f9 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -52,7 +52,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"
 
 
@@ -206,6 +206,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
@@ -1094,7 +1106,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
@@ -1604,7 +1616,7 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
 	bool		result;
 	PrivateRefCountEntry *ref;
 
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	ref = GetPrivateRefCountEntry(b, true);
 
@@ -1687,7 +1699,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.
@@ -2423,7 +2435,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.
@@ -3496,7 +3508,7 @@ FlushRelationBuffers(Relation rel)
 
 		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
@@ -3591,7 +3603,7 @@ FlushRelationsAllBuffers(SMgrRelation *smgrs, int nrels)
 
 		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (RelFileNodeEquals(bufHdr->tag.rnode, srelent->rnode) &&
@@ -3646,7 +3658,7 @@ FlushDatabaseBuffers(Oid dbid)
 
 		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (bufHdr->tag.rnode.dbNode == dbid &&
@@ -3729,7 +3741,7 @@ void
 IncrBufferRefCount(Buffer buffer)
 {
 	Assert(BufferIsPinned(buffer));
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	if (BufferIsLocal(buffer))
 		LocalRefCount[-buffer - 1]++;
 	else
@@ -4803,3 +4815,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 f7c15ea8a44..3f22d7127b9 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -22,7 +22,7 @@
 #include "storage/bufmgr.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 
 
 /*#define LBDEBUG*/
@@ -124,7 +124,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 b58502837aa..1172ea245e0 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -97,7 +97,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)
@@ -340,6 +340,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
  */
@@ -1430,7 +1448,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
@@ -1612,7 +1630,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
@@ -1742,7 +1760,7 @@ PathNameCreateTemporaryFile(const char *path, bool error_on_failure)
 {
 	File		file;
 
-	ResourceOwnerEnlargeFiles(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	/*
 	 * Open the file.  Note: we don't use O_EXCL, in case there is an orphaned
@@ -1780,7 +1798,7 @@ PathNameOpenTemporaryFile(const char *path, int mode)
 {
 	File		file;
 
-	ResourceOwnerEnlargeFiles(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	file = PathNameOpenFile(path, mode | PG_BINARY);
 
@@ -3700,3 +3718,19 @@ pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
 
 	return sum;
 }
+
+/*
+ * 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 ae82b4bdc0e..31e8860fb53 100644
--- a/src/backend/storage/ipc/dsm.c
+++ b/src/backend/storage/ipc/dsm.c
@@ -37,13 +37,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
 
@@ -139,6 +141,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.
  *
@@ -895,7 +916,7 @@ void
 dsm_unpin_mapping(dsm_segment *seg)
 {
 	Assert(seg->resowner == NULL);
-	ResourceOwnerEnlargeDSMs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	seg->resowner = CurrentResourceOwner;
 	ResourceOwnerRememberDSM(seg->resowner, seg);
 }
@@ -1162,7 +1183,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);
@@ -1241,3 +1262,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 20e50247ea4..de39c844d49 100644
--- a/src/backend/storage/lmgr/lock.c
+++ b/src/backend/storage/lmgr/lock.c
@@ -47,7 +47,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 86fdfc454d2..185ff00ee76 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_funcs =
+{
+	/* catcache references */
+	.name = "catcache reference",
+	.phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.ReleaseResource = ResOwnerReleaseCatCache,
+	.PrintLeakWarning = ResOwnerPrintCatCacheLeakWarning
+};
+
+static ResourceOwnerFuncs catlistref_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_funcs)
+#define ResourceOwnerForgetCatCacheRef(owner, tuple) \
+	ResourceOwnerForget(owner, PointerGetDatum(tuple), &catcache_funcs)
+#define ResourceOwnerRememberCatCacheListRef(owner, list) \
+	ResourceOwnerRemember(owner, PointerGetDatum(list), &catlistref_funcs)
+#define ResourceOwnerForgetCatCacheListRef(owner, list) \
+	ResourceOwnerForget(owner, PointerGetDatum(list), &catlistref_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
@@ -1270,7 +1307,7 @@ SearchCatCacheInternal(CatCache *cache,
 		 */
 		if (!ct->negative)
 		{
-			ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner);
+			ResourceOwnerEnlarge(CurrentResourceOwner);
 			ct->refcount++;
 			ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple);
 
@@ -1371,7 +1408,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 */
@@ -1583,7 +1620,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);
 
@@ -1695,7 +1732,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);
@@ -2069,14 +2106,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));
 
@@ -2090,9 +2132,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 cc04b5b4bef..ecc9296d2d3 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;
 
@@ -1229,7 +1249,7 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 
 	/* Flag the plan as in use by caller */
 	if (useResOwner)
-		ResourceOwnerEnlargePlanCacheRefs(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 	plan->refcount++;
 	if (useResOwner)
 		ResourceOwnerRememberPlanCacheRef(CurrentResourceOwner, 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);
 	}
@@ -2205,3 +2225,20 @@ ResetPlanCache(void)
 		cexpr->is_valid = false;
 	}
 }
+
+/*
+ * ResourceOwner callbacks
+ */
+
+static void
+ResOwnerReleaseCachedPlan(Datum res)
+{
+	ReleaseCachedPlan((CachedPlan *) DatumGetPointer(res), true);
+}
+
+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 7ef510cd01b..255d19a88b9 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -78,13 +78,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"
 
@@ -2078,6 +2079,18 @@ 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
+};
+
 /*
  * RelationIncrementReferenceCount
  *		Increments relation reference count.
@@ -2089,10 +2102,10 @@ RelationIdGetRelation(Oid relationId)
 void
 RelationIncrementReferenceCount(Relation rel)
 {
-	ResourceOwnerEnlargeRelationRefs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	rel->rd_refcnt += 1;
 	if (!IsBootstrapProcessingMode())
-		ResourceOwnerRememberRelationRef(CurrentResourceOwner, rel);
+		ResourceOwnerRemember(CurrentResourceOwner, PointerGetDatum(rel), &relref_resowner_funcs);
 }
 
 /*
@@ -2105,7 +2118,7 @@ RelationDecrementReferenceCount(Relation rel)
 	Assert(rel->rd_refcnt > 0);
 	rel->rd_refcnt -= 1;
 	if (!IsBootstrapProcessingMode())
-		ResourceOwnerForgetRelationRef(CurrentResourceOwner, rel);
+		ResourceOwnerForget(CurrentResourceOwner, PointerGetDatum(rel), &relref_resowner_funcs);
 }
 
 /*
@@ -6417,3 +6430,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 2998f6bb362..34896b303ac 100644
--- a/src/backend/utils/resowner/README
+++ b/src/backend/utils/resowner/README
@@ -60,13 +60,18 @@ subtransaction or portal.  Therefore, the "release" operation on a child
 ResourceOwner transfers lock ownership to the parent instead of actually
 releasing the lock, if isCommit is true.
 
-Currently, ResourceOwners contain direct support for recording ownership of
-buffer pins, lmgr locks, and catcache, relcache, plancache, tupdesc, and
-snapshot references.  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 objects.
+As of this writing, it's used internally for buffer pins, lmgr locks, and
+catcache, relcache, plancache, tupdesc, snapshot, DSM and JIT context
+references. ResourceOwner treats all objects the same, but to register a
+new kind of an object with it, you need to fill in a few callback
+functions, see ResourceOwnerFuncs.
+
+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. This used to be the only way to register new kinds
+of objects with a resource owner; nowadays it easier to write custom
+ResourceOwnerFuncs callbacks.
 
 Whenever we are inside a transaction, the global variable
 CurrentResourceOwner shows which resource owner should be assigned
diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index 10f15f6a357..99240679e09 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -22,73 +22,44 @@
 
 #include "common/cryptohash.h"
 #include "common/hashfn.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;
+} 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 8
 
 /*
- * 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 32
 
 /*
- * 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
@@ -118,23 +89,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 */
+	/*
+	 * 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;
 
 
 /*****************************************************************************
@@ -146,6 +127,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
  */
@@ -160,296 +153,311 @@ 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 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);
 
 
 /*****************************************************************************
  *	  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
- */
 static void
-ResourceArrayInit(ResourceArray *resarr, Datum invalidval)
+ResourceArrayAddToHash(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;
+
+	idx = hash_resource_elem(value, kind) & mask;
+	for (;;)
+	{
+		if (owner->hash[idx].kind == NULL)
+			break;
+		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;
-
-	if (resarr->nitems < resarr->maxitems)
-		return;					/* no work needed */
+	bool		found;
+	int			capacity;
 
-	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. */
+	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 */
+	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 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
- *
- * Returns true on success, false if resource was not found.
+ * Make sure there is room for at least one more resource in an array.
  *
- * Note: if same resource ID appears more than once, one instance is removed.
+ * 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.
  */
-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 FIXME */
+		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)
+					ResourceArrayAddToHash(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 */
+	for (int i = 0; i < owner->narr; i++)
+	{
+		ResourceArrayAddToHash(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.
+ * Remember that an object is owner by a ReourceOwner
  *
- * "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.
- *
- * 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++;
-		}
-	}
+	Assert(owner->narr < RESOWNER_ARRAY_SIZE);
 
-	*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
+ *
+ * Returns true on success. If the resource was not found, returns false,
+ * and calls kind->ForgetError callback.
+ *
+ * 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;
 
+#ifdef RESOWNER_TRACE
+	elog(LOG, "FORGET %d: owner %p value " UINT64_FORMAT ", kind: %s",
+		 resowner_trace_counter++, owner, DatumGetUInt64(value), kind->name);
+#endif
 
-/*****************************************************************************
- *	  EXPORTED ROUTINES														 *
- *****************************************************************************/
-
+	/* 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));
+		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);
 }
 
 /*
@@ -486,6 +494,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
@@ -497,7 +514,6 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 	ResourceOwner child;
 	ResourceOwner save;
 	ResourceReleaseCallbackItem *item;
-	Datum		foundres;
 
 	/* Recurse to handle descendants */
 	for (child = owner->firstchild; child != NULL; child = child->nextchild)
@@ -513,61 +529,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 references that need to be released before the locks.
 		 *
-		 * During a commit, there shouldn't be any remaining pins --- that
+		 * During a commit, there shouldn't be any remaining references --- 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 *) PointerGetDatum(foundres);
-
-			jit_release_context(context);
-		}
-
-		/* Ditto for cryptohash contexts */
-		while (ResourceArrayGetAny(&(owner->cryptohasharr), &foundres))
-		{
-			pg_cryptohash_ctx *context =
-			(pg_cryptohash_ctx *) PointerGetDatum(foundres);
-
-			if (isCommit)
-				PrintCryptoHashLeakWarning(foundres);
-			pg_cryptohash_free(context);
-		}
+		ResourceOwnerReleaseAll(owner, phase, isCommit);
 	}
 	else if (phase == RESOURCE_RELEASE_LOCKS)
 	{
@@ -576,7 +544,7 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 			/*
 			 * For a top-level xact we are going to release all locks (or at
 			 * least all non-session locks), so just do a single lmgr call at
-			 * the top of the recursion.
+			 * the top of the recursion
 			 */
 			if (owner == TopTransactionResourceOwner)
 			{
@@ -620,70 +588,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 references 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, true);
-		}
-
-		/* 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 */
@@ -704,16 +611,42 @@ void
 ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner)
 {
 	ResourceOwner save;
-	Datum		foundres;
 
 	save = CurrentResourceOwner;
 	CurrentResourceOwner = owner;
-	while (ResourceArrayGetAny(&(owner->planrefarr), &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 'false' because we already removed the entry from the resowner */
+			ReleaseCachedPlan(planref, false);
+		}
+	}
+
+	/* 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, true);
+			/* pass 'false' because we already removed the entry from the resowner */
+			ReleaseCachedPlan(planref, false);
+		}
 	}
+
 	CurrentResourceOwner = save;
 }
 
@@ -730,19 +663,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->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.
@@ -758,18 +687,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));
-
+	if (owner->hash)
+		pfree(owner->hash);
 	pfree(owner);
 }
 
@@ -922,44 +841,6 @@ ReleaseAuxProcessResourcesCallback(int code, Datum arg)
 	ReleaseAuxProcessResources(isCommit);
 }
 
-
-/*
- * Make sure there is room for at least one more entry in a ResourceOwner's
- * buffer 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
-ResourceOwnerEnlargeBuffers(ResourceOwner owner)
-{
-	/* 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
  *
@@ -1011,424 +892,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));
-}
diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c
index ae16c3ed7d6..10cec8d39d0 100644
--- a/src/backend/utils/time/snapmgr.c
+++ b/src/backend/utils/time/snapmgr.c
@@ -66,7 +66,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"
@@ -174,6 +174,18 @@ 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
+};
+
 /*
  * Snapshot fields to be serialized.
  *
@@ -831,9 +843,10 @@ 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);
+	ResourceOwnerRemember(owner, PointerGetDatum(snap),
+						  &snapshot_resowner_funcs);
 
 	if (snap->regd_count == 1)
 		pairingheap_add(&RegisteredSnapshots, &snap->ph_node);
@@ -870,7 +883,8 @@ UnregisterSnapshotFromOwner(Snapshot snapshot, ResourceOwner owner)
 	Assert(snapshot->regd_count > 0);
 	Assert(!pairingheap_is_empty(&RegisteredSnapshots));
 
-	ResourceOwnerForgetSnapshot(owner, snapshot);
+	ResourceOwnerForget(owner, PointerGetDatum(snapshot),
+						&snapshot_resowner_funcs);
 
 	snapshot->regd_count--;
 	if (snapshot->regd_count == 0)
@@ -2345,3 +2359,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 551ec392b60..642e72d8c0f 100644
--- a/src/common/cryptohash_openssl.c
+++ b/src/common/cryptohash_openssl.c
@@ -27,7 +27,6 @@
 #ifndef FRONTEND
 #include "utils/memutils.h"
 #include "utils/resowner.h"
-#include "utils/resowner_private.h"
 #endif
 
 /*
@@ -60,6 +59,21 @@ struct pg_cryptohash_ctx
 #endif
 };
 
+/* ResourceOwner callbacks to hold JitContexts  */
+#ifndef FRONTEND
+static void ResOwnerReleaseCryptoHash(Datum res);
+static void ResOwnerPrintCryptoHashLeakWarning(Datum res);
+
+static ResourceOwnerFuncs cryptohash_funcs =
+{
+	/* relcache references */
+	.name = "OpenSSL cryptohash context",
+	.phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.ReleaseResource = ResOwnerReleaseCryptoHash,
+	.PrintLeakWarning = ResOwnerPrintCryptoHashLeakWarning,
+};
+#endif
+
 /*
  * pg_cryptohash_create
  *
@@ -77,7 +91,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));
@@ -106,8 +120,8 @@ pg_cryptohash_create(pg_cryptohash_type type)
 
 #ifndef FRONTEND
 	ctx->resowner = CurrentResourceOwner;
-	ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
-									PointerGetDatum(ctx));
+	ResourceOwnerRemember(CurrentResourceOwner, PointerGetDatum(ctx),
+						  &cryptohash_funcs);
 #endif
 
 	return ctx;
@@ -207,10 +221,29 @@ pg_cryptohash_free(pg_cryptohash_ctx *ctx)
 	EVP_MD_CTX_destroy(ctx->evpctx);
 
 #ifndef FRONTEND
-	ResourceOwnerForgetCryptoHash(ctx->resowner,
-								  PointerGetDatum(ctx));
+	ResourceOwnerForget(ctx->resowner, PointerGetDatum(ctx),
+						&cryptohash_funcs);
 #endif
 
 	explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
 	FREE(ctx);
 }
+
+
+/*
+ * 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/include/storage/buf_internals.h b/src/include/storage/buf_internals.h
index f6b57829653..6815dc38a16 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -296,6 +296,15 @@ typedef struct CkptSortItem
 
 extern 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 ddc2762eb3f..2bf95c22e84 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 79d96e5ed03..964af79b30d 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 109ac31b248..c3f1f060fa9 100644
--- a/src/include/utils/resowner.h
+++ b/src/include/utils/resowner.h
@@ -50,6 +50,32 @@ typedef enum
 	RESOURCE_RELEASE_AFTER_LOCKS
 } ResourceReleasePhase;
 
+/*
+ * In order to track an object, resowner.c needs a few callbacks for it.
+ * The callbacks for an object of a specific kind are encapsulated in
+ * ResourceOwnerFuncs.
+ */
+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 +97,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 c480a1a24be..00000000000
--- a/src/include/utils/resowner_private.h
+++ /dev/null
@@ -1,105 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * resowner_private.h
- *	  POSTGRES resource owner private definitions.
- *
- * See utils/resowner/README for more info.
- *
- *
- * Portions Copyright (c) 1996-2021, 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);
-
-#endif							/* RESOWNER_PRIVATE_H */
-- 
2.29.2

#16kuroda.hayato@fujitsu.com
kuroda.hayato@fujitsu.com
In reply to: Heikki Linnakangas (#15)
RE: ResourceOwner refactoring

Dear Heikki,

Hmm. ResOwnerReleaseTupleDesc() does exist, those functions are needed
for the callbacks. I think you meant the wrappers around
ResourceOwnerRemember and ResourceOwnerForget, like
ResourceOwnerRememberCatCacheRef(). I admit those are not fully
consistent: I didn't bother with the wrapper functions when there is
only one caller.

Yeah, I meant it. And I prefer your policy.

Hmm. ResOwnerReleaseTupleDesc() does exist, those functions are needed
for the callbacks. I think you meant the wrappers around
ResourceOwnerRemember and ResourceOwnerForget, like
ResourceOwnerRememberCatCacheRef(). I admit those are not fully
consistent: I didn't bother with the wrapper functions when there is
only one caller.

Good job. I confirmed your fixes, and I confirmed it looks fine.
I will check another ResourceOwnerEnlarge() if I have a time.

I've been working on performance testing too.

I'm looking forward to seeing it.

Best regards,
Hayato Kuroda
FUJITSU LIMITED

#17kuroda.hayato@fujitsu.com
kuroda.hayato@fujitsu.com
In reply to: kuroda.hayato@fujitsu.com (#16)
RE: ResourceOwner refactoring

Dear Heikki,

I apologize for sending again.

I will check another ResourceOwnerEnlarge() if I have a time.

I checked all ResourceOwnerEnlarge() types after applying patch 0001.
(searched by "grep -rI ResourceOwnerEnlarge")
No problem was found.

Hayato Kuroda
FUJITSU LIMITED

#18Heikki Linnakangas
hlinnaka@iki.fi
In reply to: kuroda.hayato@fujitsu.com (#17)
3 attachment(s)
Re: ResourceOwner refactoring

On 18/01/2021 09:49, kuroda.hayato@fujitsu.com wrote:

Dear Heikki,

I apologize for sending again.

I will check another ResourceOwnerEnlarge() if I have a time.

I checked all ResourceOwnerEnlarge() types after applying patch 0001.
(searched by "grep -rI ResourceOwnerEnlarge")
No problem was found.

Great, thanks!

Here are more details on the performance testing I did:

Unpatched
----------

postgres=# \i snaptest.sql
numkeep | numsnaps | lifo_time_ns | fifo_time_ns
---------+----------+--------------+--------------
0 | 1 | 38.2 | 34.8
0 | 5 | 35.7 | 35.4
0 | 10 | 37.6 | 37.6
0 | 60 | 35.4 | 39.2
0 | 70 | 55.0 | 53.8
0 | 100 | 48.6 | 48.9
0 | 1000 | 54.8 | 57.0
0 | 10000 | 63.9 | 67.1
9 | 10 | 39.3 | 38.8
9 | 100 | 44.0 | 42.5
9 | 1000 | 45.8 | 47.1
9 | 10000 | 64.2 | 66.8
65 | 70 | 45.7 | 43.7
65 | 100 | 42.9 | 43.7
65 | 1000 | 46.9 | 45.7
65 | 10000 | 65.0 | 64.5
(16 rows)

Patched
--------

postgres=# \i snaptest.sql
numkeep | numsnaps | lifo_time_ns | fifo_time_ns
---------+----------+--------------+--------------
0 | 1 | 35.3 | 33.8
0 | 5 | 34.8 | 34.6
0 | 10 | 49.8 | 51.4
0 | 60 | 53.1 | 57.2
0 | 70 | 53.2 | 59.6
0 | 100 | 53.1 | 58.2
0 | 1000 | 63.1 | 69.7
0 | 10000 | 83.3 | 83.5
9 | 10 | 35.0 | 35.1
9 | 100 | 55.4 | 54.1
9 | 1000 | 56.8 | 60.3
9 | 10000 | 88.6 | 82.0
65 | 70 | 36.4 | 35.1
65 | 100 | 52.4 | 53.0
65 | 1000 | 55.8 | 59.4
65 | 10000 | 79.3 | 80.3
(16 rows)

About the test:

Each test call Register/UnregisterSnapshot in a loop. numsnaps is the
number of snapshots that are registered in each iteration. For exmaple,
on the second line with numkeep=0 and numnaps=5, each iteration in the
LIFO test does essentially:

rs[0] = RegisterSnapshot()
rs[1] = RegisterSnapshot()
rs[2] = RegisterSnapshot()
rs[3] = RegisterSnapshot()
rs[4] = RegisterSnapshot()
UnregisterSnapshot(rs[4]);
UnregisterSnapshot(rs[3]);
UnregisterSnapshot(rs[2]);
UnregisterSnapshot(rs[1]);
UnregisterSnapshot(rs[0]);

In the FIFO tests, the Unregister calls are made in reverse order.

When numkeep is non zero, that many snapshots are registered once at the
beginning of the test, and released only after the test loop. So this
simulates the scenario that you remember a bunch of resources and hold
them for a long time, and while holding them, you do many more
remember/forget calls.

Based on this test, this patch makes things slightly slower overall.
However, *not* in the cases that matter the most I believe. That's the
cases where the (numsnaps - numkeep) is small, so that the hot action
happens in the array and the hashing is not required. In my testing
earlier, about 95% of the ResourceOwnerRemember calls in the regression
tests fall into that category.

There are a few simple things we could do speed this up, if needed. A
different hash function might be cheaper, for example. And we could
inline the fast paths of the ResourceOwnerEnlarge and
ResourceOwnerRemember() into the callers. But I didn't do those things
as part of this patch, because it wouldn't be a fair comparison; we
could do those with the old implementation too. And unless we really
need the speed, it's more readable this way.

I'm OK with these results. Any objections?

Attached are the patches again. Same as previous version, except for a
couple of little comment changes. And a third patch containing the
source for the performance test.

- Heikki

Attachments:

v5-0002-Make-resowners-more-easily-extensible.patchtext/x-patch; charset=UTF-8; name=v5-0002-Make-resowners-more-easily-extensible.patchDownload
From a927977ecfd9a849fbb72a8fdc167ae1faaf0504 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 16 Dec 2020 17:26:03 +0200
Subject: [PATCH v5 2/2] Make resowners more easily extensible.

Use a single array and hash, instead of one for each object kind.
---
 src/backend/access/common/tupdesc.c   |   42 +-
 src/backend/jit/jit.c                 |    2 -
 src/backend/jit/llvm/llvmjit.c        |   40 +-
 src/backend/storage/buffer/bufmgr.c   |   45 +-
 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    |   39 +-
 src/backend/utils/resowner/README     |   19 +-
 src/backend/utils/resowner/resowner.c | 1221 +++++++------------------
 src/backend/utils/time/snapmgr.c      |   38 +-
 src/common/cryptohash_openssl.c       |   45 +-
 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  |  105 ---
 20 files changed, 811 insertions(+), 1057 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 902f59440cd..7fa44d5f7ee 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -29,9 +29,21 @@
 #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
+};
 
 /*
  * CreateTemplateTupleDesc
@@ -376,9 +388,10 @@ IncrTupleDescRefCount(TupleDesc tupdesc)
 {
 	Assert(tupdesc->tdrefcount >= 0);
 
-	ResourceOwnerEnlargeTupleDescs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	tupdesc->tdrefcount++;
-	ResourceOwnerRememberTupleDesc(CurrentResourceOwner, tupdesc);
+	ResourceOwnerRemember(CurrentResourceOwner, PointerGetDatum(tupdesc),
+						  &tupdesc_resowner_funcs);
 }
 
 /*
@@ -394,7 +407,8 @@ DecrTupleDescRefCount(TupleDesc tupdesc)
 {
 	Assert(tupdesc->tdrefcount > 0);
 
-	ResourceOwnerForgetTupleDesc(CurrentResourceOwner, tupdesc);
+	ResourceOwnerForget(CurrentResourceOwner, PointerGetDatum(tupdesc),
+						&tupdesc_resowner_funcs);
 	if (--tupdesc->tdrefcount == 0)
 		FreeTupleDesc(tupdesc);
 }
@@ -925,3 +939,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 2da300e000d..1aa04d173b4 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 b0789a5fb80..64c7fd92bc7 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,8 +121,20 @@ static LLVMOrcLLJITRef llvm_create_jit_instance(LLVMTargetMachineRef tm);
 static char *llvm_error_message(LLVMErrorRef error);
 #endif							/* LLVM_VERSION_MAJOR > 11 */
 
-PG_MODULE_MAGIC;
+/* ResourceOwner callbacks to hold JitContexts  */
+static void ResOwnerReleaseJitContext(Datum res);
+static void ResOwnerPrintJitContextLeakWarning(Datum res);
+
+static ResourceOwnerFuncs jit_funcs =
+{
+	/* relcache references */
+	.name = "LLVM JIT context",
+	.phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.ReleaseResource = ResOwnerReleaseJitContext,
+	.PrintLeakWarning = ResOwnerPrintJitContextLeakWarning
+};
 
+PG_MODULE_MAGIC;
 
 /*
  * Initialize LLVM JIT provider.
@@ -151,7 +163,7 @@ llvm_create_context(int jitFlags)
 
 	llvm_session_initialize();
 
-	ResourceOwnerEnlargeJIT(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	context = MemoryContextAllocZero(TopMemoryContext,
 									 sizeof(LLVMJitContext));
@@ -159,7 +171,7 @@ llvm_create_context(int jitFlags)
 
 	/* ensure cleanup */
 	context->base.resowner = CurrentResourceOwner;
-	ResourceOwnerRememberJIT(CurrentResourceOwner, PointerGetDatum(context));
+	ResourceOwnerRemember(CurrentResourceOwner, PointerGetDatum(context), &jit_funcs);
 
 	return context;
 }
@@ -221,6 +233,8 @@ llvm_release_context(JitContext *context)
 
 		pfree(jit_handle);
 	}
+
+	ResourceOwnerForget(context->resowner, PointerGetDatum(context), &jit_funcs);
 }
 
 /*
@@ -1231,3 +1245,21 @@ llvm_error_message(LLVMErrorRef error)
 }
 
 #endif							/* LLVM_VERSION_MAJOR > 11 */
+
+/*
+ * ResourceOwner callbacks
+ */
+static void
+ResOwnerReleaseJitContext(Datum res)
+{
+	jit_release_context((JitContext *) PointerGetDatum(res));
+}
+
+static void
+ResOwnerPrintJitContextLeakWarning(Datum res)
+{
+	/* XXX: We used to not print these. Was that intentional? */
+	JitContext *context = (JitContext *) PointerGetDatum(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 cde869e7d64..82e39de19f9 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -52,7 +52,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"
 
 
@@ -206,6 +206,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
@@ -1094,7 +1106,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
@@ -1604,7 +1616,7 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
 	bool		result;
 	PrivateRefCountEntry *ref;
 
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	ref = GetPrivateRefCountEntry(b, true);
 
@@ -1687,7 +1699,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.
@@ -2423,7 +2435,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.
@@ -3496,7 +3508,7 @@ FlushRelationBuffers(Relation rel)
 
 		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
@@ -3591,7 +3603,7 @@ FlushRelationsAllBuffers(SMgrRelation *smgrs, int nrels)
 
 		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (RelFileNodeEquals(bufHdr->tag.rnode, srelent->rnode) &&
@@ -3646,7 +3658,7 @@ FlushDatabaseBuffers(Oid dbid)
 
 		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (bufHdr->tag.rnode.dbNode == dbid &&
@@ -3729,7 +3741,7 @@ void
 IncrBufferRefCount(Buffer buffer)
 {
 	Assert(BufferIsPinned(buffer));
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	if (BufferIsLocal(buffer))
 		LocalRefCount[-buffer - 1]++;
 	else
@@ -4803,3 +4815,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 f7c15ea8a44..3f22d7127b9 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -22,7 +22,7 @@
 #include "storage/bufmgr.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 
 
 /*#define LBDEBUG*/
@@ -124,7 +124,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 b58502837aa..1172ea245e0 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -97,7 +97,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)
@@ -340,6 +340,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
  */
@@ -1430,7 +1448,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
@@ -1612,7 +1630,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
@@ -1742,7 +1760,7 @@ PathNameCreateTemporaryFile(const char *path, bool error_on_failure)
 {
 	File		file;
 
-	ResourceOwnerEnlargeFiles(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	/*
 	 * Open the file.  Note: we don't use O_EXCL, in case there is an orphaned
@@ -1780,7 +1798,7 @@ PathNameOpenTemporaryFile(const char *path, int mode)
 {
 	File		file;
 
-	ResourceOwnerEnlargeFiles(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	file = PathNameOpenFile(path, mode | PG_BINARY);
 
@@ -3700,3 +3718,19 @@ pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
 
 	return sum;
 }
+
+/*
+ * 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 ae82b4bdc0e..31e8860fb53 100644
--- a/src/backend/storage/ipc/dsm.c
+++ b/src/backend/storage/ipc/dsm.c
@@ -37,13 +37,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
 
@@ -139,6 +141,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.
  *
@@ -895,7 +916,7 @@ void
 dsm_unpin_mapping(dsm_segment *seg)
 {
 	Assert(seg->resowner == NULL);
-	ResourceOwnerEnlargeDSMs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	seg->resowner = CurrentResourceOwner;
 	ResourceOwnerRememberDSM(seg->resowner, seg);
 }
@@ -1162,7 +1183,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);
@@ -1241,3 +1262,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 20e50247ea4..de39c844d49 100644
--- a/src/backend/storage/lmgr/lock.c
+++ b/src/backend/storage/lmgr/lock.c
@@ -47,7 +47,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 86fdfc454d2..185ff00ee76 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_funcs =
+{
+	/* catcache references */
+	.name = "catcache reference",
+	.phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.ReleaseResource = ResOwnerReleaseCatCache,
+	.PrintLeakWarning = ResOwnerPrintCatCacheLeakWarning
+};
+
+static ResourceOwnerFuncs catlistref_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_funcs)
+#define ResourceOwnerForgetCatCacheRef(owner, tuple) \
+	ResourceOwnerForget(owner, PointerGetDatum(tuple), &catcache_funcs)
+#define ResourceOwnerRememberCatCacheListRef(owner, list) \
+	ResourceOwnerRemember(owner, PointerGetDatum(list), &catlistref_funcs)
+#define ResourceOwnerForgetCatCacheListRef(owner, list) \
+	ResourceOwnerForget(owner, PointerGetDatum(list), &catlistref_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
@@ -1270,7 +1307,7 @@ SearchCatCacheInternal(CatCache *cache,
 		 */
 		if (!ct->negative)
 		{
-			ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner);
+			ResourceOwnerEnlarge(CurrentResourceOwner);
 			ct->refcount++;
 			ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple);
 
@@ -1371,7 +1408,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 */
@@ -1583,7 +1620,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);
 
@@ -1695,7 +1732,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);
@@ -2069,14 +2106,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));
 
@@ -2090,9 +2132,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 cc04b5b4bef..ecc9296d2d3 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;
 
@@ -1229,7 +1249,7 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 
 	/* Flag the plan as in use by caller */
 	if (useResOwner)
-		ResourceOwnerEnlargePlanCacheRefs(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 	plan->refcount++;
 	if (useResOwner)
 		ResourceOwnerRememberPlanCacheRef(CurrentResourceOwner, 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);
 	}
@@ -2205,3 +2225,20 @@ ResetPlanCache(void)
 		cexpr->is_valid = false;
 	}
 }
+
+/*
+ * ResourceOwner callbacks
+ */
+
+static void
+ResOwnerReleaseCachedPlan(Datum res)
+{
+	ReleaseCachedPlan((CachedPlan *) DatumGetPointer(res), true);
+}
+
+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 7ef510cd01b..255d19a88b9 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -78,13 +78,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"
 
@@ -2078,6 +2079,18 @@ 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
+};
+
 /*
  * RelationIncrementReferenceCount
  *		Increments relation reference count.
@@ -2089,10 +2102,10 @@ RelationIdGetRelation(Oid relationId)
 void
 RelationIncrementReferenceCount(Relation rel)
 {
-	ResourceOwnerEnlargeRelationRefs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	rel->rd_refcnt += 1;
 	if (!IsBootstrapProcessingMode())
-		ResourceOwnerRememberRelationRef(CurrentResourceOwner, rel);
+		ResourceOwnerRemember(CurrentResourceOwner, PointerGetDatum(rel), &relref_resowner_funcs);
 }
 
 /*
@@ -2105,7 +2118,7 @@ RelationDecrementReferenceCount(Relation rel)
 	Assert(rel->rd_refcnt > 0);
 	rel->rd_refcnt -= 1;
 	if (!IsBootstrapProcessingMode())
-		ResourceOwnerForgetRelationRef(CurrentResourceOwner, rel);
+		ResourceOwnerForget(CurrentResourceOwner, PointerGetDatum(rel), &relref_resowner_funcs);
 }
 
 /*
@@ -6417,3 +6430,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 2998f6bb362..c67ca603526 100644
--- a/src/backend/utils/resowner/README
+++ b/src/backend/utils/resowner/README
@@ -60,13 +60,18 @@ subtransaction or portal.  Therefore, the "release" operation on a child
 ResourceOwner transfers lock ownership to the parent instead of actually
 releasing the lock, if isCommit is true.
 
-Currently, ResourceOwners contain direct support for recording ownership of
-buffer pins, lmgr locks, and catcache, relcache, plancache, tupdesc, and
-snapshot references.  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 objects.
+As of this writing, it's used internally for buffer pins, lmgr locks, and
+catcache, relcache, plancache, tupdesc, snapshot, DSM and JIT context
+references. ResourceOwner treats all resource the same, and extensions
+can define a new kind of resource by defining a new ResourceOwnerFuncs
+instance with a few 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. This used to be the only way to register new kinds
+of objects with a resource owner; nowadays it easier to write custom
+ResourceOwnerFuncs callbacks.
 
 Whenever we are inside a transaction, the global variable
 CurrentResourceOwner shows which resource owner should be assigned
diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index 10f15f6a357..75fda4fcc13 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -22,73 +22,44 @@
 
 #include "common/cryptohash.h"
 #include "common/hashfn.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;
+} 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 8
 
 /*
- * 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 32
 
 /*
- * 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
@@ -118,23 +89,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 */
+	/*
+	 * 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;
 
 
 /*****************************************************************************
@@ -146,6 +127,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
  */
@@ -160,296 +153,311 @@ 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 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);
 
 
 /*****************************************************************************
  *	  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
- */
 static void
-ResourceArrayInit(ResourceArray *resarr, Datum invalidval)
+ResourceArrayAddToHash(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;
+
+	idx = hash_resource_elem(value, kind) & mask;
+	for (;;)
+	{
+		if (owner->hash[idx].kind == NULL)
+			break;
+		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;
-
-	if (resarr->nitems < resarr->maxitems)
-		return;					/* no work needed */
+	bool		found;
+	int			capacity;
 
-	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. */
+	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 */
+	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 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
- *
- * Returns true on success, false if resource was not found.
+ * Make sure there is room for at least one more resource in an array.
  *
- * Note: if same resource ID appears more than once, one instance is removed.
+ * 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.
  */
-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 FIXME */
+		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)
+					ResourceArrayAddToHash(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 */
+	for (int i = 0; i < owner->narr; i++)
+	{
+		ResourceArrayAddToHash(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++;
-		}
-	}
+	Assert(owner->narr < RESOWNER_ARRAY_SIZE);
 
-	*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
+ *
+ * Returns true on success. If the resource was not found, returns false,
+ * and calls kind->ForgetError callback.
+ *
+ * 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;
 
+#ifdef RESOWNER_TRACE
+	elog(LOG, "FORGET %d: owner %p value " UINT64_FORMAT ", kind: %s",
+		 resowner_trace_counter++, owner, DatumGetUInt64(value), kind->name);
+#endif
 
-/*****************************************************************************
- *	  EXPORTED ROUTINES														 *
- *****************************************************************************/
+	/* 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--;
 
+#ifdef RESOWNER_STATS
+			narray_lookups++;
+#endif
 
-/*
- * 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;
-
-	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));
+		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);
 }
 
 /*
@@ -486,6 +494,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
@@ -497,7 +514,6 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 	ResourceOwner child;
 	ResourceOwner save;
 	ResourceReleaseCallbackItem *item;
-	Datum		foundres;
 
 	/* Recurse to handle descendants */
 	for (child = owner->firstchild; child != NULL; child = child->nextchild)
@@ -513,61 +529,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 references that need to be released before the locks.
 		 *
-		 * During a commit, there shouldn't be any remaining pins --- that
+		 * During a commit, there shouldn't be any remaining references --- 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 *) PointerGetDatum(foundres);
-
-			jit_release_context(context);
-		}
-
-		/* Ditto for cryptohash contexts */
-		while (ResourceArrayGetAny(&(owner->cryptohasharr), &foundres))
-		{
-			pg_cryptohash_ctx *context =
-			(pg_cryptohash_ctx *) PointerGetDatum(foundres);
-
-			if (isCommit)
-				PrintCryptoHashLeakWarning(foundres);
-			pg_cryptohash_free(context);
-		}
+		ResourceOwnerReleaseAll(owner, phase, isCommit);
 	}
 	else if (phase == RESOURCE_RELEASE_LOCKS)
 	{
@@ -576,7 +544,7 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 			/*
 			 * For a top-level xact we are going to release all locks (or at
 			 * least all non-session locks), so just do a single lmgr call at
-			 * the top of the recursion.
+			 * the top of the recursion
 			 */
 			if (owner == TopTransactionResourceOwner)
 			{
@@ -620,70 +588,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 references 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, true);
-		}
-
-		/* 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 */
@@ -704,16 +611,42 @@ void
 ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner)
 {
 	ResourceOwner save;
-	Datum		foundres;
 
 	save = CurrentResourceOwner;
 	CurrentResourceOwner = owner;
-	while (ResourceArrayGetAny(&(owner->planrefarr), &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 'false' because we already removed the entry from the resowner */
+			ReleaseCachedPlan(planref, false);
+		}
+	}
+
+	/* 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);
 
-		ReleaseCachedPlan(res, true);
+			owner->hash[i].item = (Datum) 0;
+			owner->hash[i].kind = NULL;
+			owner->nhash--;
+
+			/* pass 'false' because we already removed the entry from the resowner */
+			ReleaseCachedPlan(planref, false);
+		}
 	}
+
 	CurrentResourceOwner = save;
 }
 
@@ -730,19 +663,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->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.
@@ -758,18 +687,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));
-
+	if (owner->hash)
+		pfree(owner->hash);
 	pfree(owner);
 }
 
@@ -827,11 +746,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)
@@ -922,44 +840,6 @@ ReleaseAuxProcessResourcesCallback(int code, Datum arg)
 	ReleaseAuxProcessResources(isCommit);
 }
 
-
-/*
- * Make sure there is room for at least one more entry in a ResourceOwner's
- * buffer 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
-ResourceOwnerEnlargeBuffers(ResourceOwner owner)
-{
-	/* 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
  *
@@ -1011,424 +891,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));
-}
diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c
index ae16c3ed7d6..10cec8d39d0 100644
--- a/src/backend/utils/time/snapmgr.c
+++ b/src/backend/utils/time/snapmgr.c
@@ -66,7 +66,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"
@@ -174,6 +174,18 @@ 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
+};
+
 /*
  * Snapshot fields to be serialized.
  *
@@ -831,9 +843,10 @@ 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);
+	ResourceOwnerRemember(owner, PointerGetDatum(snap),
+						  &snapshot_resowner_funcs);
 
 	if (snap->regd_count == 1)
 		pairingheap_add(&RegisteredSnapshots, &snap->ph_node);
@@ -870,7 +883,8 @@ UnregisterSnapshotFromOwner(Snapshot snapshot, ResourceOwner owner)
 	Assert(snapshot->regd_count > 0);
 	Assert(!pairingheap_is_empty(&RegisteredSnapshots));
 
-	ResourceOwnerForgetSnapshot(owner, snapshot);
+	ResourceOwnerForget(owner, PointerGetDatum(snapshot),
+						&snapshot_resowner_funcs);
 
 	snapshot->regd_count--;
 	if (snapshot->regd_count == 0)
@@ -2345,3 +2359,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 551ec392b60..642e72d8c0f 100644
--- a/src/common/cryptohash_openssl.c
+++ b/src/common/cryptohash_openssl.c
@@ -27,7 +27,6 @@
 #ifndef FRONTEND
 #include "utils/memutils.h"
 #include "utils/resowner.h"
-#include "utils/resowner_private.h"
 #endif
 
 /*
@@ -60,6 +59,21 @@ struct pg_cryptohash_ctx
 #endif
 };
 
+/* ResourceOwner callbacks to hold JitContexts  */
+#ifndef FRONTEND
+static void ResOwnerReleaseCryptoHash(Datum res);
+static void ResOwnerPrintCryptoHashLeakWarning(Datum res);
+
+static ResourceOwnerFuncs cryptohash_funcs =
+{
+	/* relcache references */
+	.name = "OpenSSL cryptohash context",
+	.phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.ReleaseResource = ResOwnerReleaseCryptoHash,
+	.PrintLeakWarning = ResOwnerPrintCryptoHashLeakWarning,
+};
+#endif
+
 /*
  * pg_cryptohash_create
  *
@@ -77,7 +91,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));
@@ -106,8 +120,8 @@ pg_cryptohash_create(pg_cryptohash_type type)
 
 #ifndef FRONTEND
 	ctx->resowner = CurrentResourceOwner;
-	ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
-									PointerGetDatum(ctx));
+	ResourceOwnerRemember(CurrentResourceOwner, PointerGetDatum(ctx),
+						  &cryptohash_funcs);
 #endif
 
 	return ctx;
@@ -207,10 +221,29 @@ pg_cryptohash_free(pg_cryptohash_ctx *ctx)
 	EVP_MD_CTX_destroy(ctx->evpctx);
 
 #ifndef FRONTEND
-	ResourceOwnerForgetCryptoHash(ctx->resowner,
-								  PointerGetDatum(ctx));
+	ResourceOwnerForget(ctx->resowner, PointerGetDatum(ctx),
+						&cryptohash_funcs);
 #endif
 
 	explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
 	FREE(ctx);
 }
+
+
+/*
+ * 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/include/storage/buf_internals.h b/src/include/storage/buf_internals.h
index f6b57829653..6815dc38a16 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -296,6 +296,15 @@ typedef struct CkptSortItem
 
 extern 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 ddc2762eb3f..2bf95c22e84 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 79d96e5ed03..964af79b30d 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 109ac31b248..d59d14c0d01 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 an resource 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 c480a1a24be..00000000000
--- a/src/include/utils/resowner_private.h
+++ /dev/null
@@ -1,105 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * resowner_private.h
- *	  POSTGRES resource owner private definitions.
- *
- * See utils/resowner/README for more info.
- *
- *
- * Portions Copyright (c) 1996-2021, 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);
-
-#endif							/* RESOWNER_PRIVATE_H */
-- 
2.29.2

0001-resownerbench.patchtext/x-patch; charset=UTF-8; name=0001-resownerbench.patchDownload
From f56b69c9495cb325d91664e4cd509c91c850f52a Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Thu, 12 Nov 2020 00:40:45 +0200
Subject: [PATCH 1/1] resownerbench

---
 contrib/resownerbench/Makefile               |  17 ++
 contrib/resownerbench/resownerbench--1.0.sql |  14 ++
 contrib/resownerbench/resownerbench.c        | 154 +++++++++++++++++++
 contrib/resownerbench/resownerbench.control  |   6 +
 contrib/resownerbench/snaptest.sql           |  37 +++++
 5 files changed, 228 insertions(+)
 create mode 100644 contrib/resownerbench/Makefile
 create mode 100644 contrib/resownerbench/resownerbench--1.0.sql
 create mode 100644 contrib/resownerbench/resownerbench.c
 create mode 100644 contrib/resownerbench/resownerbench.control
 create mode 100644 contrib/resownerbench/snaptest.sql

diff --git a/contrib/resownerbench/Makefile b/contrib/resownerbench/Makefile
new file mode 100644
index 00000000000..9b0e1cfee1a
--- /dev/null
+++ b/contrib/resownerbench/Makefile
@@ -0,0 +1,17 @@
+MODULE_big = resownerbench
+OBJS = resownerbench.o
+
+EXTENSION = resownerbench
+DATA = resownerbench--1.0.sql
+PGFILEDESC = "resownerbench - benchmark for ResourceOwners"
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/resownerbench
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/resownerbench/resownerbench--1.0.sql b/contrib/resownerbench/resownerbench--1.0.sql
new file mode 100644
index 00000000000..d29182f5982
--- /dev/null
+++ b/contrib/resownerbench/resownerbench--1.0.sql
@@ -0,0 +1,14 @@
+/* contrib/resownerbench/resownerbench--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION resownerbench" to load this file. \quit
+
+CREATE FUNCTION snapshotbench_lifo(numkeep int, numsnaps int, numiters int)
+RETURNS double precision
+AS 'MODULE_PATHNAME', 'snapshotbench_lifo'
+LANGUAGE C STRICT VOLATILE;
+
+CREATE FUNCTION snapshotbench_fifo(numkeep int, numsnaps int, numiters int)
+RETURNS double precision
+AS 'MODULE_PATHNAME', 'snapshotbench_fifo'
+LANGUAGE C STRICT VOLATILE;
diff --git a/contrib/resownerbench/resownerbench.c b/contrib/resownerbench/resownerbench.c
new file mode 100644
index 00000000000..acfb6c39199
--- /dev/null
+++ b/contrib/resownerbench/resownerbench.c
@@ -0,0 +1,154 @@
+#include "postgres.h"
+
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "executor/spi.h"
+#include "funcapi.h"
+#include "libpq/pqsignal.h"
+#include "utils/catcache.h"
+#include "utils/syscache.h"
+#include "utils/timestamp.h"
+#include "utils/snapmgr.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(snapshotbench_lifo);
+PG_FUNCTION_INFO_V1(snapshotbench_fifo);
+
+/*
+ * ResourceOwner Performance test, using RegisterSnapshot().
+ *
+ * This takes three parameters: numkeep, numsnaps, numiters.
+ *
+ * First, we register 'numkeep' snapshots. They are kept registed
+ * until the end of the test. Then, we repeatedly register and
+ * unregister 'numsnaps - numkeep' additional snapshots, repeating
+ * 'numiters' times. All the register/unregister calls are made in
+ * LIFO order.
+ *
+ * Returns the time spent, in milliseconds.
+ *
+ * The idea is to test the performance of ResourceOwnerRemember()
+ * and ReourceOwnerForget() operations, under different regimes.
+ *
+ * In the old implementation, if 'numsnaps' is small enough, all
+ * the entries fit in the resource owner's small array (it can
+ * hold 64 entries).
+ *
+ * In the new implementation, the array is much smaller, only 8
+ * entries, but it's used together with the hash table so that
+ * we stay in the "array regime" as long as 'numsnaps - numkeep'
+ * is smaller than 8 entries.
+ *
+ * 'numiters' can be adjusted to adjust the overall runtime to be
+ * suitable long.
+ */
+Datum
+snapshotbench_lifo(PG_FUNCTION_ARGS)
+{
+	int			numkeep = PG_GETARG_INT32(0);
+	int			numsnaps = PG_GETARG_INT32(1);
+	int			numiters = PG_GETARG_INT32(2);
+	int			i;
+	instr_time	start,
+				duration;
+	Snapshot	lsnap;
+	Snapshot   *rs;
+	int			numregistered = 0;
+
+	rs = palloc(Max(numsnaps, numkeep) * sizeof(Snapshot));
+
+	lsnap = GetLatestSnapshot();
+
+	PG_SETMASK(&BlockSig);
+	INSTR_TIME_SET_CURRENT(start);
+
+	while (numregistered < numkeep)
+	{
+		rs[numregistered] = RegisterSnapshot(lsnap);
+		numregistered++;
+	}
+
+	for (i = 0 ; i < numiters; i++)
+	{
+		while (numregistered < numsnaps)
+		{
+			rs[numregistered] = RegisterSnapshot(lsnap);
+			numregistered++;
+		}
+
+		while (numregistered > numkeep)
+		{
+			numregistered--;
+			UnregisterSnapshot(rs[numregistered]);
+		}
+	}
+
+	while (numregistered > 0)
+	{
+		numregistered--;
+		UnregisterSnapshot(rs[numregistered]);
+	}
+
+	INSTR_TIME_SET_CURRENT(duration);
+	INSTR_TIME_SUBTRACT(duration, start);
+	PG_SETMASK(&UnBlockSig);
+
+	PG_RETURN_FLOAT8(INSTR_TIME_GET_MILLISEC(duration));
+};
+
+
+/*
+ * Same, but do the register/unregister operations in
+ * FIFO order.
+ */
+Datum
+snapshotbench_fifo(PG_FUNCTION_ARGS)
+{
+	int			numkeep = PG_GETARG_INT32(0);
+	int			numsnaps = PG_GETARG_INT32(1);
+	int			numiters = PG_GETARG_INT32(2);
+	int			i,
+				j;
+	instr_time	start,
+				duration;
+	Snapshot	lsnap;
+	Snapshot   *rs;
+	int			numregistered = 0;
+
+	rs = palloc(Max(numsnaps, numkeep) * sizeof(Snapshot));
+
+	lsnap = GetLatestSnapshot();
+
+	PG_SETMASK(&BlockSig);
+	INSTR_TIME_SET_CURRENT(start);
+
+	while (numregistered < numkeep)
+	{
+		rs[numregistered] = RegisterSnapshot(lsnap);
+		numregistered++;
+	}
+
+	for (i = 0 ; i < numiters; i++)
+	{
+		while (numregistered < numsnaps)
+		{
+			rs[numregistered] = RegisterSnapshot(lsnap);
+			numregistered++;
+		}
+
+		for (j = numkeep; j < numregistered; j++)
+			UnregisterSnapshot(rs[j]);
+		numregistered = numkeep;
+	}
+
+	for (j = 0; j < numregistered; j++)
+		UnregisterSnapshot(rs[j]);
+	numregistered = numkeep;
+
+	INSTR_TIME_SET_CURRENT(duration);
+	INSTR_TIME_SUBTRACT(duration, start);
+	PG_SETMASK(&UnBlockSig);
+
+	PG_RETURN_FLOAT8(INSTR_TIME_GET_MILLISEC(duration));
+};
diff --git a/contrib/resownerbench/resownerbench.control b/contrib/resownerbench/resownerbench.control
new file mode 100644
index 00000000000..ada88b8eed8
--- /dev/null
+++ b/contrib/resownerbench/resownerbench.control
@@ -0,0 +1,6 @@
+# resownerbench
+
+comment = 'benchmark for ResourceOwners'
+default_version = '1.0'
+module_pathname = '$libdir/resownerbench'
+relocatable = true
diff --git a/contrib/resownerbench/snaptest.sql b/contrib/resownerbench/snaptest.sql
new file mode 100644
index 00000000000..18c54e13fc9
--- /dev/null
+++ b/contrib/resownerbench/snaptest.sql
@@ -0,0 +1,37 @@
+--
+-- Performance test RegisterSnapshot/UnregisterSnapshot.
+--
+select numkeep, numsnaps,
+       -- numiters,
+       -- round(lifo_time_ms) as lifo_total_time_ms,
+       -- round(fifo_time_ms) as fifo_total_time_ms,
+       round((lifo_time_ms::numeric / (numkeep + (numsnaps - numkeep) * numiters)) * 1000000, 1) as lifo_time_ns,
+       round((fifo_time_ms::numeric / (numkeep + (numsnaps - numkeep) * numiters)) * 1000000, 1) as fifo_time_ns
+from
+(values (0,      1,  10000000),
+        (0,      5,   2000000),
+        (0,     10,   1000000),
+        (0,     60,    100000),
+        (0,     70,    100000),
+        (0,    100,    100000),
+        (0,   1000,     10000),
+        (0,  10000,      1000),
+
+-- These tests keep 9 snapshots registered across the iterations. That
+-- exceeds the size of the little array in the patch, so this exercises
+-- the hash lookups. Without the patch, these still fit in the array
+-- (it's 64 entries without the patch)
+        (9,     10,  10000000),
+        (9,    100,    100000),
+        (9,   1000,     10000),
+        (9,  10000,      1000),
+
+-- These exceed the 64 entry array even without the patch, so these fall
+-- in the hash table regime with and without the patch.
+        (65,    70,   1000000),
+        (65,   100,    100000),
+        (65,  1000,     10000),
+        (65, 10000,      1000)
+) AS params (numkeep, numsnaps, numiters),
+lateral snapshotbench_lifo(numkeep, numsnaps, numiters) as lifo_time_ms,
+lateral snapshotbench_fifo(numkeep, numsnaps, numiters) as fifo_time_ms;
-- 
2.29.2

v5-0001-Move-a-few-ResourceOwnerEnlarge-calls-for-safety-.patchtext/x-patch; charset=UTF-8; name=v5-0001-Move-a-few-ResourceOwnerEnlarge-calls-for-safety-.patchDownload
From ca5ef92d30d6d2d77e6758da3ae60d075451d5c1 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 13 Jan 2021 12:21:28 +0200
Subject: [PATCH v5 1/2] Move a few ResourceOwnerEnlarge() calls for safety and
 clarity.

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
inbetween, 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.
---
 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 561c212092f..cde869e7d64 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -738,9 +738,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,
@@ -1093,9 +1090,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 resoure owner has room to remember the
+		 * pin.
 		 */
 		ReservePrivateRefCountEntry();
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
 
 		/*
 		 * Select a victim buffer.  The buffer is returned with its header
@@ -1595,8 +1594,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.
  */
@@ -1607,6 +1604,8 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
 	bool		result;
 	PrivateRefCountEntry *ref;
 
+	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+
 	ref = GetPrivateRefCountEntry(b, true);
 
 	if (ref == NULL)
@@ -1687,7 +1686,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.
@@ -1857,9 +1857,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
@@ -2334,9 +2331,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;
@@ -2418,8 +2412,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)
@@ -2429,7 +2421,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.
@@ -3487,9 +3481,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;
@@ -3503,7 +3494,9 @@ FlushRelationBuffers(Relation rel)
 		if (!RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node))
 			continue;
 
+		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
@@ -3560,9 +3553,6 @@ FlushRelationsAllBuffers(SMgrRelation *smgrs, int nrels)
 	if (use_bsearch)
 		pg_qsort(srels, nrels, sizeof(SMgrSortArray), rnode_comparator);
 
-	/* Make sure we can handle the pin inside the loop */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
-
 	for (i = 0; i < NBuffers; i++)
 	{
 		SMgrSortArray *srelent = NULL;
@@ -3599,7 +3589,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 (RelFileNodeEquals(bufHdr->tag.rnode, srelent->rnode) &&
@@ -3639,9 +3631,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;
@@ -3655,7 +3644,9 @@ FlushDatabaseBuffers(Oid dbid)
 		if (bufHdr->tag.rnode.dbNode != dbid)
 			continue;
 
+		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (bufHdr->tag.rnode.dbNode == dbid &&
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index 04b3558ea33..f7c15ea8a44 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -123,6 +123,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, (void *) &newTag, HASH_FIND, NULL);
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index fa2b49c676e..86fdfc454d2 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1607,8 +1607,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();
@@ -1696,13 +1694,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.29.2

#19Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Heikki Linnakangas (#18)
Re: ResourceOwner refactoring

On 2021-Jan-18, Heikki Linnakangas wrote:

+static ResourceOwnerFuncs jit_funcs =
+{
+	/* relcache references */
+	.name = "LLVM JIT context",
+	.phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.ReleaseResource = ResOwnerReleaseJitContext,
+	.PrintLeakWarning = ResOwnerPrintJitContextLeakWarning
+};

I think you mean jit_resowner_funcs here; "jit_funcs" is a bit
excessively vague. Also, why did you choose not to define
ResourceOwnerRememberJIT? You do that in other modules and it seems
better.

+static ResourceOwnerFuncs catcache_funcs =
+{
+	/* catcache references */
+	.name = "catcache reference",
+	.phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.ReleaseResource = ResOwnerReleaseCatCache,
+	.PrintLeakWarning = ResOwnerPrintCatCacheLeakWarning
+};
+
+static ResourceOwnerFuncs catlistref_funcs =
+{
+	/* catcache-list pins */
+	.name = "catcache list reference",
+	.phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.ReleaseResource = ResOwnerReleaseCatCacheList,
+	.PrintLeakWarning = ResOwnerPrintCatCacheListLeakWarning
+};

Similar naming issue here, I say.

diff --git a/src/common/cryptohash_openssl.c b/src/common/cryptohash_openssl.c
index 551ec392b60..642e72d8c0f 100644
--- a/src/common/cryptohash_openssl.c
+++ b/src/common/cryptohash_openssl.c
@@ -60,6 +59,21 @@ struct pg_cryptohash_ctx
#endif
};
+/* ResourceOwner callbacks to hold JitContexts  */
+#ifndef FRONTEND
+static void ResOwnerReleaseCryptoHash(Datum res);
+static void ResOwnerPrintCryptoHashLeakWarning(Datum res);

The comment is wrong -- "Crypohashes", not "JitContexts".

So according to your performance benchmark, we're willing to accept a
30% performance loss on an allegedly common operation -- numkeep=0
numsnaps=10 becomes 49.8ns from 37.6ns. That seems a bit shocking.
Maybe you can claim that these operations aren't exactly hot spots, and
so the fact that we remain in the same power-of-ten is sufficient. Is
that the argument?

--
�lvaro Herrera 39�49'30"S 73�17'W
"The Postgresql hackers have what I call a "NASA space shot" mentality.
Quite refreshing in a world of "weekend drag racer" developers."
(Scott Marlowe)

#20Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Alvaro Herrera (#19)
Re: ResourceOwner refactoring

On 18/01/2021 16:34, Alvaro Herrera wrote:

So according to your performance benchmark, we're willing to accept a
30% performance loss on an allegedly common operation -- numkeep=0
numsnaps=10 becomes 49.8ns from 37.6ns. That seems a bit shocking.
Maybe you can claim that these operations aren't exactly hot spots, and
so the fact that we remain in the same power-of-ten is sufficient. Is
that the argument?

That's right. The fast path is fast, and that's important. The slow path
becomes 30% slower, but that's acceptable.

- Heikki

#21Robert Haas
robertmhaas@gmail.com
In reply to: Heikki Linnakangas (#20)
Re: ResourceOwner refactoring

On Mon, Jan 18, 2021 at 10:19 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

On 18/01/2021 16:34, Alvaro Herrera wrote:

So according to your performance benchmark, we're willing to accept a
30% performance loss on an allegedly common operation -- numkeep=0
numsnaps=10 becomes 49.8ns from 37.6ns. That seems a bit shocking.
Maybe you can claim that these operations aren't exactly hot spots, and
so the fact that we remain in the same power-of-ten is sufficient. Is
that the argument?

That's right. The fast path is fast, and that's important. The slow path
becomes 30% slower, but that's acceptable.

- Heikki

--
Robert Haas
EDB: http://www.enterprisedb.com

#22Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#21)
Re: ResourceOwner refactoring

On Mon, Jan 18, 2021 at 11:11 AM Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Jan 18, 2021 at 10:19 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

On 18/01/2021 16:34, Alvaro Herrera wrote:

So according to your performance benchmark, we're willing to accept a
30% performance loss on an allegedly common operation -- numkeep=0
numsnaps=10 becomes 49.8ns from 37.6ns. That seems a bit shocking.
Maybe you can claim that these operations aren't exactly hot spots, and
so the fact that we remain in the same power-of-ten is sufficient. Is
that the argument?

That's right. The fast path is fast, and that's important. The slow path
becomes 30% slower, but that's acceptable.

Sorry for the empty message.

I don't know whether a 30% slowdown will hurt anybody, but it seems
like kind of a lot, and I'm not sure I understand what corresponding
benefit we're getting.

--
Robert Haas
EDB: http://www.enterprisedb.com

#23Michael Paquier
michael@paquier.xyz
In reply to: Heikki Linnakangas (#18)
Re: ResourceOwner refactoring

On Mon, Jan 18, 2021 at 02:22:33PM +0200, Heikki Linnakangas wrote:

diff --git a/src/common/cryptohash_openssl.c b/src/common/cryptohash_openssl.c
index 551ec392b60..642e72d8c0f 100644
--- a/src/common/cryptohash_openssl.c
+++ b/src/common/cryptohash_openssl.c

[...]

+/* ResourceOwner callbacks to hold JitContexts */

Slight copy-paste error here.

/*
* Ensure, while the spinlock's not yet held, that there's a free
-	 * refcount entry.
+	 * refcount entry and that the resoure owner has room to remember the
+	 * pin.

s/resoure/resource/.
--
Michael

#24Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Robert Haas (#22)
Re: ResourceOwner refactoring

On 18/01/2021 18:11, Robert Haas wrote:

On Mon, Jan 18, 2021 at 11:11 AM Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Jan 18, 2021 at 10:19 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

On 18/01/2021 16:34, Alvaro Herrera wrote:

So according to your performance benchmark, we're willing to accept a
30% performance loss on an allegedly common operation -- numkeep=0
numsnaps=10 becomes 49.8ns from 37.6ns. That seems a bit shocking.
Maybe you can claim that these operations aren't exactly hot spots, and
so the fact that we remain in the same power-of-ten is sufficient. Is
that the argument?

That's right. The fast path is fast, and that's important. The slow path
becomes 30% slower, but that's acceptable.

I don't know whether a 30% slowdown will hurt anybody, but it seems
like kind of a lot, and I'm not sure I understand what corresponding
benefit we're getting.

The benefit is to make it easy for extensions to use resource owners to
track whatever resources they need to track. And arguably, the new
mechanism is nicer for built-in code, too.

I'll see if I can optimize the slow paths, to make it more palatable.

- Heikki

#25Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Alvaro Herrera (#19)
Re: ResourceOwner refactoring

On 18/01/2021 16:34, Alvaro Herrera wrote:

On 2021-Jan-18, Heikki Linnakangas wrote:

+static ResourceOwnerFuncs jit_funcs =
+{
+	/* relcache references */
+	.name = "LLVM JIT context",
+	.phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.ReleaseResource = ResOwnerReleaseJitContext,
+	.PrintLeakWarning = ResOwnerPrintJitContextLeakWarning
+};

I think you mean jit_resowner_funcs here; "jit_funcs" is a bit
excessively vague. Also, why did you choose not to define
ResourceOwnerRememberJIT? You do that in other modules and it seems
better.

I did it in modules that had more than one ResourceOwnerRemeber/Forget
call. Didn't seem worth it in functions like IncrTupleDescRefCount(),
for example.

Hayato Kuroda also pointed that out, though. So perhaps it's better to
be consistent, to avoid the confusion. I'll add the missing wrappers.

- Heikki

#26Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Heikki Linnakangas (#24)
4 attachment(s)
Re: ResourceOwner refactoring

On 19/01/2021 11:09, Heikki Linnakangas wrote:

On 18/01/2021 18:11, Robert Haas wrote:

On Mon, Jan 18, 2021 at 11:11 AM Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Jan 18, 2021 at 10:19 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

On 18/01/2021 16:34, Alvaro Herrera wrote:

So according to your performance benchmark, we're willing to accept a
30% performance loss on an allegedly common operation -- numkeep=0
numsnaps=10 becomes 49.8ns from 37.6ns. That seems a bit shocking.
Maybe you can claim that these operations aren't exactly hot spots, and
so the fact that we remain in the same power-of-ten is sufficient. Is
that the argument?

That's right. The fast path is fast, and that's important. The slow path
becomes 30% slower, but that's acceptable.

I don't know whether a 30% slowdown will hurt anybody, but it seems
like kind of a lot, and I'm not sure I understand what corresponding
benefit we're getting.

The benefit is to make it easy for extensions to use resource owners to
track whatever resources they need to track. And arguably, the new
mechanism is nicer for built-in code, too.

I'll see if I can optimize the slow paths, to make it more palatable.

Ok, here's a new set of patches, and new test results. I replaced the
hash function with a cheaper one. I also added the missing wrappers that
Alvaro and Hayato Kuroda commented on, and fixed the typos that Michael
Paquier pointed out.

In the test script, I increased the number of iterations used in the
tests, to make them run longer and produce more stable results. There is
still a fair amount of jitter in the results, so take any particular
number with a grain of salt, but the overall trend is repeatable.

The results now look like this:

Unpatched
---------

postgres=# \i snaptest.sql
numkeep | numsnaps | lifo_time_ns | fifo_time_ns
---------+----------+--------------+--------------
0 | 1 | 32.7 | 32.3
0 | 5 | 33.0 | 32.9
0 | 10 | 34.1 | 35.5
0 | 60 | 32.5 | 38.3
0 | 70 | 47.6 | 48.9
0 | 100 | 47.9 | 49.3
0 | 1000 | 52.9 | 52.7
0 | 10000 | 61.7 | 62.4
9 | 10 | 38.4 | 37.6
9 | 100 | 42.3 | 42.3
9 | 1000 | 43.9 | 45.0
9 | 10000 | 62.2 | 62.5
65 | 70 | 42.4 | 42.9
65 | 100 | 43.2 | 43.9
65 | 1000 | 44.0 | 45.1
65 | 10000 | 62.4 | 62.6
(16 rows)

Patched
-------

postgres=# \i snaptest.sql
numkeep | numsnaps | lifo_time_ns | fifo_time_ns
---------+----------+--------------+--------------
0 | 1 | 32.8 | 34.2
0 | 5 | 33.8 | 32.2
0 | 10 | 37.2 | 40.2
0 | 60 | 40.2 | 45.1
0 | 70 | 40.9 | 48.4
0 | 100 | 41.9 | 45.2
0 | 1000 | 49.0 | 55.6
0 | 10000 | 62.4 | 67.2
9 | 10 | 33.6 | 33.0
9 | 100 | 39.6 | 39.7
9 | 1000 | 42.2 | 45.0
9 | 10000 | 60.7 | 63.9
65 | 70 | 32.5 | 33.6
65 | 100 | 37.5 | 39.7
65 | 1000 | 42.3 | 46.3
65 | 10000 | 61.9 | 64.8
(16 rows)

For easier side-by-side comparison, here are the same numbers with the
patched and unpatched results side by side, and their ratio (ratio < 1
means the patched version is faster):

LIFO tests:
numkeep | numsnaps | unpatched | patched | ratio
---------+----------+-----------+---------+-------
0 | 1 | 32.7 | 32.8 | 1.00
0 | 5 | 33.0 | 33.8 | 1.02
0 | 10 | 34.1 | 37.2 | 1.09
0 | 60 | 32.5 | 40.2 | 1.24
0 | 70 | 47.6 | 40.9 | 0.86
0 | 100 | 47.9 | 41.9 | 0.87
0 | 1000 | 52.9 | 49.0 | 0.93
0 | 10000 | 61.7 | 62.4 | 1.01
9 | 10 | 38.4 | 33.6 | 0.88
9 | 100 | 42.3 | 39.6 | 0.94
9 | 1000 | 43.9 | 42.2 | 0.96
9 | 10000 | 62.2 | 60.7 | 0.98
65 | 70 | 42.4 | 32.5 | 0.77
65 | 100 | 43.2 | 37.5 | 0.87
65 | 1000 | 44.0 | 42.3 | 0.96
65 | 10000 | 62.4 | 61.9 | 0.99
(16 rows)

FIFO tests:
numkeep | numsnaps | unpatched | patched | ratio
---------+----------+-----------+---------+-------
0 | 1 | 32.3 | 34.2 | 1.06
0 | 5 | 32.9 | 32.2 | 0.98
0 | 10 | 35.5 | 40.2 | 1.13
0 | 60 | 38.3 | 45.1 | 1.18
0 | 70 | 48.9 | 48.4 | 0.99
0 | 100 | 49.3 | 45.2 | 0.92
0 | 1000 | 52.7 | 55.6 | 1.06
0 | 10000 | 62.4 | 67.2 | 1.08
9 | 10 | 37.6 | 33.0 | 0.88
9 | 100 | 42.3 | 39.7 | 0.94
9 | 1000 | 45.0 | 45.0 | 1.00
9 | 10000 | 62.5 | 63.9 | 1.02
65 | 70 | 42.9 | 33.6 | 0.78
65 | 100 | 43.9 | 39.7 | 0.90
65 | 1000 | 45.1 | 46.3 | 1.03
65 | 10000 | 62.6 | 64.8 | 1.04
(16 rows)

Summary: In the the worst scenario, the patched version is still 24%
slower than unpatched. But many other scenarios are now faster with the
patch.

- Heikki

Attachments:

v6-0001-Move-a-few-ResourceOwnerEnlarge-calls-for-safety-.patchtext/x-patch; charset=UTF-8; name=v6-0001-Move-a-few-ResourceOwnerEnlarge-calls-for-safety-.patchDownload
From 3cea7f1118b400ab2d613a6bd2054d3c8e67dc02 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 13 Jan 2021 12:21:28 +0200
Subject: [PATCH v6 1/3] Move a few ResourceOwnerEnlarge() calls for safety and
 clarity.

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
inbetween, 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.
---
 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 561c212092f..cde869e7d64 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -738,9 +738,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,
@@ -1093,9 +1090,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 resoure owner has room to remember the
+		 * pin.
 		 */
 		ReservePrivateRefCountEntry();
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
 
 		/*
 		 * Select a victim buffer.  The buffer is returned with its header
@@ -1595,8 +1594,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.
  */
@@ -1607,6 +1604,8 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
 	bool		result;
 	PrivateRefCountEntry *ref;
 
+	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+
 	ref = GetPrivateRefCountEntry(b, true);
 
 	if (ref == NULL)
@@ -1687,7 +1686,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.
@@ -1857,9 +1857,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
@@ -2334,9 +2331,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;
@@ -2418,8 +2412,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)
@@ -2429,7 +2421,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.
@@ -3487,9 +3481,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;
@@ -3503,7 +3494,9 @@ FlushRelationBuffers(Relation rel)
 		if (!RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node))
 			continue;
 
+		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
@@ -3560,9 +3553,6 @@ FlushRelationsAllBuffers(SMgrRelation *smgrs, int nrels)
 	if (use_bsearch)
 		pg_qsort(srels, nrels, sizeof(SMgrSortArray), rnode_comparator);
 
-	/* Make sure we can handle the pin inside the loop */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
-
 	for (i = 0; i < NBuffers; i++)
 	{
 		SMgrSortArray *srelent = NULL;
@@ -3599,7 +3589,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 (RelFileNodeEquals(bufHdr->tag.rnode, srelent->rnode) &&
@@ -3639,9 +3631,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;
@@ -3655,7 +3644,9 @@ FlushDatabaseBuffers(Oid dbid)
 		if (bufHdr->tag.rnode.dbNode != dbid)
 			continue;
 
+		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (bufHdr->tag.rnode.dbNode == dbid &&
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index 04b3558ea33..f7c15ea8a44 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -123,6 +123,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, (void *) &newTag, HASH_FIND, NULL);
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index fa2b49c676e..86fdfc454d2 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1607,8 +1607,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();
@@ -1696,13 +1694,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.29.2

v6-0002-Make-resowners-more-easily-extensible.patchtext/x-patch; charset=UTF-8; name=v6-0002-Make-resowners-more-easily-extensible.patchDownload
From cfedc2c227fb3a1129bfc75b070c3a556cb12308 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 20 Jan 2021 23:16:07 +0200
Subject: [PATCH v6 2/3] Make resowners more easily extensible.

Use a single array and hash, instead of one for each object kind.
---
 src/backend/access/common/tupdesc.c   |   42 +-
 src/backend/jit/jit.c                 |    2 -
 src/backend/jit/llvm/llvmjit.c        |   46 +-
 src/backend/storage/buffer/bufmgr.c   |   45 +-
 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     |   19 +-
 src/backend/utils/resowner/resowner.c | 1236 +++++++------------------
 src/backend/utils/time/snapmgr.c      |   38 +-
 src/common/cryptohash_openssl.c       |   49 +-
 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  |  105 ---
 src/tools/pgindent/typedefs.list      |    4 +-
 21 files changed, 844 insertions(+), 1055 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 902f59440cd..2bf0c121fda 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -29,9 +29,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
@@ -376,7 +394,7 @@ IncrTupleDescRefCount(TupleDesc tupdesc)
 {
 	Assert(tupdesc->tdrefcount >= 0);
 
-	ResourceOwnerEnlargeTupleDescs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	tupdesc->tdrefcount++;
 	ResourceOwnerRememberTupleDesc(CurrentResourceOwner, tupdesc);
 }
@@ -925,3 +943,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 2da300e000d..1aa04d173b4 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 b0789a5fb80..8e5b86a0e5b 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,8 +121,26 @@ static LLVMOrcLLJITRef llvm_create_jit_instance(LLVMTargetMachineRef tm);
 static char *llvm_error_message(LLVMErrorRef error);
 #endif							/* LLVM_VERSION_MAJOR > 11 */
 
-PG_MODULE_MAGIC;
+/* 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;
 
 /*
  * Initialize LLVM JIT provider.
@@ -151,7 +169,7 @@ llvm_create_context(int jitFlags)
 
 	llvm_session_initialize();
 
-	ResourceOwnerEnlargeJIT(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	context = MemoryContextAllocZero(TopMemoryContext,
 									 sizeof(LLVMJitContext));
@@ -159,7 +177,7 @@ llvm_create_context(int jitFlags)
 
 	/* ensure cleanup */
 	context->base.resowner = CurrentResourceOwner;
-	ResourceOwnerRememberJIT(CurrentResourceOwner, PointerGetDatum(context));
+	ResourceOwnerRememberJIT(CurrentResourceOwner, context);
 
 	return context;
 }
@@ -221,6 +239,8 @@ llvm_release_context(JitContext *context)
 
 		pfree(jit_handle);
 	}
+
+	ResourceOwnerForgetJIT(context->resowner, context);
 }
 
 /*
@@ -1231,3 +1251,21 @@ llvm_error_message(LLVMErrorRef error)
 }
 
 #endif							/* LLVM_VERSION_MAJOR > 11 */
+
+/*
+ * ResourceOwner callbacks
+ */
+static void
+ResOwnerReleaseJitContext(Datum res)
+{
+	jit_release_context((JitContext *) PointerGetDatum(res));
+}
+
+static void
+ResOwnerPrintJitContextLeakWarning(Datum res)
+{
+	/* XXX: We used to not print these. Was that intentional? */
+	JitContext *context = (JitContext *) PointerGetDatum(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 cde869e7d64..82e39de19f9 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -52,7 +52,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"
 
 
@@ -206,6 +206,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
@@ -1094,7 +1106,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
@@ -1604,7 +1616,7 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
 	bool		result;
 	PrivateRefCountEntry *ref;
 
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	ref = GetPrivateRefCountEntry(b, true);
 
@@ -1687,7 +1699,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.
@@ -2423,7 +2435,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.
@@ -3496,7 +3508,7 @@ FlushRelationBuffers(Relation rel)
 
 		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
@@ -3591,7 +3603,7 @@ FlushRelationsAllBuffers(SMgrRelation *smgrs, int nrels)
 
 		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (RelFileNodeEquals(bufHdr->tag.rnode, srelent->rnode) &&
@@ -3646,7 +3658,7 @@ FlushDatabaseBuffers(Oid dbid)
 
 		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (bufHdr->tag.rnode.dbNode == dbid &&
@@ -3729,7 +3741,7 @@ void
 IncrBufferRefCount(Buffer buffer)
 {
 	Assert(BufferIsPinned(buffer));
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	if (BufferIsLocal(buffer))
 		LocalRefCount[-buffer - 1]++;
 	else
@@ -4803,3 +4815,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 f7c15ea8a44..3f22d7127b9 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -22,7 +22,7 @@
 #include "storage/bufmgr.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 
 
 /*#define LBDEBUG*/
@@ -124,7 +124,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 b58502837aa..1172ea245e0 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -97,7 +97,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)
@@ -340,6 +340,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
  */
@@ -1430,7 +1448,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
@@ -1612,7 +1630,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
@@ -1742,7 +1760,7 @@ PathNameCreateTemporaryFile(const char *path, bool error_on_failure)
 {
 	File		file;
 
-	ResourceOwnerEnlargeFiles(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	/*
 	 * Open the file.  Note: we don't use O_EXCL, in case there is an orphaned
@@ -1780,7 +1798,7 @@ PathNameOpenTemporaryFile(const char *path, int mode)
 {
 	File		file;
 
-	ResourceOwnerEnlargeFiles(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	file = PathNameOpenFile(path, mode | PG_BINARY);
 
@@ -3700,3 +3718,19 @@ pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
 
 	return sum;
 }
+
+/*
+ * 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 ae82b4bdc0e..31e8860fb53 100644
--- a/src/backend/storage/ipc/dsm.c
+++ b/src/backend/storage/ipc/dsm.c
@@ -37,13 +37,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
 
@@ -139,6 +141,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.
  *
@@ -895,7 +916,7 @@ void
 dsm_unpin_mapping(dsm_segment *seg)
 {
 	Assert(seg->resowner == NULL);
-	ResourceOwnerEnlargeDSMs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	seg->resowner = CurrentResourceOwner;
 	ResourceOwnerRememberDSM(seg->resowner, seg);
 }
@@ -1162,7 +1183,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);
@@ -1241,3 +1262,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 20e50247ea4..de39c844d49 100644
--- a/src/backend/storage/lmgr/lock.c
+++ b/src/backend/storage/lmgr/lock.c
@@ -47,7 +47,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 86fdfc454d2..9f9db426274 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
@@ -1270,7 +1307,7 @@ SearchCatCacheInternal(CatCache *cache,
 		 */
 		if (!ct->negative)
 		{
-			ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner);
+			ResourceOwnerEnlarge(CurrentResourceOwner);
 			ct->refcount++;
 			ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple);
 
@@ -1371,7 +1408,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 */
@@ -1583,7 +1620,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);
 
@@ -1695,7 +1732,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);
@@ -2069,14 +2106,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));
 
@@ -2090,9 +2132,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 cc04b5b4bef..ecc9296d2d3 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;
 
@@ -1229,7 +1249,7 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 
 	/* Flag the plan as in use by caller */
 	if (useResOwner)
-		ResourceOwnerEnlargePlanCacheRefs(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 	plan->refcount++;
 	if (useResOwner)
 		ResourceOwnerRememberPlanCacheRef(CurrentResourceOwner, 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);
 	}
@@ -2205,3 +2225,20 @@ ResetPlanCache(void)
 		cexpr->is_valid = false;
 	}
 }
+
+/*
+ * ResourceOwner callbacks
+ */
+
+static void
+ResOwnerReleaseCachedPlan(Datum res)
+{
+	ReleaseCachedPlan((CachedPlan *) DatumGetPointer(res), true);
+}
+
+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 7ef510cd01b..6ca5b50552b 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -78,13 +78,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"
 
@@ -2078,6 +2079,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.
@@ -2089,7 +2108,7 @@ RelationIdGetRelation(Oid relationId)
 void
 RelationIncrementReferenceCount(Relation rel)
 {
-	ResourceOwnerEnlargeRelationRefs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	rel->rd_refcnt += 1;
 	if (!IsBootstrapProcessingMode())
 		ResourceOwnerRememberRelationRef(CurrentResourceOwner, rel);
@@ -6417,3 +6436,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 2998f6bb362..4ed13afa101 100644
--- a/src/backend/utils/resowner/README
+++ b/src/backend/utils/resowner/README
@@ -60,13 +60,18 @@ subtransaction or portal.  Therefore, the "release" operation on a child
 ResourceOwner transfers lock ownership to the parent instead of actually
 releasing the lock, if isCommit is true.
 
-Currently, ResourceOwners contain direct support for recording ownership of
-buffer pins, lmgr locks, and catcache, relcache, plancache, tupdesc, and
-snapshot references.  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.
+As of this writing, it's used internally for buffer pins, lmgr locks, and
+catcache, relcache, plancache, tupdesc, snapshot, DSM and JIT context
+references. ResourceOwner treats all resources the same, and extensions
+can define new kinds of resources by filling in a ResourceOwnerFuncs
+struct with a 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. This used to be the only way to register new kinds
+of objects with a resource owner; nowadays it easier to write custom
+ResourceOwnerFuncs callbacks.
 
 Whenever we are inside a transaction, the global variable
 CurrentResourceOwner shows which resource owner should be assigned
diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index 10f15f6a357..1db1e7cf14b 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -20,75 +20,45 @@
  */
 #include "postgres.h"
 
-#include "common/cryptohash.h"
 #include "common/hashfn.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;
+} 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 8
 
 /*
- * 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 32
 
 /*
- * 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
@@ -118,23 +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 */
+	/*
+	 * 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;
 
 
 /*****************************************************************************
@@ -146,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
  */
@@ -160,296 +152,315 @@ 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 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);
 
 
 /*****************************************************************************
  *	  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
- */
 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;
+
+	idx = hash_resource_elem(value, kind) & mask;
+	for (;;)
+	{
+		if (owner->hash[idx].kind == NULL)
+			break;
+		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;
-
-	if (resarr->nitems < resarr->maxitems)
-		return;					/* no work needed */
+	bool		found;
+	int			capacity;
 
-	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. */
+	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 */
+	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 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
- *
- * Returns true on success, false if resource was not found.
+ * Make sure there is room for at least one more resource in an array.
  *
- * Note: if same resource ID appears more than once, one instance is removed.
+ * 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.
  */
-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.
+ * Remember that an object is owner by a ReourceOwner
  *
- * "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.
- *
- * 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++;
-		}
-	}
+	Assert(owner->narr < RESOWNER_ARRAY_SIZE);
 
-	*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
+ *
+ * Returns true on success. If the resource was not found, returns false,
+ * and calls kind->ForgetError callback.
+ *
+ * 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));
+		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);
 }
 
 /*
@@ -486,6 +497,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
@@ -497,7 +517,6 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 	ResourceOwner child;
 	ResourceOwner save;
 	ResourceReleaseCallbackItem *item;
-	Datum		foundres;
 
 	/* Recurse to handle descendants */
 	for (child = owner->firstchild; child != NULL; child = child->nextchild)
@@ -513,61 +532,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 references 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 references ---
+		 * 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 *) PointerGetDatum(foundres);
-
-			jit_release_context(context);
-		}
-
-		/* Ditto for cryptohash contexts */
-		while (ResourceArrayGetAny(&(owner->cryptohasharr), &foundres))
-		{
-			pg_cryptohash_ctx *context =
-			(pg_cryptohash_ctx *) PointerGetDatum(foundres);
-
-			if (isCommit)
-				PrintCryptoHashLeakWarning(foundres);
-			pg_cryptohash_free(context);
-		}
+		ResourceOwnerReleaseAll(owner, phase, isCommit);
 	}
 	else if (phase == RESOURCE_RELEASE_LOCKS)
 	{
@@ -576,7 +547,7 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 			/*
 			 * For a top-level xact we are going to release all locks (or at
 			 * least all non-session locks), so just do a single lmgr call at
-			 * the top of the recursion.
+			 * the top of the recursion
 			 */
 			if (owner == TopTransactionResourceOwner)
 			{
@@ -620,70 +591,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 references 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, true);
-		}
-
-		/* 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 */
@@ -704,16 +614,48 @@ void
 ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner)
 {
 	ResourceOwner save;
-	Datum		foundres;
 
 	save = CurrentResourceOwner;
 	CurrentResourceOwner = owner;
-	while (ResourceArrayGetAny(&(owner->planrefarr), &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 'false' because we already removed the entry from the
+			 * resowner
+			 */
+			ReleaseCachedPlan(planref, false);
+		}
+	}
+
+	/* 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);
 
-		ReleaseCachedPlan(res, true);
+			owner->hash[i].item = (Datum) 0;
+			owner->hash[i].kind = NULL;
+			owner->nhash--;
+
+			/*
+			 * pass 'false' because we already removed the entry from the
+			 * resowner
+			 */
+			ReleaseCachedPlan(planref, false);
+		}
 	}
+
 	CurrentResourceOwner = save;
 }
 
@@ -730,19 +672,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->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.
@@ -758,18 +696,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));
-
+	if (owner->hash)
+		pfree(owner->hash);
 	pfree(owner);
 }
 
@@ -827,11 +755,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)
@@ -922,44 +849,6 @@ ReleaseAuxProcessResourcesCallback(int code, Datum arg)
 	ReleaseAuxProcessResources(isCommit);
 }
 
-
-/*
- * Make sure there is room for at least one more entry in a ResourceOwner's
- * buffer 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
-ResourceOwnerEnlargeBuffers(ResourceOwner owner)
-{
-	/* 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
  *
@@ -1011,424 +900,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));
-}
diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c
index ae16c3ed7d6..3ded16f6fc8 100644
--- a/src/backend/utils/time/snapmgr.c
+++ b/src/backend/utils/time/snapmgr.c
@@ -66,7 +66,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"
@@ -174,6 +174,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.
  *
@@ -831,7 +849,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);
 
@@ -2345,3 +2363,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 551ec392b60..6421f1de5cd 100644
--- a/src/common/cryptohash_openssl.c
+++ b/src/common/cryptohash_openssl.c
@@ -27,7 +27,6 @@
 #ifndef FRONTEND
 #include "utils/memutils.h"
 #include "utils/resowner.h"
-#include "utils/resowner_private.h"
 #endif
 
 /*
@@ -60,6 +59,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
+
 /*
  * pg_cryptohash_create
  *
@@ -77,7 +97,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));
@@ -106,8 +126,7 @@ pg_cryptohash_create(pg_cryptohash_type type)
 
 #ifndef FRONTEND
 	ctx->resowner = CurrentResourceOwner;
-	ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
-									PointerGetDatum(ctx));
+	ResourceOwnerRememberCryptoHash(CurrentResourceOwner, ctx);
 #endif
 
 	return ctx;
@@ -207,10 +226,28 @@ 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));
 	FREE(ctx);
 }
+
+
+/*
+ * 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/include/storage/buf_internals.h b/src/include/storage/buf_internals.h
index f6b57829653..6815dc38a16 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -296,6 +296,15 @@ typedef struct CkptSortItem
 
 extern 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 ddc2762eb3f..2bf95c22e84 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 79d96e5ed03..964af79b30d 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 109ac31b248..d59d14c0d01 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 an resource 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 c480a1a24be..00000000000
--- a/src/include/utils/resowner_private.h
+++ /dev/null
@@ -1,105 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * resowner_private.h
- *	  POSTGRES resource owner private definitions.
- *
- * See utils/resowner/README for more info.
- *
- *
- * Portions Copyright (c) 1996-2021, 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);
-
-#endif							/* RESOWNER_PRIVATE_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 943142ced8c..2df8d3cbefa 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2127,8 +2127,10 @@ ReplicationStateOnDisk
 ResTarget
 ReservoirState
 ReservoirStateData
-ResourceArray
+ResourceElem
 ResourceOwner
+ResourceOwnerData
+ResourceOwnerFuncs
 ResourceReleaseCallback
 ResourceReleaseCallbackItem
 ResourceReleasePhase
-- 
2.29.2

v6-0003-Optimize-hash-function.patchtext/x-patch; charset=UTF-8; name=v6-0003-Optimize-hash-function.patchDownload
From ab5b8c6eb4c21dceaf24f2e16405afcc6de5d9b2 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Thu, 21 Jan 2021 00:06:58 +0200
Subject: [PATCH v6 3/3] Optimize hash function

---
 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 1db1e7cf14b..72e4d8a784c 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -163,15 +163,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 resources types 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
 }
 
 static void
diff --git a/src/include/common/hashfn.h b/src/include/common/hashfn.h
index c634cc067a1..009ffbbdd38 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.29.2

v2-0001-resownerbench.patchtext/x-patch; charset=UTF-8; name=v2-0001-resownerbench.patchDownload
From 5735f6b925ea5d25dfb823ba088500ae9172e3b2 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Thu, 12 Nov 2020 00:40:45 +0200
Subject: [PATCH v2 1/1] resownerbench

---
 contrib/resownerbench/Makefile               |  17 ++
 contrib/resownerbench/resownerbench--1.0.sql |  14 ++
 contrib/resownerbench/resownerbench.c        | 154 +++++++++++++++++++
 contrib/resownerbench/resownerbench.control  |   6 +
 contrib/resownerbench/snaptest.sql           |  37 +++++
 5 files changed, 228 insertions(+)
 create mode 100644 contrib/resownerbench/Makefile
 create mode 100644 contrib/resownerbench/resownerbench--1.0.sql
 create mode 100644 contrib/resownerbench/resownerbench.c
 create mode 100644 contrib/resownerbench/resownerbench.control
 create mode 100644 contrib/resownerbench/snaptest.sql

diff --git a/contrib/resownerbench/Makefile b/contrib/resownerbench/Makefile
new file mode 100644
index 00000000000..9b0e1cfee1a
--- /dev/null
+++ b/contrib/resownerbench/Makefile
@@ -0,0 +1,17 @@
+MODULE_big = resownerbench
+OBJS = resownerbench.o
+
+EXTENSION = resownerbench
+DATA = resownerbench--1.0.sql
+PGFILEDESC = "resownerbench - benchmark for ResourceOwners"
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/resownerbench
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/resownerbench/resownerbench--1.0.sql b/contrib/resownerbench/resownerbench--1.0.sql
new file mode 100644
index 00000000000..d29182f5982
--- /dev/null
+++ b/contrib/resownerbench/resownerbench--1.0.sql
@@ -0,0 +1,14 @@
+/* contrib/resownerbench/resownerbench--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION resownerbench" to load this file. \quit
+
+CREATE FUNCTION snapshotbench_lifo(numkeep int, numsnaps int, numiters int)
+RETURNS double precision
+AS 'MODULE_PATHNAME', 'snapshotbench_lifo'
+LANGUAGE C STRICT VOLATILE;
+
+CREATE FUNCTION snapshotbench_fifo(numkeep int, numsnaps int, numiters int)
+RETURNS double precision
+AS 'MODULE_PATHNAME', 'snapshotbench_fifo'
+LANGUAGE C STRICT VOLATILE;
diff --git a/contrib/resownerbench/resownerbench.c b/contrib/resownerbench/resownerbench.c
new file mode 100644
index 00000000000..acfb6c39199
--- /dev/null
+++ b/contrib/resownerbench/resownerbench.c
@@ -0,0 +1,154 @@
+#include "postgres.h"
+
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "executor/spi.h"
+#include "funcapi.h"
+#include "libpq/pqsignal.h"
+#include "utils/catcache.h"
+#include "utils/syscache.h"
+#include "utils/timestamp.h"
+#include "utils/snapmgr.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(snapshotbench_lifo);
+PG_FUNCTION_INFO_V1(snapshotbench_fifo);
+
+/*
+ * ResourceOwner Performance test, using RegisterSnapshot().
+ *
+ * This takes three parameters: numkeep, numsnaps, numiters.
+ *
+ * First, we register 'numkeep' snapshots. They are kept registed
+ * until the end of the test. Then, we repeatedly register and
+ * unregister 'numsnaps - numkeep' additional snapshots, repeating
+ * 'numiters' times. All the register/unregister calls are made in
+ * LIFO order.
+ *
+ * Returns the time spent, in milliseconds.
+ *
+ * The idea is to test the performance of ResourceOwnerRemember()
+ * and ReourceOwnerForget() operations, under different regimes.
+ *
+ * In the old implementation, if 'numsnaps' is small enough, all
+ * the entries fit in the resource owner's small array (it can
+ * hold 64 entries).
+ *
+ * In the new implementation, the array is much smaller, only 8
+ * entries, but it's used together with the hash table so that
+ * we stay in the "array regime" as long as 'numsnaps - numkeep'
+ * is smaller than 8 entries.
+ *
+ * 'numiters' can be adjusted to adjust the overall runtime to be
+ * suitable long.
+ */
+Datum
+snapshotbench_lifo(PG_FUNCTION_ARGS)
+{
+	int			numkeep = PG_GETARG_INT32(0);
+	int			numsnaps = PG_GETARG_INT32(1);
+	int			numiters = PG_GETARG_INT32(2);
+	int			i;
+	instr_time	start,
+				duration;
+	Snapshot	lsnap;
+	Snapshot   *rs;
+	int			numregistered = 0;
+
+	rs = palloc(Max(numsnaps, numkeep) * sizeof(Snapshot));
+
+	lsnap = GetLatestSnapshot();
+
+	PG_SETMASK(&BlockSig);
+	INSTR_TIME_SET_CURRENT(start);
+
+	while (numregistered < numkeep)
+	{
+		rs[numregistered] = RegisterSnapshot(lsnap);
+		numregistered++;
+	}
+
+	for (i = 0 ; i < numiters; i++)
+	{
+		while (numregistered < numsnaps)
+		{
+			rs[numregistered] = RegisterSnapshot(lsnap);
+			numregistered++;
+		}
+
+		while (numregistered > numkeep)
+		{
+			numregistered--;
+			UnregisterSnapshot(rs[numregistered]);
+		}
+	}
+
+	while (numregistered > 0)
+	{
+		numregistered--;
+		UnregisterSnapshot(rs[numregistered]);
+	}
+
+	INSTR_TIME_SET_CURRENT(duration);
+	INSTR_TIME_SUBTRACT(duration, start);
+	PG_SETMASK(&UnBlockSig);
+
+	PG_RETURN_FLOAT8(INSTR_TIME_GET_MILLISEC(duration));
+};
+
+
+/*
+ * Same, but do the register/unregister operations in
+ * FIFO order.
+ */
+Datum
+snapshotbench_fifo(PG_FUNCTION_ARGS)
+{
+	int			numkeep = PG_GETARG_INT32(0);
+	int			numsnaps = PG_GETARG_INT32(1);
+	int			numiters = PG_GETARG_INT32(2);
+	int			i,
+				j;
+	instr_time	start,
+				duration;
+	Snapshot	lsnap;
+	Snapshot   *rs;
+	int			numregistered = 0;
+
+	rs = palloc(Max(numsnaps, numkeep) * sizeof(Snapshot));
+
+	lsnap = GetLatestSnapshot();
+
+	PG_SETMASK(&BlockSig);
+	INSTR_TIME_SET_CURRENT(start);
+
+	while (numregistered < numkeep)
+	{
+		rs[numregistered] = RegisterSnapshot(lsnap);
+		numregistered++;
+	}
+
+	for (i = 0 ; i < numiters; i++)
+	{
+		while (numregistered < numsnaps)
+		{
+			rs[numregistered] = RegisterSnapshot(lsnap);
+			numregistered++;
+		}
+
+		for (j = numkeep; j < numregistered; j++)
+			UnregisterSnapshot(rs[j]);
+		numregistered = numkeep;
+	}
+
+	for (j = 0; j < numregistered; j++)
+		UnregisterSnapshot(rs[j]);
+	numregistered = numkeep;
+
+	INSTR_TIME_SET_CURRENT(duration);
+	INSTR_TIME_SUBTRACT(duration, start);
+	PG_SETMASK(&UnBlockSig);
+
+	PG_RETURN_FLOAT8(INSTR_TIME_GET_MILLISEC(duration));
+};
diff --git a/contrib/resownerbench/resownerbench.control b/contrib/resownerbench/resownerbench.control
new file mode 100644
index 00000000000..ada88b8eed8
--- /dev/null
+++ b/contrib/resownerbench/resownerbench.control
@@ -0,0 +1,6 @@
+# resownerbench
+
+comment = 'benchmark for ResourceOwners'
+default_version = '1.0'
+module_pathname = '$libdir/resownerbench'
+relocatable = true
diff --git a/contrib/resownerbench/snaptest.sql b/contrib/resownerbench/snaptest.sql
new file mode 100644
index 00000000000..e3c0a9710ee
--- /dev/null
+++ b/contrib/resownerbench/snaptest.sql
@@ -0,0 +1,37 @@
+--
+-- Performance test RegisterSnapshot/UnregisterSnapshot.
+--
+select numkeep, numsnaps,
+       -- numiters,
+       -- round(lifo_time_ms) as lifo_total_time_ms,
+       -- round(fifo_time_ms) as fifo_total_time_ms,
+       round((lifo_time_ms::numeric / (numkeep + (numsnaps - numkeep) * numiters)) * 1000000, 1) as lifo_time_ns,
+       round((fifo_time_ms::numeric / (numkeep + (numsnaps - numkeep) * numiters)) * 1000000, 1) as fifo_time_ns
+from
+(values (0,      1,  10000000 * 10),
+        (0,      5,   2000000 * 10),
+        (0,     10,   1000000 * 10),
+        (0,     60,    100000 * 10),
+        (0,     70,    100000 * 10),
+        (0,    100,    100000 * 10),
+        (0,   1000,     10000 * 10),
+        (0,  10000,      1000 * 10),
+
+-- These tests keep 9 snapshots registered across the iterations. That
+-- exceeds the size of the little array in the patch, so this exercises
+-- the hash lookups. Without the patch, these still fit in the array
+-- (it's 64 entries without the patch)
+        (9,     10,  10000000 * 10),
+        (9,    100,    100000 * 10),
+        (9,   1000,     10000 * 10),
+        (9,  10000,      1000 * 10),
+
+-- These exceed the 64 entry array even without the patch, so these fall
+-- in the hash table regime with and without the patch.
+        (65,    70,   1000000 * 10),
+        (65,   100,    100000 * 10),
+        (65,  1000,     10000 * 10),
+        (65, 10000,      1000 * 10)
+) AS params (numkeep, numsnaps, numiters),
+lateral snapshotbench_lifo(numkeep, numsnaps, numiters) as lifo_time_ms,
+lateral snapshotbench_fifo(numkeep, numsnaps, numiters) as fifo_time_ms;
-- 
2.29.2

#27kuroda.hayato@fujitsu.com
kuroda.hayato@fujitsu.com
In reply to: Heikki Linnakangas (#26)
RE: ResourceOwner refactoring

Dear Heikki,

I tested in the same situation, and I confirmed that almost same results are returned.
(results are at the end of the e-mail)

You think that these results are acceptable
because resowners own many resources(more than 64) in general
and it's mainly faster in such a situation, isn't it?

I cannot distinguish correctly, but sounds good.

====test result====

unpatched

numkeep | numsnaps | lifo_time_ns | fifo_time_ns
---------+----------+--------------+--------------
0 | 1 | 68.5 | 69.9
0 | 5 | 73.2 | 76.8
0 | 10 | 70.6 | 74.7
0 | 60 | 68.0 | 75.6
0 | 70 | 91.3 | 94.8
0 | 100 | 89.0 | 89.1
0 | 1000 | 97.9 | 98.9
0 | 10000 | 116.0 | 115.9
9 | 10 | 74.7 | 76.6
9 | 100 | 80.8 | 80.1
9 | 1000 | 86.0 | 86.2
9 | 10000 | 116.1 | 116.8
65 | 70 | 84.7 | 85.3
65 | 100 | 80.5 | 80.3
65 | 1000 | 86.3 | 86.2
65 | 10000 | 115.4 | 115.9
(16 rows)

patched

numkeep | numsnaps | lifo_time_ns | fifo_time_ns
---------+----------+--------------+--------------
0 | 1 | 62.4 | 62.6
0 | 5 | 68.0 | 66.9
0 | 10 | 73.6 | 78.1
0 | 60 | 82.3 | 87.2
0 | 70 | 83.0 | 89.1
0 | 100 | 82.8 | 87.9
0 | 1000 | 88.2 | 96.6
0 | 10000 | 119.6 | 124.5
9 | 10 | 62.0 | 62.8
9 | 100 | 75.3 | 78.0
9 | 1000 | 82.6 | 89.3
9 | 10000 | 116.6 | 122.6
65 | 70 | 66.7 | 66.4
65 | 100 | 74.6 | 77.2
65 | 1000 | 82.1 | 88.2
65 | 10000 | 118.0 | 124.1
(16 rows)

Best Regards,
Hayato Kuroda
FUJITSU LIMITED

#28Michael Paquier
michael@paquier.xyz
In reply to: Heikki Linnakangas (#26)
Re: ResourceOwner refactoring

On Thu, Jan 21, 2021 at 12:11:37AM +0200, Heikki Linnakangas wrote:

Summary: In the the worst scenario, the patched version is still 24% slower
than unpatched. But many other scenarios are now faster with the patch.

Is there a reason explaining the sudden drop for numsnaps within the
[10,60] range? The gap looks deeper with a low numkeep.
--
Michael

#29Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Michael Paquier (#28)
Re: ResourceOwner refactoring

On 21/01/2021 06:14, Michael Paquier wrote:

On Thu, Jan 21, 2021 at 12:11:37AM +0200, Heikki Linnakangas wrote:

Summary: In the the worst scenario, the patched version is still 24% slower
than unpatched. But many other scenarios are now faster with the patch.

Is there a reason explaining the sudden drop for numsnaps within the
[10,60] range? The gap looks deeper with a low numkeep.

It's the switch from array to hash table. With the patch, the array
holds 8 entries. Without the patch, it's 64 entries. So you see a drop
around those points. I added more data points in that range to get a
better picture:

LIFO tests:
numkeep | numsnaps | unpatched | patched | ratio
---------+----------+-----------+---------+-------
0 | 1 | 32.8 | 32.9 | 1.00
0 | 2 | 31.6 | 32.8 | 1.04
0 | 4 | 32.7 | 32.0 | 0.98
0 | 6 | 34.1 | 33.9 | 0.99
0 | 8 | 35.1 | 32.4 | 0.92
0 | 10 | 34.0 | 37.1 | 1.09
0 | 15 | 33.1 | 35.9 | 1.08
0 | 20 | 33.0 | 38.8 | 1.18
0 | 25 | 32.9 | 42.3 | 1.29
0 | 30 | 32.9 | 40.5 | 1.23
0 | 35 | 33.1 | 39.9 | 1.21
0 | 40 | 33.0 | 39.0 | 1.18
0 | 45 | 35.3 | 41.1 | 1.16
0 | 50 | 33.0 | 40.8 | 1.24
0 | 55 | 32.8 | 40.6 | 1.24
0 | 58 | 33.0 | 41.5 | 1.26
0 | 60 | 33.1 | 41.6 | 1.26
0 | 62 | 32.8 | 41.7 | 1.27
0 | 64 | 46.8 | 40.9 | 0.87
0 | 66 | 47.0 | 42.5 | 0.90
0 | 68 | 47.1 | 41.8 | 0.89
0 | 70 | 47.8 | 41.7 | 0.87
(22 rows)

FIFO tests:
numkeep | numsnaps | unpatched | patched | ratio
---------+----------+-----------+---------+-------
0 | 1 | 32.3 | 32.1 | 0.99
0 | 2 | 33.4 | 31.6 | 0.95
0 | 4 | 34.0 | 31.4 | 0.92
0 | 6 | 35.4 | 33.2 | 0.94
0 | 8 | 34.8 | 31.9 | 0.92
0 | 10 | 35.4 | 40.2 | 1.14
0 | 15 | 35.4 | 40.3 | 1.14
0 | 20 | 35.6 | 43.8 | 1.23
0 | 25 | 35.4 | 42.4 | 1.20
0 | 30 | 36.0 | 43.3 | 1.20
0 | 35 | 36.4 | 45.1 | 1.24
0 | 40 | 36.9 | 46.6 | 1.26
0 | 45 | 37.7 | 45.3 | 1.20
0 | 50 | 37.2 | 43.9 | 1.18
0 | 55 | 38.4 | 46.8 | 1.22
0 | 58 | 37.6 | 45.0 | 1.20
0 | 60 | 37.7 | 46.6 | 1.24
0 | 62 | 38.4 | 46.5 | 1.21
0 | 64 | 48.7 | 47.6 | 0.98
0 | 66 | 48.2 | 45.8 | 0.95
0 | 68 | 48.5 | 47.5 | 0.98
0 | 70 | 48.4 | 47.3 | 0.98
(22 rows)

Let's recap the behavior:

Without patch
-------------

For each different kind of resource, there's an array that holds up to
64 entries. In ResourceOwnerForget(), the array is scanned linearly. If
the array fills up, we replace the array with a hash table. After
switching, all operations use the hash table.

With patch
----------

There is one array that holds up to 8 entries. It is shared by all
resources. In ResourceOwnerForget(), the array is always scanned.

If the array fills up, all the entries are moved to a hash table,
freeing up space in the array, and the new entry is added to the array.
So the array is used together with the hash table, like an LRU cache of
the most recently remembered entries.

Why this change? I was afraid that now that all different resources
share the same data structure, remembering e.g. a lot of locks at the
beginning of a transaction would cause the switch to the hash table,
making all subsequent remember/forget operations, like for buffer pins,
slower. That kind of interference seems bad. By continuing to use the
array for the recently-remembered entries, we avoid that problem. The
[numkeep, numsnaps] = [65, 70] test is in that regime, and the patched
version was significantly faster.

Because the array is now always scanned, I felt that it needs to be
small, to avoid wasting much time scanning for entries that have already
been moved to the hash table. That's why I made it just 8 entries.

Perhaps 8 entries is too small, after all? Linearly scanning an array is
very fast. To test that, I bumped up RESOWNER_ARRAY_SIZE to 64, and ran
the test again:

numkeep | numsnaps | lifo_time_ns | fifo_time_ns
---------+----------+--------------+--------------
0 | 1 | 35.4 | 31.5
0 | 2 | 32.3 | 32.3
0 | 4 | 32.8 | 31.0
0 | 6 | 34.5 | 33.7
0 | 8 | 33.9 | 32.7
0 | 10 | 33.7 | 33.0
0 | 15 | 34.8 | 33.1
0 | 20 | 35.0 | 32.6
0 | 25 | 36.9 | 33.0
0 | 30 | 38.7 | 33.2
0 | 35 | 39.9 | 34.5
0 | 40 | 40.8 | 35.5
0 | 45 | 42.6 | 36.4
0 | 50 | 44.9 | 37.8
0 | 55 | 45.4 | 38.5
0 | 58 | 47.7 | 39.6
0 | 60 | 45.9 | 40.2
0 | 62 | 47.6 | 40.9
0 | 64 | 51.4 | 41.2
0 | 66 | 38.2 | 39.8
0 | 68 | 39.7 | 40.3
0 | 70 | 38.1 | 40.9
(22 rows)

Here you can see that as numsnaps increases, the test becomes slower,
but then it becomes faster again at 64-66, when it switches to the hash
table. So 64 seems too much. 32 seems to be the sweet spot here, that's
where scanning the hash and scanning the array are about the same speed.

- Heikki

#30Robert Haas
robertmhaas@gmail.com
In reply to: Heikki Linnakangas (#29)
Re: ResourceOwner refactoring

On Thu, Jan 21, 2021 at 5:14 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

Here you can see that as numsnaps increases, the test becomes slower,
but then it becomes faster again at 64-66, when it switches to the hash
table. So 64 seems too much. 32 seems to be the sweet spot here, that's
where scanning the hash and scanning the array are about the same speed.

That sounds good. I mean, it could be that not all hardware behaves
the same here. But trying to get it into the right ballpark makes
sense.

I also like the fact that this now has some cases where it wins by a
significant margin. That's pretty cool; thanks for working on it!

--
Robert Haas
EDB: http://www.enterprisedb.com

#31Ibrar Ahmed
ibrar.ahmad@gmail.com
In reply to: Robert Haas (#30)
Re: ResourceOwner refactoring

On Mon, Jan 25, 2021 at 10:15 PM Robert Haas <robertmhaas@gmail.com> wrote:

On Thu, Jan 21, 2021 at 5:14 AM Heikki Linnakangas <hlinnaka@iki.fi>
wrote:

Here you can see that as numsnaps increases, the test becomes slower,
but then it becomes faster again at 64-66, when it switches to the hash
table. So 64 seems too much. 32 seems to be the sweet spot here, that's
where scanning the hash and scanning the array are about the same speed.

That sounds good. I mean, it could be that not all hardware behaves
the same here. But trying to get it into the right ballpark makes
sense.

I also like the fact that this now has some cases where it wins by a
significant margin. That's pretty cool; thanks for working on it!

--
Robert Haas
EDB: http://www.enterprisedb.com

The patchset does not apply successfully, there are some hunk failures.

http://cfbot.cputube.org/patch_32_2834.log

v6-0002-Make-resowners-more-easily-extensible.patch

1 out of 6 hunks FAILED -- saving rejects to file
src/backend/utils/cache/plancache.c.rej
2 out of 15 hunks FAILED -- saving rejects to file
src/backend/utils/resowner/resowner.c.rej

Can we get a rebase?

I am marking the patch "Waiting on Author"

--
Ibrar Ahmed

#32Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Ibrar Ahmed (#31)
3 attachment(s)
Re: ResourceOwner refactoring

On 08/03/2021 18:47, Ibrar Ahmed wrote:

The patchset does not apply successfully, there are some hunk failures.

http://cfbot.cputube.org/patch_32_2834.log
<http://cfbot.cputube.org/patch_32_2834.log&gt;

v6-0002-Make-resowners-more-easily-extensible.patch

1 out of 6 hunks FAILED -- saving rejects to file
src/backend/utils/cache/plancache.c.rej
2 out of 15 hunks FAILED -- saving rejects to file
src/backend/utils/resowner/resowner.c.rej

Can we get a rebase?

Here you go.

- Heikki

Attachments:

v7-0001-Move-a-few-ResourceOwnerEnlarge-calls-for-safety-.patchtext/x-patch; charset=UTF-8; name=v7-0001-Move-a-few-ResourceOwnerEnlarge-calls-for-safety-.patchDownload
From 8b852723212c51d3329267fcbf8e7a28f49dbbdd Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 13 Jan 2021 12:21:28 +0200
Subject: [PATCH v7 1/3] Move a few ResourceOwnerEnlarge() calls for safety and
 clarity.

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
inbetween, 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.
---
 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 561c212092f..cde869e7d64 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -738,9 +738,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,
@@ -1093,9 +1090,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 resoure owner has room to remember the
+		 * pin.
 		 */
 		ReservePrivateRefCountEntry();
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
 
 		/*
 		 * Select a victim buffer.  The buffer is returned with its header
@@ -1595,8 +1594,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.
  */
@@ -1607,6 +1604,8 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
 	bool		result;
 	PrivateRefCountEntry *ref;
 
+	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+
 	ref = GetPrivateRefCountEntry(b, true);
 
 	if (ref == NULL)
@@ -1687,7 +1686,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.
@@ -1857,9 +1857,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
@@ -2334,9 +2331,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;
@@ -2418,8 +2412,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)
@@ -2429,7 +2421,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.
@@ -3487,9 +3481,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;
@@ -3503,7 +3494,9 @@ FlushRelationBuffers(Relation rel)
 		if (!RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node))
 			continue;
 
+		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
@@ -3560,9 +3553,6 @@ FlushRelationsAllBuffers(SMgrRelation *smgrs, int nrels)
 	if (use_bsearch)
 		pg_qsort(srels, nrels, sizeof(SMgrSortArray), rnode_comparator);
 
-	/* Make sure we can handle the pin inside the loop */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
-
 	for (i = 0; i < NBuffers; i++)
 	{
 		SMgrSortArray *srelent = NULL;
@@ -3599,7 +3589,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 (RelFileNodeEquals(bufHdr->tag.rnode, srelent->rnode) &&
@@ -3639,9 +3631,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;
@@ -3655,7 +3644,9 @@ FlushDatabaseBuffers(Oid dbid)
 		if (bufHdr->tag.rnode.dbNode != dbid)
 			continue;
 
+		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (bufHdr->tag.rnode.dbNode == dbid &&
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index 04b3558ea33..f7c15ea8a44 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -123,6 +123,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, (void *) &newTag, HASH_FIND, NULL);
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 55c94458981..2630c800b05 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1607,8 +1607,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();
@@ -1696,13 +1694,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.1

v7-0002-Make-resowners-more-easily-extensible.patchtext/x-patch; charset=UTF-8; name=v7-0002-Make-resowners-more-easily-extensible.patchDownload
From 235f43e5298dbc4b88a246d39937adbcaa409192 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 20 Jan 2021 23:16:07 +0200
Subject: [PATCH v7 2/3] Make resowners more easily extensible.

Use a single array and hash, instead of one for each object kind.
---
 src/backend/access/common/tupdesc.c   |   42 +-
 src/backend/jit/jit.c                 |    2 -
 src/backend/jit/llvm/llvmjit.c        |   46 +-
 src/backend/storage/buffer/bufmgr.c   |   45 +-
 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     |   19 +-
 src/backend/utils/resowner/resowner.c | 1233 +++++++------------------
 src/backend/utils/time/snapmgr.c      |   38 +-
 src/common/cryptohash_openssl.c       |   49 +-
 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  |  105 ---
 src/tools/pgindent/typedefs.list      |    4 +-
 21 files changed, 841 insertions(+), 1055 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 902f59440cd..2bf0c121fda 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -29,9 +29,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
@@ -376,7 +394,7 @@ IncrTupleDescRefCount(TupleDesc tupdesc)
 {
 	Assert(tupdesc->tdrefcount >= 0);
 
-	ResourceOwnerEnlargeTupleDescs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	tupdesc->tdrefcount++;
 	ResourceOwnerRememberTupleDesc(CurrentResourceOwner, tupdesc);
 }
@@ -925,3 +943,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 2da300e000d..1aa04d173b4 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 98a27f08bfd..45eb9cc2ff2 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,8 +121,26 @@ static LLVMOrcLLJITRef llvm_create_jit_instance(LLVMTargetMachineRef tm);
 static char *llvm_error_message(LLVMErrorRef error);
 #endif							/* LLVM_VERSION_MAJOR > 11 */
 
-PG_MODULE_MAGIC;
+/* 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;
 
 /*
  * Initialize LLVM JIT provider.
@@ -151,7 +169,7 @@ llvm_create_context(int jitFlags)
 
 	llvm_session_initialize();
 
-	ResourceOwnerEnlargeJIT(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	context = MemoryContextAllocZero(TopMemoryContext,
 									 sizeof(LLVMJitContext));
@@ -159,7 +177,7 @@ llvm_create_context(int jitFlags)
 
 	/* ensure cleanup */
 	context->base.resowner = CurrentResourceOwner;
-	ResourceOwnerRememberJIT(CurrentResourceOwner, PointerGetDatum(context));
+	ResourceOwnerRememberJIT(CurrentResourceOwner, context);
 
 	return context;
 }
@@ -221,6 +239,8 @@ llvm_release_context(JitContext *context)
 
 		pfree(jit_handle);
 	}
+
+	ResourceOwnerForgetJIT(context->resowner, context);
 }
 
 /*
@@ -1231,3 +1251,21 @@ llvm_error_message(LLVMErrorRef error)
 }
 
 #endif							/* LLVM_VERSION_MAJOR > 11 */
+
+/*
+ * ResourceOwner callbacks
+ */
+static void
+ResOwnerReleaseJitContext(Datum res)
+{
+	jit_release_context((JitContext *) PointerGetDatum(res));
+}
+
+static void
+ResOwnerPrintJitContextLeakWarning(Datum res)
+{
+	/* XXX: We used to not print these. Was that intentional? */
+	JitContext *context = (JitContext *) PointerGetDatum(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 cde869e7d64..82e39de19f9 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -52,7 +52,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"
 
 
@@ -206,6 +206,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
@@ -1094,7 +1106,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
@@ -1604,7 +1616,7 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
 	bool		result;
 	PrivateRefCountEntry *ref;
 
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	ref = GetPrivateRefCountEntry(b, true);
 
@@ -1687,7 +1699,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.
@@ -2423,7 +2435,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.
@@ -3496,7 +3508,7 @@ FlushRelationBuffers(Relation rel)
 
 		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
@@ -3591,7 +3603,7 @@ FlushRelationsAllBuffers(SMgrRelation *smgrs, int nrels)
 
 		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (RelFileNodeEquals(bufHdr->tag.rnode, srelent->rnode) &&
@@ -3646,7 +3658,7 @@ FlushDatabaseBuffers(Oid dbid)
 
 		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (bufHdr->tag.rnode.dbNode == dbid &&
@@ -3729,7 +3741,7 @@ void
 IncrBufferRefCount(Buffer buffer)
 {
 	Assert(BufferIsPinned(buffer));
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	if (BufferIsLocal(buffer))
 		LocalRefCount[-buffer - 1]++;
 	else
@@ -4803,3 +4815,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 f7c15ea8a44..3f22d7127b9 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -22,7 +22,7 @@
 #include "storage/bufmgr.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 
 
 /*#define LBDEBUG*/
@@ -124,7 +124,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 b58502837aa..1172ea245e0 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -97,7 +97,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)
@@ -340,6 +340,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
  */
@@ -1430,7 +1448,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
@@ -1612,7 +1630,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
@@ -1742,7 +1760,7 @@ PathNameCreateTemporaryFile(const char *path, bool error_on_failure)
 {
 	File		file;
 
-	ResourceOwnerEnlargeFiles(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	/*
 	 * Open the file.  Note: we don't use O_EXCL, in case there is an orphaned
@@ -1780,7 +1798,7 @@ PathNameOpenTemporaryFile(const char *path, int mode)
 {
 	File		file;
 
-	ResourceOwnerEnlargeFiles(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	file = PathNameOpenFile(path, mode | PG_BINARY);
 
@@ -3700,3 +3718,19 @@ pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
 
 	return sum;
 }
+
+/*
+ * 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 b461a5f7e96..77b6e58fd9d 100644
--- a/src/backend/storage/ipc/dsm.c
+++ b/src/backend/storage/ipc/dsm.c
@@ -37,13 +37,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
 
@@ -139,6 +141,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.
  *
@@ -900,7 +921,7 @@ void
 dsm_unpin_mapping(dsm_segment *seg)
 {
 	Assert(seg->resowner == NULL);
-	ResourceOwnerEnlargeDSMs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	seg->resowner = CurrentResourceOwner;
 	ResourceOwnerRememberDSM(seg->resowner, seg);
 }
@@ -1167,7 +1188,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);
@@ -1246,3 +1267,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 108b4d90238..719612da04d 100644
--- a/src/backend/storage/lmgr/lock.c
+++ b/src/backend/storage/lmgr/lock.c
@@ -47,7 +47,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 2630c800b05..1d86f543f4a 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
@@ -1270,7 +1307,7 @@ SearchCatCacheInternal(CatCache *cache,
 		 */
 		if (!ct->negative)
 		{
-			ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner);
+			ResourceOwnerEnlarge(CurrentResourceOwner);
 			ct->refcount++;
 			ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple);
 
@@ -1371,7 +1408,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 */
@@ -1583,7 +1620,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);
 
@@ -1695,7 +1732,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);
@@ -2069,14 +2106,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));
 
@@ -2090,9 +2132,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 1a0950489d7..b5773ba2d49 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;
 
@@ -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);
 	}
@@ -2205,3 +2225,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 7ef510cd01b..6ca5b50552b 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -78,13 +78,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"
 
@@ -2078,6 +2079,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.
@@ -2089,7 +2108,7 @@ RelationIdGetRelation(Oid relationId)
 void
 RelationIncrementReferenceCount(Relation rel)
 {
-	ResourceOwnerEnlargeRelationRefs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	rel->rd_refcnt += 1;
 	if (!IsBootstrapProcessingMode())
 		ResourceOwnerRememberRelationRef(CurrentResourceOwner, rel);
@@ -6417,3 +6436,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 2998f6bb362..4ed13afa101 100644
--- a/src/backend/utils/resowner/README
+++ b/src/backend/utils/resowner/README
@@ -60,13 +60,18 @@ subtransaction or portal.  Therefore, the "release" operation on a child
 ResourceOwner transfers lock ownership to the parent instead of actually
 releasing the lock, if isCommit is true.
 
-Currently, ResourceOwners contain direct support for recording ownership of
-buffer pins, lmgr locks, and catcache, relcache, plancache, tupdesc, and
-snapshot references.  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.
+As of this writing, it's used internally for buffer pins, lmgr locks, and
+catcache, relcache, plancache, tupdesc, snapshot, DSM and JIT context
+references. ResourceOwner treats all resources the same, and extensions
+can define new kinds of resources by filling in a ResourceOwnerFuncs
+struct with a 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. This used to be the only way to register new kinds
+of objects with a resource owner; nowadays it easier to write custom
+ResourceOwnerFuncs callbacks.
 
 Whenever we are inside a transaction, the global variable
 CurrentResourceOwner shows which resource owner should be assigned
diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index a171df573ce..d62b1495dd3 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -20,75 +20,45 @@
  */
 #include "postgres.h"
 
-#include "common/cryptohash.h"
 #include "common/hashfn.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;
+} 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 8
 
 /*
- * 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 32
 
 /*
- * 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
@@ -118,23 +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 */
+	/*
+	 * 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;
 
 
 /*****************************************************************************
@@ -146,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
  */
@@ -160,296 +152,315 @@ 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 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);
 
 
 /*****************************************************************************
  *	  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
- */
 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;
+
+	idx = hash_resource_elem(value, kind) & mask;
+	for (;;)
+	{
+		if (owner->hash[idx].kind == NULL)
+			break;
+		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;
-
-	if (resarr->nitems < resarr->maxitems)
-		return;					/* no work needed */
+	bool		found;
+	int			capacity;
 
-	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. */
+	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 */
+	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 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
- *
- * Returns true on success, false if resource was not found.
+ * Make sure there is room for at least one more resource in an array.
  *
- * Note: if same resource ID appears more than once, one instance is removed.
+ * 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.
  */
-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.
+ * Remember that an object is owner by a ReourceOwner
  *
- * "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.
- *
- * 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++;
-		}
-	}
+	Assert(owner->narr < RESOWNER_ARRAY_SIZE);
 
-	*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
+ *
+ * Returns true on success. If the resource was not found, returns false,
+ * and calls kind->ForgetError callback.
+ *
+ * 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));
+		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);
 }
 
 /*
@@ -486,6 +497,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
@@ -497,7 +517,6 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 	ResourceOwner child;
 	ResourceOwner save;
 	ResourceReleaseCallbackItem *item;
-	Datum		foundres;
 
 	/* Recurse to handle descendants */
 	for (child = owner->firstchild; child != NULL; child = child->nextchild)
@@ -513,61 +532,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 references 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 references ---
+		 * 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 *) PointerGetDatum(foundres);
-
-			jit_release_context(context);
-		}
-
-		/* Ditto for cryptohash contexts */
-		while (ResourceArrayGetAny(&(owner->cryptohasharr), &foundres))
-		{
-			pg_cryptohash_ctx *context =
-			(pg_cryptohash_ctx *) PointerGetDatum(foundres);
-
-			if (isCommit)
-				PrintCryptoHashLeakWarning(foundres);
-			pg_cryptohash_free(context);
-		}
+		ResourceOwnerReleaseAll(owner, phase, isCommit);
 	}
 	else if (phase == RESOURCE_RELEASE_LOCKS)
 	{
@@ -576,7 +547,7 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 			/*
 			 * For a top-level xact we are going to release all locks (or at
 			 * least all non-session locks), so just do a single lmgr call at
-			 * the top of the recursion.
+			 * the top of the recursion
 			 */
 			if (owner == TopTransactionResourceOwner)
 			{
@@ -620,70 +591,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 references 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 */
@@ -703,13 +613,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);
 
-	while (ResourceArrayGetAny(&(owner->planrefarr), &foundres))
+			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);
+		}
+	}
+
+	/* 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);
+		}
 	}
 }
 
@@ -726,19 +665,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->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.
@@ -754,18 +689,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));
-
+	if (owner->hash)
+		pfree(owner->hash);
 	pfree(owner);
 }
 
@@ -823,11 +748,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)
@@ -918,44 +842,6 @@ ReleaseAuxProcessResourcesCallback(int code, Datum arg)
 	ReleaseAuxProcessResources(isCommit);
 }
 
-
-/*
- * Make sure there is room for at least one more entry in a ResourceOwner's
- * buffer 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
-ResourceOwnerEnlargeBuffers(ResourceOwner owner)
-{
-	/* 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
  *
@@ -1007,424 +893,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));
-}
diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c
index 95704265b67..984af42e6da 100644
--- a/src/backend/utils/time/snapmgr.c
+++ b/src/backend/utils/time/snapmgr.c
@@ -66,7 +66,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"
@@ -174,6 +174,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.
  *
@@ -831,7 +849,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);
 
@@ -2349,3 +2367,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 643cc7aea2c..cf5fc9eacb4 100644
--- a/src/common/cryptohash_openssl.c
+++ b/src/common/cryptohash_openssl.c
@@ -30,7 +30,6 @@
 #ifndef FRONTEND
 #include "utils/memutils.h"
 #include "utils/resowner.h"
-#include "utils/resowner_private.h"
 #endif
 
 /*
@@ -63,6 +62,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
+
 /*
  * pg_cryptohash_create
  *
@@ -80,7 +100,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));
@@ -109,8 +129,7 @@ pg_cryptohash_create(pg_cryptohash_type type)
 
 #ifndef FRONTEND
 	ctx->resowner = CurrentResourceOwner;
-	ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
-									PointerGetDatum(ctx));
+	ResourceOwnerRememberCryptoHash(CurrentResourceOwner, ctx);
 #endif
 
 	return ctx;
@@ -241,10 +260,28 @@ 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));
 	FREE(ctx);
 }
+
+
+/*
+ * 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/include/storage/buf_internals.h b/src/include/storage/buf_internals.h
index f6b57829653..6815dc38a16 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -296,6 +296,15 @@ typedef struct CkptSortItem
 
 extern 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 ddc2762eb3f..2bf95c22e84 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 ff09c63a02f..f3e3e50fb21 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 109ac31b248..d59d14c0d01 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 an resource 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 c480a1a24be..00000000000
--- a/src/include/utils/resowner_private.h
+++ /dev/null
@@ -1,105 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * resowner_private.h
- *	  POSTGRES resource owner private definitions.
- *
- * See utils/resowner/README for more info.
- *
- *
- * Portions Copyright (c) 1996-2021, 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);
-
-#endif							/* RESOWNER_PRIVATE_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 8bd95aefa1d..7e511e02690 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2128,8 +2128,10 @@ ReplicationStateOnDisk
 ResTarget
 ReservoirState
 ReservoirStateData
-ResourceArray
+ResourceElem
 ResourceOwner
+ResourceOwnerData
+ResourceOwnerFuncs
 ResourceReleaseCallback
 ResourceReleaseCallbackItem
 ResourceReleasePhase
-- 
2.30.1

v7-0003-Optimize-hash-function.patchtext/x-patch; charset=UTF-8; name=v7-0003-Optimize-hash-function.patchDownload
From 8dd6ec265887b4a762a037925063135f6147913a Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Thu, 21 Jan 2021 00:06:58 +0200
Subject: [PATCH v7 3/3] Optimize hash function

---
 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 d62b1495dd3..6189d051072 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -163,15 +163,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 resources types 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
 }
 
 static void
diff --git a/src/include/common/hashfn.h b/src/include/common/hashfn.h
index c634cc067a1..009ffbbdd38 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.1

#33vignesh C
vignesh21@gmail.com
In reply to: Heikki Linnakangas (#32)
Re: ResourceOwner refactoring

On Tue, Mar 9, 2021 at 6:10 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

On 08/03/2021 18:47, Ibrar Ahmed wrote:

The patchset does not apply successfully, there are some hunk failures.

http://cfbot.cputube.org/patch_32_2834.log
<http://cfbot.cputube.org/patch_32_2834.log&gt;

v6-0002-Make-resowners-more-easily-extensible.patch

1 out of 6 hunks FAILED -- saving rejects to file
src/backend/utils/cache/plancache.c.rej
2 out of 15 hunks FAILED -- saving rejects to file
src/backend/utils/resowner/resowner.c.rej

Can we get a rebase?

Here you go.

The patch does not apply on Head anymore, could you rebase and post a
patch. I'm changing the status to "Waiting for Author".

Regards,
Vignesh

#34Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: vignesh C (#33)
Re: ResourceOwner refactoring

On 2021-Jul-14, vignesh C wrote:

On Tue, Mar 9, 2021 at 6:10 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

Here you go.

The patch does not apply on Head anymore, could you rebase and post a
patch. I'm changing the status to "Waiting for Author".

Support for hmac was added by e6bdfd9700eb so the rebase is not trivial.

--
Álvaro Herrera Valdivia, Chile — https://www.EnterpriseDB.com/
"Ellos andaban todos desnudos como su madre los parió, y también las mujeres,
aunque no vi más que una, harto moza, y todos los que yo vi eran todos
mancebos, que ninguno vi de edad de más de XXX años" (Cristóbal Colón)

#35Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Alvaro Herrera (#34)
3 attachment(s)
Re: ResourceOwner refactoring

On 14/07/2021 17:07, Alvaro Herrera wrote:

On 2021-Jul-14, vignesh C wrote:

On Tue, Mar 9, 2021 at 6:10 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

Here you go.

The patch does not apply on Head anymore, could you rebase and post a
patch. I'm changing the status to "Waiting for Author".

Support for hmac was added by e6bdfd9700eb so the rebase is not trivial.

Yeah, needed some manual fixing, but here you go.

- Heikki

Attachments:

v8-0001-Move-a-few-ResourceOwnerEnlarge-calls-for-safety-.patchtext/x-patch; charset=UTF-8; name=v8-0001-Move-a-few-ResourceOwnerEnlarge-calls-for-safety-.patchDownload
From 19fb4369e3b7bfb29afc6cfe597af5a89698dd3f Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 13 Jan 2021 12:21:28 +0200
Subject: [PATCH v8 1/3] Move a few ResourceOwnerEnlarge() calls for safety and
 clarity.

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
inbetween, 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.
---
 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 86ef607ff38..2383e4cac08 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -810,9 +810,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,
@@ -1165,9 +1162,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 resoure owner has room to remember the
+		 * pin.
 		 */
 		ReservePrivateRefCountEntry();
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
 
 		/*
 		 * Select a victim buffer.  The buffer is returned with its header
@@ -1668,8 +1667,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.
  */
@@ -1680,6 +1677,8 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
 	bool		result;
 	PrivateRefCountEntry *ref;
 
+	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+
 	ref = GetPrivateRefCountEntry(b, true);
 
 	if (ref == NULL)
@@ -1760,7 +1759,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.
@@ -1936,9 +1936,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
@@ -2412,9 +2409,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;
@@ -2496,8 +2490,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)
@@ -2507,7 +2499,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.
@@ -3559,9 +3553,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;
@@ -3575,7 +3566,9 @@ FlushRelationBuffers(Relation rel)
 		if (!RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node))
 			continue;
 
+		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
@@ -3632,9 +3625,6 @@ FlushRelationsAllBuffers(SMgrRelation *smgrs, int nrels)
 	if (use_bsearch)
 		pg_qsort(srels, nrels, sizeof(SMgrSortArray), rnode_comparator);
 
-	/* Make sure we can handle the pin inside the loop */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
-
 	for (i = 0; i < NBuffers; i++)
 	{
 		SMgrSortArray *srelent = NULL;
@@ -3671,7 +3661,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 (RelFileNodeEquals(bufHdr->tag.rnode, srelent->rnode) &&
@@ -3711,9 +3703,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;
@@ -3727,7 +3716,9 @@ FlushDatabaseBuffers(Oid dbid)
 		if (bufHdr->tag.rnode.dbNode != dbid)
 			continue;
 
+		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (bufHdr->tag.rnode.dbNode == dbid &&
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index 04b3558ea33..f7c15ea8a44 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -123,6 +123,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, (void *) &newTag, HASH_FIND, NULL);
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 4fbdc62d8c7..13eed587601 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1609,8 +1609,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();
@@ -1698,13 +1696,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

v8-0002-Make-resowners-more-easily-extensible.patchtext/x-patch; charset=UTF-8; name=v8-0002-Make-resowners-more-easily-extensible.patchDownload
From b791a6af0c01673e7e6d982cb9b679cdfcd8e7a7 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 20 Jan 2021 23:16:07 +0200
Subject: [PATCH v8 2/3] Make resowners more easily extensible.

Use a single array and hash, instead of one for each object kind.
---
 src/backend/access/common/tupdesc.c   |   42 +-
 src/backend/jit/jit.c                 |    2 -
 src/backend/jit/llvm/llvmjit.c        |   46 +-
 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     |   19 +-
 src/backend/utils/resowner/resowner.c | 1312 +++++++------------------
 src/backend/utils/time/snapmgr.c      |   38 +-
 src/common/cryptohash_openssl.c       |   49 +-
 src/common/hmac_openssl.c             |   47 +-
 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, 894 insertions(+), 1137 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 4c63bd4dc64..f2d4442c938 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);
 }
@@ -910,3 +928,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 2da300e000d..1aa04d173b4 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 df691cbf1c5..57e3144fae7 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,8 +121,26 @@ static LLVMOrcLLJITRef llvm_create_jit_instance(LLVMTargetMachineRef tm);
 static char *llvm_error_message(LLVMErrorRef error);
 #endif							/* LLVM_VERSION_MAJOR > 11 */
 
-PG_MODULE_MAGIC;
+/* 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;
 
 /*
  * Initialize LLVM JIT provider.
@@ -151,7 +169,7 @@ llvm_create_context(int jitFlags)
 
 	llvm_session_initialize();
 
-	ResourceOwnerEnlargeJIT(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	context = MemoryContextAllocZero(TopMemoryContext,
 									 sizeof(LLVMJitContext));
@@ -159,7 +177,7 @@ llvm_create_context(int jitFlags)
 
 	/* ensure cleanup */
 	context->base.resowner = CurrentResourceOwner;
-	ResourceOwnerRememberJIT(CurrentResourceOwner, PointerGetDatum(context));
+	ResourceOwnerRememberJIT(CurrentResourceOwner, context);
 
 	return context;
 }
@@ -221,6 +239,8 @@ llvm_release_context(JitContext *context)
 
 		pfree(jit_handle);
 	}
+
+	ResourceOwnerForgetJIT(context->resowner, context);
 }
 
 /*
@@ -1234,3 +1254,21 @@ llvm_error_message(LLVMErrorRef error)
 }
 
 #endif							/* LLVM_VERSION_MAJOR > 11 */
+
+/*
+ * ResourceOwner callbacks
+ */
+static void
+ResOwnerReleaseJitContext(Datum res)
+{
+	jit_release_context((JitContext *) PointerGetDatum(res));
+}
+
+static void
+ResOwnerPrintJitContextLeakWarning(Datum res)
+{
+	/* XXX: We used to not print these. Was that intentional? */
+	JitContext *context = (JitContext *) PointerGetDatum(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 2383e4cac08..9e55ad3f743 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -52,7 +52,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"
 
 
@@ -206,6 +206,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
@@ -625,7 +637,7 @@ ReadRecentBuffer(RelFileNode rnode, ForkNumber forkNum, BlockNumber blockNum,
 
 	Assert(BufferIsValid(recent_buffer));
 
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	ReservePrivateRefCountEntry();
 	INIT_BUFFERTAG(tag, rnode, forkNum, blockNum);
 
@@ -1166,7 +1178,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
@@ -1677,7 +1689,7 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
 	bool		result;
 	PrivateRefCountEntry *ref;
 
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	ref = GetPrivateRefCountEntry(b, true);
 
@@ -1760,7 +1772,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.
@@ -2501,7 +2513,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.
@@ -3568,7 +3580,7 @@ FlushRelationBuffers(Relation rel)
 
 		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
@@ -3663,7 +3675,7 @@ FlushRelationsAllBuffers(SMgrRelation *smgrs, int nrels)
 
 		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (RelFileNodeEquals(bufHdr->tag.rnode, srelent->rnode) &&
@@ -3718,7 +3730,7 @@ FlushDatabaseBuffers(Oid dbid)
 
 		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (bufHdr->tag.rnode.dbNode == dbid &&
@@ -3801,7 +3813,7 @@ void
 IncrBufferRefCount(Buffer buffer)
 {
 	Assert(BufferIsPinned(buffer));
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	if (BufferIsLocal(buffer))
 		LocalRefCount[-buffer - 1]++;
 	else
@@ -4848,3 +4860,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 f7c15ea8a44..3f22d7127b9 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -22,7 +22,7 @@
 #include "storage/bufmgr.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 
 
 /*#define LBDEBUG*/
@@ -124,7 +124,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 a340a5f6afe..6a9ee97ea1e 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -99,7 +99,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)
@@ -345,6 +345,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
  */
@@ -1449,7 +1467,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
@@ -1631,7 +1649,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
@@ -1761,7 +1779,7 @@ PathNameCreateTemporaryFile(const char *path, bool error_on_failure)
 {
 	File		file;
 
-	ResourceOwnerEnlargeFiles(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	/*
 	 * Open the file.  Note: we don't use O_EXCL, in case there is an orphaned
@@ -1799,7 +1817,7 @@ PathNameOpenTemporaryFile(const char *path, int mode)
 {
 	File		file;
 
-	ResourceOwnerEnlargeFiles(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	file = PathNameOpenFile(path, mode | PG_BINARY);
 
@@ -3779,3 +3797,19 @@ pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
 
 	return sum;
 }
+
+/*
+ * 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 b461a5f7e96..77b6e58fd9d 100644
--- a/src/backend/storage/ipc/dsm.c
+++ b/src/backend/storage/ipc/dsm.c
@@ -37,13 +37,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
 
@@ -139,6 +141,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.
  *
@@ -900,7 +921,7 @@ void
 dsm_unpin_mapping(dsm_segment *seg)
 {
 	Assert(seg->resowner == NULL);
-	ResourceOwnerEnlargeDSMs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	seg->resowner = CurrentResourceOwner;
 	ResourceOwnerRememberDSM(seg->resowner, seg);
 }
@@ -1167,7 +1188,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);
@@ -1246,3 +1267,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 108b4d90238..719612da04d 100644
--- a/src/backend/storage/lmgr/lock.c
+++ b/src/backend/storage/lmgr/lock.c
@@ -47,7 +47,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 13eed587601..ed046db57ec 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
@@ -1272,7 +1309,7 @@ SearchCatCacheInternal(CatCache *cache,
 		 */
 		if (!ct->negative)
 		{
-			ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner);
+			ResourceOwnerEnlarge(CurrentResourceOwner);
 			ct->refcount++;
 			ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple);
 
@@ -1373,7 +1410,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 */
@@ -1585,7 +1622,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);
 
@@ -1697,7 +1734,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);
@@ -2071,14 +2108,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));
 
@@ -2092,9 +2134,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 6767eae8f20..a10362629f8 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;
 
@@ -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);
 	}
@@ -2205,3 +2225,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 13d9994af3e..90d2892489f 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -77,13 +77,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"
 
@@ -2056,6 +2057,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.
@@ -2067,7 +2086,7 @@ RelationIdGetRelation(Oid relationId)
 void
 RelationIncrementReferenceCount(Relation rel)
 {
-	ResourceOwnerEnlargeRelationRefs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	rel->rd_refcnt += 1;
 	if (!IsBootstrapProcessingMode())
 		ResourceOwnerRememberRelationRef(CurrentResourceOwner, rel);
@@ -6546,3 +6565,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 2998f6bb362..4ed13afa101 100644
--- a/src/backend/utils/resowner/README
+++ b/src/backend/utils/resowner/README
@@ -60,13 +60,18 @@ subtransaction or portal.  Therefore, the "release" operation on a child
 ResourceOwner transfers lock ownership to the parent instead of actually
 releasing the lock, if isCommit is true.
 
-Currently, ResourceOwners contain direct support for recording ownership of
-buffer pins, lmgr locks, and catcache, relcache, plancache, tupdesc, and
-snapshot references.  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.
+As of this writing, it's used internally for buffer pins, lmgr locks, and
+catcache, relcache, plancache, tupdesc, snapshot, DSM and JIT context
+references. ResourceOwner treats all resources the same, and extensions
+can define new kinds of resources by filling in a ResourceOwnerFuncs
+struct with a 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. This used to be the only way to register new kinds
+of objects with a resource owner; nowadays it easier to write custom
+ResourceOwnerFuncs callbacks.
 
 Whenever we are inside a transaction, the global variable
 CurrentResourceOwner shows which resource owner should be assigned
diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index e24f00f0601..d62b1495dd3 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;
+} 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 8
 
 /*
- * 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 32
 
 /*
- * 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,315 @@ 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 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
- */
 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;
+
+	idx = hash_resource_elem(value, kind) & mask;
+	for (;;)
+	{
+		if (owner->hash[idx].kind == NULL)
+			break;
+		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;
-
-	if (resarr->nitems < resarr->maxitems)
-		return;					/* no work needed */
+	bool		found;
+	int			capacity;
 
-	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. */
+	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 */
+	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 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
- *
- * Returns true on success, false if resource was not found.
+ * Make sure there is room for at least one more resource in an array.
  *
- * Note: if same resource ID appears more than once, one instance is removed.
+ * 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.
  */
-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.
+ * Remember that an object is owner by a ReourceOwner
  *
- * "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.
- *
- * 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++;
-		}
-	}
+	Assert(owner->narr < RESOWNER_ARRAY_SIZE);
 
-	*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
+ *
+ * Returns true on success. If the resource was not found, returns false,
+ * and calls kind->ForgetError callback.
+ *
+ * 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 +497,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
@@ -501,7 +517,6 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 	ResourceOwner child;
 	ResourceOwner save;
 	ResourceReleaseCallbackItem *item;
-	Datum		foundres;
 
 	/* Recurse to handle descendants */
 	for (child = owner->firstchild; child != NULL; child = child->nextchild)
@@ -517,71 +532,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 references 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 references ---
+		 * 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 *) PointerGetDatum(foundres);
-
-			jit_release_context(context);
-		}
-
-		/* Ditto for cryptohash contexts */
-		while (ResourceArrayGetAny(&(owner->cryptohasharr), &foundres))
-		{
-			pg_cryptohash_ctx *context =
-			(pg_cryptohash_ctx *) PointerGetDatum(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 *) PointerGetDatum(foundres);
-
-			if (isCommit)
-				PrintHMACLeakWarning(foundres);
-			pg_hmac_free(context);
-		}
+		ResourceOwnerReleaseAll(owner, phase, isCommit);
 	}
 	else if (phase == RESOURCE_RELEASE_LOCKS)
 	{
@@ -590,7 +547,7 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 			/*
 			 * For a top-level xact we are going to release all locks (or at
 			 * least all non-session locks), so just do a single lmgr call at
-			 * the top of the recursion.
+			 * the top of the recursion
 			 */
 			if (owner == TopTransactionResourceOwner)
 			{
@@ -634,70 +591,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 references 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 */
@@ -717,13 +613,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);
+		}
 	}
 }
 
@@ -740,20 +665,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.
@@ -769,19 +689,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);
 }
 
@@ -839,11 +748,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)
@@ -934,58 +842,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 */
@@ -1023,469 +893,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 2968c7f7b7d..1f46cbe58c9 100644
--- a/src/backend/utils/time/snapmgr.c
+++ b/src/backend/utils/time/snapmgr.c
@@ -66,7 +66,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"
@@ -174,6 +174,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.
  *
@@ -831,7 +849,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);
 
@@ -2349,3 +2367,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 643cc7aea2c..cf5fc9eacb4 100644
--- a/src/common/cryptohash_openssl.c
+++ b/src/common/cryptohash_openssl.c
@@ -30,7 +30,6 @@
 #ifndef FRONTEND
 #include "utils/memutils.h"
 #include "utils/resowner.h"
-#include "utils/resowner_private.h"
 #endif
 
 /*
@@ -63,6 +62,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
+
 /*
  * pg_cryptohash_create
  *
@@ -80,7 +100,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));
@@ -109,8 +129,7 @@ pg_cryptohash_create(pg_cryptohash_type type)
 
 #ifndef FRONTEND
 	ctx->resowner = CurrentResourceOwner;
-	ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
-									PointerGetDatum(ctx));
+	ResourceOwnerRememberCryptoHash(CurrentResourceOwner, ctx);
 #endif
 
 	return ctx;
@@ -241,10 +260,28 @@ 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));
 	FREE(ctx);
 }
+
+
+/*
+ * 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 1acf59476eb..0a65284bae9 100644
--- a/src/common/hmac_openssl.c
+++ b/src/common/hmac_openssl.c
@@ -29,7 +29,6 @@
 #ifndef FRONTEND
 #include "utils/memutils.h"
 #include "utils/resowner.h"
-#include "utils/resowner_private.h"
 #endif
 
 /*
@@ -63,6 +62,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
+
 /*
  * pg_hmac_create
  *
@@ -86,7 +106,7 @@ pg_hmac_create(pg_cryptohash_type type)
 	 */
 #ifdef HAVE_HMAC_CTX_NEW
 #ifndef FRONTEND
-	ResourceOwnerEnlargeHMAC(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 #endif
 	ctx->hmacctx = HMAC_CTX_new();
 #else
@@ -108,7 +128,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));
@@ -244,7 +264,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));
@@ -254,3 +274,22 @@ pg_hmac_free(pg_hmac_ctx *ctx)
 	explicit_bzero(ctx, sizeof(pg_hmac_ctx));
 	FREE(ctx);
 }
+
+
+/*
+ * 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 33fcaf5c9a8..10ef8d850e4 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -300,6 +300,15 @@ typedef struct CkptSortItem
 
 extern 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 ddc2762eb3f..2bf95c22e84 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 ff09c63a02f..f3e3e50fb21 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 109ac31b248..d59d14c0d01 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 an resource 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 6dafc87e28c..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-2021, 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 37cf4b2f76b..1a0e05df62b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2223,8 +2223,10 @@ ReplicationStateOnDisk
 ResTarget
 ReservoirState
 ReservoirStateData
-ResourceArray
+ResourceElem
 ResourceOwner
+ResourceOwnerData
+ResourceOwnerFuncs
 ResourceReleaseCallback
 ResourceReleaseCallbackItem
 ResourceReleasePhase
-- 
2.30.2

v8-0003-Optimize-hash-function.patchtext/x-patch; charset=UTF-8; name=v8-0003-Optimize-hash-function.patchDownload
From 3a92705934fc91cd9f0389e08df1fd90e4f862ee Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Thu, 21 Jan 2021 00:06:58 +0200
Subject: [PATCH v8 3/3] Optimize hash function

---
 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 d62b1495dd3..6189d051072 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -163,15 +163,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 resources types 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
 }
 
 static void
diff --git a/src/include/common/hashfn.h b/src/include/common/hashfn.h
index c634cc067a1..009ffbbdd38 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

#36Zhihong Yu
zyu@yugabyte.com
In reply to: Heikki Linnakangas (#35)
Re: ResourceOwner refactoring

On Wed, Jul 14, 2021 at 7:40 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

On 14/07/2021 17:07, Alvaro Herrera wrote:

On 2021-Jul-14, vignesh C wrote:

On Tue, Mar 9, 2021 at 6:10 PM Heikki Linnakangas <hlinnaka@iki.fi>

wrote:

Here you go.

The patch does not apply on Head anymore, could you rebase and post a
patch. I'm changing the status to "Waiting for Author".

Support for hmac was added by e6bdfd9700eb so the rebase is not trivial.

Yeah, needed some manual fixing, but here you go.

- Heikki

Hi,
For the loop over the hash:

+       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)
...
+   } while (capacity != owner->capacity);

Since the phase variable doesn't seem to change for the while loop, I
wonder what benefit the while loop has (since the release is governed by
phase).

Cheers

#37Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Zhihong Yu (#36)
Re: ResourceOwner refactoring

Thanks for having a look!

On 14/07/2021 18:18, Zhihong Yu wrote:

For the loop over the hash:

+       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)
...
+   } while (capacity != owner->capacity);

Since the phase variable doesn't seem to change for the while loop, I
wonder what benefit the while loop has (since the release is governed by
phase).

Hmm, the phase variable doesn't change, but could the element at
'owner->hash[idx]' change? I'm not sure about that. The loop is supposed
to handle the case that the hash table grows; could that replace the
element at 'owner->hash[idx]' with something else, with different phase?
The check is very cheap, so I'm inclined to keep it to be sure.

- Heikki

#38Zhihong Yu
zyu@yugabyte.com
In reply to: Heikki Linnakangas (#37)
Re: ResourceOwner refactoring

On Wed, Jul 14, 2021 at 8:26 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

Thanks for having a look!

On 14/07/2021 18:18, Zhihong Yu wrote:

For the loop over the hash:

+       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)
...
+   } while (capacity != owner->capacity);

Since the phase variable doesn't seem to change for the while loop, I
wonder what benefit the while loop has (since the release is governed by
phase).

Hmm, the phase variable doesn't change, but could the element at
'owner->hash[idx]' change? I'm not sure about that. The loop is supposed
to handle the case that the hash table grows; could that replace the
element at 'owner->hash[idx]' with something else, with different phase?
The check is very cheap, so I'm inclined to keep it to be sure.

- Heikki

Hi,
Agreed that ```owner->hash[idx].kind->phase == phase``` can be kept.

I just wonder if we should put limit on the number of iterations for the
while loop.
Maybe add a bool variable indicating that kind->ReleaseResource(value) is
called in the inner loop.
If there is no releasing when the inner loop finishes, we can come out of
the while loop.

Cheers

#39Aleksander Alekseev
aleksander@timescale.com
In reply to: Zhihong Yu (#38)
4 attachment(s)
Re: ResourceOwner refactoring

Hi Heikki,

Yeah, needed some manual fixing, but here you go.

Thanks for working on this!

v8-0002 didn't apply to the current master, so I rebased it. See
attached v9-* patches. I also included v9-0004 with some minor tweaks
from me. I have several notes regarding the code.

1. Not sure if I understand this code of ResourceOwnerReleaseAll():
```
/* First handle all the entries in the array. */
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;

if (printLeakWarnings)
kind->PrintLeakWarning(value);
kind->ReleaseResource(value);
found = true;
}
}

/*
* 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.
*/
} while (found && owner->narr > 0);
```

Shouldn't we mark the resource as released and/or decrease narr and/or
save the last processed i? Why this will not call ReleaseResource()
endlessly on the same resource (array item)? Same question for the
following code that iterates over the hash table.

2. Just an idea/observation. It's possible that the performance of
ResourceOwnerEnlarge() can be slightly improved. Since the size of the
hash table is always a power of 2 and the code always doubles the size
of the hash table, (idx & mask) value will get one extra bit, which
can be 0 or 1. If it's 0, the value is already in its place,
otherwise, it should be moved on the known distance. In other words,
it's possible to copy `oldhash` to `newhash` and then move only half
of the items. I don't claim that this code necessarily should be
faster, or that this should be checked in the scope of this work.

--
Best regards,
Aleksander Alekseev

Attachments:

v9-0003-Optimize-hash-function.patchapplication/octet-stream; name=v9-0003-Optimize-hash-function.patchDownload
From 3a92705934fc91cd9f0389e08df1fd90e4f862ee Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Thu, 21 Jan 2021 00:06:58 +0200
Subject: [PATCH v8 3/3] Optimize hash function

---
 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 d62b1495dd3..6189d051072 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -163,15 +163,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 resources types 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
 }
 
 static void
diff --git a/src/include/common/hashfn.h b/src/include/common/hashfn.h
index c634cc067a1..009ffbbdd38 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

v9-0001-Move-a-few-ResourceOwnerEnlarge-calls-for-safety.patchapplication/octet-stream; name=v9-0001-Move-a-few-ResourceOwnerEnlarge-calls-for-safety.patchDownload
From 19fb4369e3b7bfb29afc6cfe597af5a89698dd3f Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 13 Jan 2021 12:21:28 +0200
Subject: [PATCH v8 1/3] Move a few ResourceOwnerEnlarge() calls for safety and
 clarity.

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
inbetween, 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.
---
 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 86ef607ff38..2383e4cac08 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -810,9 +810,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,
@@ -1165,9 +1162,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 resoure owner has room to remember the
+		 * pin.
 		 */
 		ReservePrivateRefCountEntry();
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
 
 		/*
 		 * Select a victim buffer.  The buffer is returned with its header
@@ -1668,8 +1667,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.
  */
@@ -1680,6 +1677,8 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
 	bool		result;
 	PrivateRefCountEntry *ref;
 
+	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+
 	ref = GetPrivateRefCountEntry(b, true);
 
 	if (ref == NULL)
@@ -1760,7 +1759,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.
@@ -1936,9 +1936,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
@@ -2412,9 +2409,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;
@@ -2496,8 +2490,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)
@@ -2507,7 +2499,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.
@@ -3559,9 +3553,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;
@@ -3575,7 +3566,9 @@ FlushRelationBuffers(Relation rel)
 		if (!RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node))
 			continue;
 
+		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
@@ -3632,9 +3625,6 @@ FlushRelationsAllBuffers(SMgrRelation *smgrs, int nrels)
 	if (use_bsearch)
 		pg_qsort(srels, nrels, sizeof(SMgrSortArray), rnode_comparator);
 
-	/* Make sure we can handle the pin inside the loop */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
-
 	for (i = 0; i < NBuffers; i++)
 	{
 		SMgrSortArray *srelent = NULL;
@@ -3671,7 +3661,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 (RelFileNodeEquals(bufHdr->tag.rnode, srelent->rnode) &&
@@ -3711,9 +3703,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;
@@ -3727,7 +3716,9 @@ FlushDatabaseBuffers(Oid dbid)
 		if (bufHdr->tag.rnode.dbNode != dbid)
 			continue;
 
+		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (bufHdr->tag.rnode.dbNode == dbid &&
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index 04b3558ea33..f7c15ea8a44 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -123,6 +123,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, (void *) &newTag, HASH_FIND, NULL);
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 4fbdc62d8c7..13eed587601 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1609,8 +1609,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();
@@ -1698,13 +1696,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

v9-0002-Make-resowners-more-easily-extensible.patchapplication/octet-stream; name=v9-0002-Make-resowners-more-easily-extensible.patchDownload
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 4c63bd4dc6..f2d4442c93 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);
 }
@@ -910,3 +928,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 91b8ae6c51..d42d1bc6c4 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 df691cbf1c..57e3144fae 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,8 +121,26 @@ static LLVMOrcLLJITRef llvm_create_jit_instance(LLVMTargetMachineRef tm);
 static char *llvm_error_message(LLVMErrorRef error);
 #endif							/* LLVM_VERSION_MAJOR > 11 */
 
-PG_MODULE_MAGIC;
+/* 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;
 
 /*
  * Initialize LLVM JIT provider.
@@ -151,7 +169,7 @@ llvm_create_context(int jitFlags)
 
 	llvm_session_initialize();
 
-	ResourceOwnerEnlargeJIT(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	context = MemoryContextAllocZero(TopMemoryContext,
 									 sizeof(LLVMJitContext));
@@ -159,7 +177,7 @@ llvm_create_context(int jitFlags)
 
 	/* ensure cleanup */
 	context->base.resowner = CurrentResourceOwner;
-	ResourceOwnerRememberJIT(CurrentResourceOwner, PointerGetDatum(context));
+	ResourceOwnerRememberJIT(CurrentResourceOwner, context);
 
 	return context;
 }
@@ -221,6 +239,8 @@ llvm_release_context(JitContext *context)
 
 		pfree(jit_handle);
 	}
+
+	ResourceOwnerForgetJIT(context->resowner, context);
 }
 
 /*
@@ -1234,3 +1254,21 @@ llvm_error_message(LLVMErrorRef error)
 }
 
 #endif							/* LLVM_VERSION_MAJOR > 11 */
+
+/*
+ * ResourceOwner callbacks
+ */
+static void
+ResOwnerReleaseJitContext(Datum res)
+{
+	jit_release_context((JitContext *) PointerGetDatum(res));
+}
+
+static void
+ResOwnerPrintJitContextLeakWarning(Datum res)
+{
+	/* XXX: We used to not print these. Was that intentional? */
+	JitContext *context = (JitContext *) PointerGetDatum(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 983e67029e..8772151ebc 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -52,7 +52,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"
 
 
@@ -206,6 +206,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
@@ -625,7 +637,7 @@ ReadRecentBuffer(RelFileNode rnode, ForkNumber forkNum, BlockNumber blockNum,
 
 	Assert(BufferIsValid(recent_buffer));
 
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	ReservePrivateRefCountEntry();
 	INIT_BUFFERTAG(tag, rnode, forkNum, blockNum);
 
@@ -1166,7 +1178,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
@@ -1677,7 +1689,7 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
 	bool		result;
 	PrivateRefCountEntry *ref;
 
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	ref = GetPrivateRefCountEntry(b, true);
 
@@ -1760,7 +1772,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.
@@ -2501,7 +2513,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.
@@ -3555,7 +3567,7 @@ FlushRelationBuffers(Relation rel)
 
 		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
@@ -3650,7 +3662,7 @@ FlushRelationsAllBuffers(SMgrRelation *smgrs, int nrels)
 
 		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (RelFileNodeEquals(bufHdr->tag.rnode, srelent->rnode) &&
@@ -3705,7 +3717,7 @@ FlushDatabaseBuffers(Oid dbid)
 
 		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (bufHdr->tag.rnode.dbNode == dbid &&
@@ -3788,7 +3800,7 @@ void
 IncrBufferRefCount(Buffer buffer)
 {
 	Assert(BufferIsPinned(buffer));
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	if (BufferIsLocal(buffer))
 		LocalRefCount[-buffer - 1]++;
 	else
@@ -4835,3 +4847,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 f7c15ea8a4..3f22d7127b 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -22,7 +22,7 @@
 #include "storage/bufmgr.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 
 
 /*#define LBDEBUG*/
@@ -124,7 +124,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 f9cda6906d..be5576451d 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -99,7 +99,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)
@@ -350,6 +350,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
  */
@@ -1529,7 +1547,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
@@ -1713,7 +1731,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
@@ -1845,7 +1863,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
@@ -1885,7 +1903,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);
 
@@ -3870,3 +3888,19 @@ pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
 
 	return sum;
 }
+
+/*
+ * 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 b461a5f7e9..77b6e58fd9 100644
--- a/src/backend/storage/ipc/dsm.c
+++ b/src/backend/storage/ipc/dsm.c
@@ -37,13 +37,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
 
@@ -139,6 +141,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.
  *
@@ -900,7 +921,7 @@ void
 dsm_unpin_mapping(dsm_segment *seg)
 {
 	Assert(seg->resowner == NULL);
-	ResourceOwnerEnlargeDSMs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	seg->resowner = CurrentResourceOwner;
 	ResourceOwnerRememberDSM(seg->resowner, seg);
 }
@@ -1167,7 +1188,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);
@@ -1246,3 +1267,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 364654e106..933f9121be 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 13eed58760..ed046db57e 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
@@ -1272,7 +1309,7 @@ SearchCatCacheInternal(CatCache *cache,
 		 */
 		if (!ct->negative)
 		{
-			ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner);
+			ResourceOwnerEnlarge(CurrentResourceOwner);
 			ct->refcount++;
 			ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple);
 
@@ -1373,7 +1410,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 */
@@ -1585,7 +1622,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);
 
@@ -1697,7 +1734,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);
@@ -2071,14 +2108,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));
 
@@ -2092,9 +2134,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 6767eae8f2..a10362629f 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;
 
@@ -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);
 	}
@@ -2205,3 +2225,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 13d9994af3..90d2892489 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -77,13 +77,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"
 
@@ -2056,6 +2057,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.
@@ -2067,7 +2086,7 @@ RelationIdGetRelation(Oid relationId)
 void
 RelationIncrementReferenceCount(Relation rel)
 {
-	ResourceOwnerEnlargeRelationRefs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	rel->rd_refcnt += 1;
 	if (!IsBootstrapProcessingMode())
 		ResourceOwnerRememberRelationRef(CurrentResourceOwner, rel);
@@ -6546,3 +6565,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 2998f6bb36..4ed13afa10 100644
--- a/src/backend/utils/resowner/README
+++ b/src/backend/utils/resowner/README
@@ -60,13 +60,18 @@ subtransaction or portal.  Therefore, the "release" operation on a child
 ResourceOwner transfers lock ownership to the parent instead of actually
 releasing the lock, if isCommit is true.
 
-Currently, ResourceOwners contain direct support for recording ownership of
-buffer pins, lmgr locks, and catcache, relcache, plancache, tupdesc, and
-snapshot references.  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.
+As of this writing, it's used internally for buffer pins, lmgr locks, and
+catcache, relcache, plancache, tupdesc, snapshot, DSM and JIT context
+references. ResourceOwner treats all resources the same, and extensions
+can define new kinds of resources by filling in a ResourceOwnerFuncs
+struct with a 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. This used to be the only way to register new kinds
+of objects with a resource owner; nowadays it easier to write custom
+ResourceOwnerFuncs callbacks.
 
 Whenever we are inside a transaction, the global variable
 CurrentResourceOwner shows which resource owner should be assigned
diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index e24f00f060..d62b1495dd 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;
+} 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 8
 
 /*
- * 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 32
 
 /*
- * 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,315 @@ 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 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
- */
 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;
+
+	idx = hash_resource_elem(value, kind) & mask;
+	for (;;)
+	{
+		if (owner->hash[idx].kind == NULL)
+			break;
+		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;
-
-	if (resarr->nitems < resarr->maxitems)
-		return;					/* no work needed */
+	bool		found;
+	int			capacity;
 
-	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. */
+	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 */
+	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 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
- *
- * Returns true on success, false if resource was not found.
+ * Make sure there is room for at least one more resource in an array.
  *
- * Note: if same resource ID appears more than once, one instance is removed.
+ * 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.
  */
-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.
+ * Remember that an object is owner by a ReourceOwner
  *
- * "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.
- *
- * 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++;
-		}
-	}
+	Assert(owner->narr < RESOWNER_ARRAY_SIZE);
 
-	*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
+ *
+ * Returns true on success. If the resource was not found, returns false,
+ * and calls kind->ForgetError callback.
+ *
+ * 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 +497,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
@@ -501,7 +517,6 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 	ResourceOwner child;
 	ResourceOwner save;
 	ResourceReleaseCallbackItem *item;
-	Datum		foundres;
 
 	/* Recurse to handle descendants */
 	for (child = owner->firstchild; child != NULL; child = child->nextchild)
@@ -517,71 +532,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 references 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 references ---
+		 * 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 *) PointerGetDatum(foundres);
-
-			jit_release_context(context);
-		}
-
-		/* Ditto for cryptohash contexts */
-		while (ResourceArrayGetAny(&(owner->cryptohasharr), &foundres))
-		{
-			pg_cryptohash_ctx *context =
-			(pg_cryptohash_ctx *) PointerGetDatum(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 *) PointerGetDatum(foundres);
-
-			if (isCommit)
-				PrintHMACLeakWarning(foundres);
-			pg_hmac_free(context);
-		}
+		ResourceOwnerReleaseAll(owner, phase, isCommit);
 	}
 	else if (phase == RESOURCE_RELEASE_LOCKS)
 	{
@@ -590,7 +547,7 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 			/*
 			 * For a top-level xact we are going to release all locks (or at
 			 * least all non-session locks), so just do a single lmgr call at
-			 * the top of the recursion.
+			 * the top of the recursion
 			 */
 			if (owner == TopTransactionResourceOwner)
 			{
@@ -634,70 +591,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 references 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 */
@@ -717,13 +613,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);
+		}
 	}
 }
 
@@ -740,20 +665,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.
@@ -769,19 +689,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);
 }
 
@@ -839,11 +748,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)
@@ -934,58 +842,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 */
@@ -1023,469 +893,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 2968c7f7b7..1f46cbe58c 100644
--- a/src/backend/utils/time/snapmgr.c
+++ b/src/backend/utils/time/snapmgr.c
@@ -66,7 +66,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"
@@ -174,6 +174,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.
  *
@@ -831,7 +849,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);
 
@@ -2349,3 +2367,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 643cc7aea2..cf5fc9eacb 100644
--- a/src/common/cryptohash_openssl.c
+++ b/src/common/cryptohash_openssl.c
@@ -30,7 +30,6 @@
 #ifndef FRONTEND
 #include "utils/memutils.h"
 #include "utils/resowner.h"
-#include "utils/resowner_private.h"
 #endif
 
 /*
@@ -63,6 +62,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
+
 /*
  * pg_cryptohash_create
  *
@@ -80,7 +100,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));
@@ -109,8 +129,7 @@ pg_cryptohash_create(pg_cryptohash_type type)
 
 #ifndef FRONTEND
 	ctx->resowner = CurrentResourceOwner;
-	ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
-									PointerGetDatum(ctx));
+	ResourceOwnerRememberCryptoHash(CurrentResourceOwner, ctx);
 #endif
 
 	return ctx;
@@ -241,10 +260,28 @@ 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));
 	FREE(ctx);
 }
+
+
+/*
+ * 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 1acf59476e..0a65284bae 100644
--- a/src/common/hmac_openssl.c
+++ b/src/common/hmac_openssl.c
@@ -29,7 +29,6 @@
 #ifndef FRONTEND
 #include "utils/memutils.h"
 #include "utils/resowner.h"
-#include "utils/resowner_private.h"
 #endif
 
 /*
@@ -63,6 +62,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
+
 /*
  * pg_hmac_create
  *
@@ -86,7 +106,7 @@ pg_hmac_create(pg_cryptohash_type type)
 	 */
 #ifdef HAVE_HMAC_CTX_NEW
 #ifndef FRONTEND
-	ResourceOwnerEnlargeHMAC(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 #endif
 	ctx->hmacctx = HMAC_CTX_new();
 #else
@@ -108,7 +128,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));
@@ -244,7 +264,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));
@@ -254,3 +274,22 @@ pg_hmac_free(pg_hmac_ctx *ctx)
 	explicit_bzero(ctx, sizeof(pg_hmac_ctx));
 	FREE(ctx);
 }
+
+
+/*
+ * 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 33fcaf5c9a..10ef8d850e 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -300,6 +300,15 @@ typedef struct CkptSortItem
 
 extern 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 ddc2762eb3..2bf95c22e8 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 ff09c63a02..f3e3e50fb2 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 109ac31b24..d59d14c0d0 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 an resource 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 6dafc87e28..0000000000
--- 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-2021, 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 423780652f..5eaef46702 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2226,8 +2226,10 @@ ReplicationStateOnDisk
 ResTarget
 ReservoirState
 ReservoirStateData
-ResourceArray
+ResourceElem
 ResourceOwner
+ResourceOwnerData
+ResourceOwnerFuncs
 ResourceReleaseCallback
 ResourceReleaseCallbackItem
 ResourceReleasePhase
v9-0004-Minor-tweaks.patchapplication/octet-stream; name=v9-0004-Minor-tweaks.patchDownload
diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index 6189d05107..e6b136ca04 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -37,7 +37,7 @@
 typedef struct ResourceElem
 {
 	Datum		item;
-	ResourceOwnerFuncs *kind;
+	ResourceOwnerFuncs *kind; /* kind == NULL indicates a free hash table slot */
 } ResourceElem;
 
 /*
@@ -186,9 +186,14 @@ hash_resource_elem(Datum value, ResourceOwnerFuncs *kind)
 #endif
 }
 
+/*
+ * Adds 'value' of given 'kind' to the ResourceOwner's hash table
+ */
 static void
 ResourceOwnerAddToHash(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind)
 {
+	Assert(kind != NULL);
+
 	/* Insert into first free slot at or after hash location. */
 	uint32		mask = owner->capacity - 1;
 	uint32		idx;
@@ -197,7 +202,7 @@ ResourceOwnerAddToHash(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kin
 	for (;;)
 	{
 		if (owner->hash[idx].kind == NULL)
-			break;
+			break; /* free slot found */
 		idx = (idx + 1) & mask;
 	}
 	owner->hash[idx].item = value;
@@ -408,9 +413,6 @@ ResourceOwnerRemember(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind
 /*
  * Forget that an object is owned by a ResourceOwner
  *
- * Returns true on success. If the resource was not found, returns false,
- * and calls kind->ForgetError callback.
- *
  * Note: if same resource ID is associated with the ResourceOwner more than once,
  * one instance is removed.
  */
#40Michael Paquier
michael@paquier.xyz
In reply to: Aleksander Alekseev (#39)
Re: ResourceOwner refactoring

On Wed, Sep 08, 2021 at 01:19:19PM +0300, Aleksander Alekseev wrote:

Thanks for working on this!

The latest patch does not apply anymore, and has been waiting on
author for a couple of weeks now, so switched as RwF for now.
--
Michael

#41Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Aleksander Alekseev (#39)
3 attachment(s)
Re: ResourceOwner refactoring

On 08/09/2021 13:19, Aleksander Alekseev wrote:

Hi Heikki,

Yeah, needed some manual fixing, but here you go.

Thanks for working on this!

v8-0002 didn't apply to the current master, so I rebased it. See
attached v9-* patches. I also included v9-0004 with some minor tweaks
from me. I have several notes regarding the code.

Thanks!

Attached is a newly rebased version. It includes your tweaks, and a few
more comment and indentation tweaks.

1. Not sure if I understand this code of ResourceOwnerReleaseAll():
```
/* First handle all the entries in the array. */
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;

if (printLeakWarnings)
kind->PrintLeakWarning(value);
kind->ReleaseResource(value);
found = true;
}
}

/*
* 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.
*/
} while (found && owner->narr > 0);
```

Shouldn't we mark the resource as released and/or decrease narr and/or
save the last processed i? Why this will not call ReleaseResource()
endlessly on the same resource (array item)? Same question for the
following code that iterates over the hash table.

ReleaseResource() must call ResourceOwnerForget(), which removes the
item from the or hash table. This works the same as the code in 'master':

/*
* Release buffer pins. Note that ReleaseBuffer will remove the
* buffer entry from our array, so we just have to iterate till there
* are none.
*
* 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.
*/
while (ResourceArrayGetAny(&(owner->bufferarr), &foundres))
{
Buffer res = DatumGetBuffer(foundres);

if (isCommit)
PrintBufferLeakWarning(res);
ReleaseBuffer(res);
}

That comment explains how it works. I added a comment like that in this
patch, too.

2. Just an idea/observation. It's possible that the performance of
ResourceOwnerEnlarge() can be slightly improved. Since the size of the
hash table is always a power of 2 and the code always doubles the size
of the hash table, (idx & mask) value will get one extra bit, which
can be 0 or 1. If it's 0, the value is already in its place,
otherwise, it should be moved on the known distance. In other words,
it's possible to copy `oldhash` to `newhash` and then move only half
of the items. I don't claim that this code necessarily should be
faster, or that this should be checked in the scope of this work.

Hmm, the hash table uses open addressing, I think we want to also
rearrange any existing entries that might not be at their right place
because of collisions. We don't actually do that currently when an
element is removed (even before this patch), but enlarging the array is
a good opportunity for it. In any case, I haven't seen
ResourceOwnerEnlarge() show up while profiling, so I'm going to leave it
as it is.

- Heikki

Attachments:

v10-0001-Move-a-few-ResourceOwnerEnlarge-calls-for-safety.patchtext/x-patch; charset=UTF-8; name=v10-0001-Move-a-few-ResourceOwnerEnlarge-calls-for-safety.patchDownload
From 98758f3774484f54f010d4bec663ac60b764170f Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 13 Jan 2021 12:21:28 +0200
Subject: [PATCH v10 1/3] Move a few ResourceOwnerEnlarge() calls for safety
 and clarity.

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
inbetween, 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.
---
 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 e88e4e918b0..cde9184c7d6 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -810,9 +810,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,
@@ -1174,9 +1171,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 resoure owner has room to remember the
+		 * pin.
 		 */
 		ReservePrivateRefCountEntry();
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
 
 		/*
 		 * Select a victim buffer.  The buffer is returned with its header
@@ -1677,8 +1676,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.
  */
@@ -1689,6 +1686,8 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
 	bool		result;
 	PrivateRefCountEntry *ref;
 
+	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+
 	ref = GetPrivateRefCountEntry(b, true);
 
 	if (ref == NULL)
@@ -1769,7 +1768,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.
@@ -1945,9 +1945,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
@@ -2421,9 +2418,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;
@@ -2505,8 +2499,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)
@@ -2516,7 +2508,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.
@@ -3555,9 +3549,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;
@@ -3571,7 +3562,9 @@ FlushRelationBuffers(Relation rel)
 		if (!RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node))
 			continue;
 
+		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
@@ -3628,9 +3621,6 @@ FlushRelationsAllBuffers(SMgrRelation *smgrs, int nrels)
 	if (use_bsearch)
 		pg_qsort(srels, nrels, sizeof(SMgrSortArray), rnode_comparator);
 
-	/* Make sure we can handle the pin inside the loop */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
-
 	for (i = 0; i < NBuffers; i++)
 	{
 		SMgrSortArray *srelent = NULL;
@@ -3667,7 +3657,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 (RelFileNodeEquals(bufHdr->tag.rnode, srelent->rnode) &&
@@ -3707,9 +3699,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;
@@ -3723,7 +3712,9 @@ FlushDatabaseBuffers(Oid dbid)
 		if (bufHdr->tag.rnode.dbNode != dbid)
 			continue;
 
+		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (bufHdr->tag.rnode.dbNode == dbid &&
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index 04b3558ea33..f7c15ea8a44 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -123,6 +123,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, (void *) &newTag, HASH_FIND, NULL);
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 4fbdc62d8c7..13eed587601 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1609,8 +1609,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();
@@ -1698,13 +1696,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

v10-0002-Make-resowners-more-easily-extensible.patchtext/x-patch; charset=UTF-8; name=v10-0002-Make-resowners-more-easily-extensible.patchDownload
From 4b5dc405486ecf077e26cb3095ee2b5d28dee833 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 20 Jan 2021 23:16:07 +0200
Subject: [PATCH v10 2/3] Make resowners more easily extensible.

Use a single array and hash, instead of one for each object kind.
---
 src/backend/access/common/tupdesc.c   |   42 +-
 src/backend/jit/jit.c                 |    2 -
 src/backend/jit/llvm/llvmjit.c        |   46 +-
 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     |   17 +-
 src/backend/utils/resowner/resowner.c | 1323 +++++++------------------
 src/backend/utils/time/snapmgr.c      |   38 +-
 src/common/cryptohash_openssl.c       |   49 +-
 src/common/hmac_openssl.c             |   47 +-
 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, 907 insertions(+), 1133 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 4c63bd4dc64..f2d4442c938 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);
 }
@@ -910,3 +928,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 91b8ae6c51a..d42d1bc6c48 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 169dad96d76..c4c766d8937 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,8 +121,26 @@ static LLVMOrcLLJITRef llvm_create_jit_instance(LLVMTargetMachineRef tm);
 static char *llvm_error_message(LLVMErrorRef error);
 #endif							/* LLVM_VERSION_MAJOR > 11 */
 
-PG_MODULE_MAGIC;
+/* 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;
 
 /*
  * Initialize LLVM JIT provider.
@@ -151,7 +169,7 @@ llvm_create_context(int jitFlags)
 
 	llvm_session_initialize();
 
-	ResourceOwnerEnlargeJIT(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	context = MemoryContextAllocZero(TopMemoryContext,
 									 sizeof(LLVMJitContext));
@@ -159,7 +177,7 @@ llvm_create_context(int jitFlags)
 
 	/* ensure cleanup */
 	context->base.resowner = CurrentResourceOwner;
-	ResourceOwnerRememberJIT(CurrentResourceOwner, PointerGetDatum(context));
+	ResourceOwnerRememberJIT(CurrentResourceOwner, context);
 
 	return context;
 }
@@ -221,6 +239,8 @@ llvm_release_context(JitContext *context)
 
 		pfree(jit_handle);
 	}
+
+	ResourceOwnerForgetJIT(context->resowner, context);
 }
 
 /*
@@ -1248,3 +1268,21 @@ llvm_error_message(LLVMErrorRef error)
 }
 
 #endif							/* LLVM_VERSION_MAJOR > 11 */
+
+/*
+ * ResourceOwner callbacks
+ */
+static void
+ResOwnerReleaseJitContext(Datum res)
+{
+	jit_release_context((JitContext *) PointerGetDatum(res));
+}
+
+static void
+ResOwnerPrintJitContextLeakWarning(Datum res)
+{
+	/* XXX: We used to not print these. Was that intentional? */
+	JitContext *context = (JitContext *) PointerGetDatum(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 cde9184c7d6..d563df548ff 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -52,7 +52,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"
 
 
@@ -206,6 +206,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
@@ -625,7 +637,7 @@ ReadRecentBuffer(RelFileNode rnode, ForkNumber forkNum, BlockNumber blockNum,
 
 	Assert(BufferIsValid(recent_buffer));
 
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	ReservePrivateRefCountEntry();
 	INIT_BUFFERTAG(tag, rnode, forkNum, blockNum);
 
@@ -1175,7 +1187,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
@@ -1686,7 +1698,7 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
 	bool		result;
 	PrivateRefCountEntry *ref;
 
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	ref = GetPrivateRefCountEntry(b, true);
 
@@ -1769,7 +1781,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.
@@ -2510,7 +2522,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.
@@ -3564,7 +3576,7 @@ FlushRelationBuffers(Relation rel)
 
 		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
@@ -3659,7 +3671,7 @@ FlushRelationsAllBuffers(SMgrRelation *smgrs, int nrels)
 
 		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (RelFileNodeEquals(bufHdr->tag.rnode, srelent->rnode) &&
@@ -3714,7 +3726,7 @@ FlushDatabaseBuffers(Oid dbid)
 
 		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (bufHdr->tag.rnode.dbNode == dbid &&
@@ -3797,7 +3809,7 @@ void
 IncrBufferRefCount(Buffer buffer)
 {
 	Assert(BufferIsPinned(buffer));
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	if (BufferIsLocal(buffer))
 		LocalRefCount[-buffer - 1]++;
 	else
@@ -4844,3 +4856,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 f7c15ea8a44..3f22d7127b9 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -22,7 +22,7 @@
 #include "storage/bufmgr.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 
 
 /*#define LBDEBUG*/
@@ -124,7 +124,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 f9cda6906d2..be5576451d5 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -99,7 +99,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)
@@ -350,6 +350,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
  */
@@ -1529,7 +1547,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
@@ -1713,7 +1731,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
@@ -1845,7 +1863,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
@@ -1885,7 +1903,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);
 
@@ -3870,3 +3888,19 @@ pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
 
 	return sum;
 }
+
+/*
+ * 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 b461a5f7e96..77b6e58fd9d 100644
--- a/src/backend/storage/ipc/dsm.c
+++ b/src/backend/storage/ipc/dsm.c
@@ -37,13 +37,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
 
@@ -139,6 +141,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.
  *
@@ -900,7 +921,7 @@ void
 dsm_unpin_mapping(dsm_segment *seg)
 {
 	Assert(seg->resowner == NULL);
-	ResourceOwnerEnlargeDSMs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	seg->resowner = CurrentResourceOwner;
 	ResourceOwnerRememberDSM(seg->resowner, seg);
 }
@@ -1167,7 +1188,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);
@@ -1246,3 +1267,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 364654e1060..933f9121be5 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 13eed587601..ed046db57ec 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
@@ -1272,7 +1309,7 @@ SearchCatCacheInternal(CatCache *cache,
 		 */
 		if (!ct->negative)
 		{
-			ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner);
+			ResourceOwnerEnlarge(CurrentResourceOwner);
 			ct->refcount++;
 			ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple);
 
@@ -1373,7 +1410,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 */
@@ -1585,7 +1622,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);
 
@@ -1697,7 +1734,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);
@@ -2071,14 +2108,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));
 
@@ -2092,9 +2134,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 6767eae8f20..a10362629f8 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;
 
@@ -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);
 	}
@@ -2205,3 +2225,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 13d9994af3e..90d2892489f 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -77,13 +77,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"
 
@@ -2056,6 +2057,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.
@@ -2067,7 +2086,7 @@ RelationIdGetRelation(Oid relationId)
 void
 RelationIncrementReferenceCount(Relation rel)
 {
-	ResourceOwnerEnlargeRelationRefs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	rel->rd_refcnt += 1;
 	if (!IsBootstrapProcessingMode())
 		ResourceOwnerRememberRelationRef(CurrentResourceOwner, rel);
@@ -6546,3 +6565,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..adaa4a9d00e 100644
--- a/src/backend/utils/resowner/README
+++ b/src/backend/utils/resowner/README
@@ -54,12 +54,17 @@ 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.  This used to be the only way to register new kinds
+of objects with a resource owner; 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 e24f00f0601..35e824d4534 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 8
 
 /*
- * 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 32
 
 /*
- * 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,332 @@ 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
- *
- * Returns true on success, false if resource was not found.
+ * Make sure there is room for at least one more resource in an array.
  *
- * Note: if same resource ID appears more than once, one instance is removed.
+ * 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.
  */
-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.
+ * Remember that an object is owner by a ReourceOwner
  *
- * "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.
- *
- * 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++;
-		}
-	}
+	Assert(owner->narr < RESOWNER_ARRAY_SIZE);
 
-	*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 +514,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
@@ -501,7 +534,6 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 	ResourceOwner child;
 	ResourceOwner save;
 	ResourceReleaseCallbackItem *item;
-	Datum		foundres;
 
 	/* Recurse to handle descendants */
 	for (child = owner->firstchild; child != NULL; child = child->nextchild)
@@ -517,71 +549,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 *) PointerGetDatum(foundres);
-
-			jit_release_context(context);
-		}
-
-		/* Ditto for cryptohash contexts */
-		while (ResourceArrayGetAny(&(owner->cryptohasharr), &foundres))
-		{
-			pg_cryptohash_ctx *context =
-			(pg_cryptohash_ctx *) PointerGetDatum(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 *) PointerGetDatum(foundres);
-
-			if (isCommit)
-				PrintHMACLeakWarning(foundres);
-			pg_hmac_free(context);
-		}
+		ResourceOwnerReleaseAll(owner, phase, isCommit);
 	}
 	else if (phase == RESOURCE_RELEASE_LOCKS)
 	{
@@ -634,70 +608,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 */
@@ -717,13 +630,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);
 
-		ReleaseCachedPlan(res, owner);
+			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);
+		}
 	}
 }
 
@@ -740,20 +682,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.
@@ -769,19 +706,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);
 }
 
@@ -839,11 +765,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)
@@ -934,58 +859,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 */
@@ -1023,469 +910,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 2968c7f7b7d..1f46cbe58c9 100644
--- a/src/backend/utils/time/snapmgr.c
+++ b/src/backend/utils/time/snapmgr.c
@@ -66,7 +66,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"
@@ -174,6 +174,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.
  *
@@ -831,7 +849,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);
 
@@ -2349,3 +2367,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 643cc7aea2c..cf5fc9eacb4 100644
--- a/src/common/cryptohash_openssl.c
+++ b/src/common/cryptohash_openssl.c
@@ -30,7 +30,6 @@
 #ifndef FRONTEND
 #include "utils/memutils.h"
 #include "utils/resowner.h"
-#include "utils/resowner_private.h"
 #endif
 
 /*
@@ -63,6 +62,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
+
 /*
  * pg_cryptohash_create
  *
@@ -80,7 +100,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));
@@ -109,8 +129,7 @@ pg_cryptohash_create(pg_cryptohash_type type)
 
 #ifndef FRONTEND
 	ctx->resowner = CurrentResourceOwner;
-	ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
-									PointerGetDatum(ctx));
+	ResourceOwnerRememberCryptoHash(CurrentResourceOwner, ctx);
 #endif
 
 	return ctx;
@@ -241,10 +260,28 @@ 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));
 	FREE(ctx);
 }
+
+
+/*
+ * 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 1acf59476eb..0a65284bae9 100644
--- a/src/common/hmac_openssl.c
+++ b/src/common/hmac_openssl.c
@@ -29,7 +29,6 @@
 #ifndef FRONTEND
 #include "utils/memutils.h"
 #include "utils/resowner.h"
-#include "utils/resowner_private.h"
 #endif
 
 /*
@@ -63,6 +62,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
+
 /*
  * pg_hmac_create
  *
@@ -86,7 +106,7 @@ pg_hmac_create(pg_cryptohash_type type)
 	 */
 #ifdef HAVE_HMAC_CTX_NEW
 #ifndef FRONTEND
-	ResourceOwnerEnlargeHMAC(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 #endif
 	ctx->hmacctx = HMAC_CTX_new();
 #else
@@ -108,7 +128,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));
@@ -244,7 +264,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));
@@ -254,3 +274,22 @@ pg_hmac_free(pg_hmac_ctx *ctx)
 	explicit_bzero(ctx, sizeof(pg_hmac_ctx));
 	FREE(ctx);
 }
+
+
+/*
+ * 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 33fcaf5c9a8..10ef8d850e4 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -300,6 +300,15 @@ typedef struct CkptSortItem
 
 extern 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 ddc2762eb3f..2bf95c22e84 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 ff09c63a02f..f3e3e50fb21 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 109ac31b248..d59d14c0d01 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 an resource 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 6dafc87e28c..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-2021, 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 402a6617a98..8a14175f406 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2227,8 +2227,10 @@ ReplicationStateOnDisk
 ResTarget
 ReservoirState
 ReservoirStateData
-ResourceArray
+ResourceElem
 ResourceOwner
+ResourceOwnerData
+ResourceOwnerFuncs
 ResourceReleaseCallback
 ResourceReleaseCallbackItem
 ResourceReleasePhase
-- 
2.30.2

v10-0003-Optimize-hash-function.patchtext/x-patch; charset=UTF-8; name=v10-0003-Optimize-hash-function.patchDownload
From 89d41607292cd2430bffb258ab42cd7045335ded Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Mon, 18 Oct 2021 13:59:11 +0300
Subject: [PATCH v10 3/3] Optimize hash function

---
 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 35e824d4534..118bd2b34c9 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -169,15 +169,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 resources types 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 c634cc067a1..009ffbbdd38 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

#42Aleksander Alekseev
aleksander@timescale.com
In reply to: Heikki Linnakangas (#41)
3 attachment(s)
Re: ResourceOwner refactoring

Hi Heikki,

Attached is a newly rebased version. It includes your tweaks, and a few
more comment and indentation tweaks.

v10-0002 rotted a little so I rebased it. The new patch set passed `make
installcheck-world` locally, but let's see if cfbot has a second opinion.

I'm a bit busy with various things at the moment, so (hopefully) I will
submit the actual code review in the follow-up email.

--
Best regards,
Aleksander Alekseev

Attachments:

v11-0001-Move-a-few-ResourceOwnerEnlarge-calls-for-safety.patchapplication/octet-stream; name=v11-0001-Move-a-few-ResourceOwnerEnlarge-calls-for-safety.patchDownload
From 98758f3774484f54f010d4bec663ac60b764170f Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 13 Jan 2021 12:21:28 +0200
Subject: [PATCH v10 1/3] Move a few ResourceOwnerEnlarge() calls for safety
 and clarity.

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
inbetween, 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.
---
 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 e88e4e918b0..cde9184c7d6 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -810,9 +810,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,
@@ -1174,9 +1171,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 resoure owner has room to remember the
+		 * pin.
 		 */
 		ReservePrivateRefCountEntry();
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
 
 		/*
 		 * Select a victim buffer.  The buffer is returned with its header
@@ -1677,8 +1676,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.
  */
@@ -1689,6 +1686,8 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
 	bool		result;
 	PrivateRefCountEntry *ref;
 
+	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+
 	ref = GetPrivateRefCountEntry(b, true);
 
 	if (ref == NULL)
@@ -1769,7 +1768,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.
@@ -1945,9 +1945,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
@@ -2421,9 +2418,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;
@@ -2505,8 +2499,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)
@@ -2516,7 +2508,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.
@@ -3555,9 +3549,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;
@@ -3571,7 +3562,9 @@ FlushRelationBuffers(Relation rel)
 		if (!RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node))
 			continue;
 
+		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
@@ -3628,9 +3621,6 @@ FlushRelationsAllBuffers(SMgrRelation *smgrs, int nrels)
 	if (use_bsearch)
 		pg_qsort(srels, nrels, sizeof(SMgrSortArray), rnode_comparator);
 
-	/* Make sure we can handle the pin inside the loop */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
-
 	for (i = 0; i < NBuffers; i++)
 	{
 		SMgrSortArray *srelent = NULL;
@@ -3667,7 +3657,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 (RelFileNodeEquals(bufHdr->tag.rnode, srelent->rnode) &&
@@ -3707,9 +3699,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;
@@ -3723,7 +3712,9 @@ FlushDatabaseBuffers(Oid dbid)
 		if (bufHdr->tag.rnode.dbNode != dbid)
 			continue;
 
+		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (bufHdr->tag.rnode.dbNode == dbid &&
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index 04b3558ea33..f7c15ea8a44 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -123,6 +123,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, (void *) &newTag, HASH_FIND, NULL);
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 4fbdc62d8c7..13eed587601 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1609,8 +1609,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();
@@ -1698,13 +1696,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

v11-0002-Make-resowners-more-easily-extensible.patchapplication/octet-stream; name=v11-0002-Make-resowners-more-easily-extensible.patchDownload
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 4c63bd4dc6..f2d4442c93 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);
 }
@@ -910,3 +928,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 91b8ae6c51..d42d1bc6c4 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 fb29449573..8e84ac5de6 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);
 }
 
 /*
@@ -1248,3 +1269,20 @@ llvm_error_message(LLVMErrorRef error)
 }
 
 #endif							/* LLVM_VERSION_MAJOR > 11 */
+/*
+ * ResourceOwner callbacks
+ */
+static void
+ResOwnerReleaseJitContext(Datum res)
+{
+	jit_release_context((JitContext *) PointerGetDatum(res));
+}
+
+static void
+ResOwnerPrintJitContextLeakWarning(Datum res)
+{
+	/* XXX: We used to not print these. Was that intentional? */
+	JitContext *context = (JitContext *) PointerGetDatum(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 67c5a12211..d347d428a9 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -52,7 +52,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"
 
 
@@ -206,6 +206,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
@@ -625,7 +637,7 @@ ReadRecentBuffer(RelFileNode rnode, ForkNumber forkNum, BlockNumber blockNum,
 
 	Assert(BufferIsValid(recent_buffer));
 
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	ReservePrivateRefCountEntry();
 	INIT_BUFFERTAG(tag, rnode, forkNum, blockNum);
 
@@ -1175,7 +1187,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
@@ -1686,7 +1698,7 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
 	bool		result;
 	PrivateRefCountEntry *ref;
 
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	ref = GetPrivateRefCountEntry(b, true);
 
@@ -1769,7 +1781,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.
@@ -2510,7 +2522,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.
@@ -3564,7 +3576,7 @@ FlushRelationBuffers(Relation rel)
 
 		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
@@ -3659,7 +3671,7 @@ FlushRelationsAllBuffers(SMgrRelation *smgrs, int nrels)
 
 		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (RelFileNodeEquals(bufHdr->tag.rnode, srelent->rnode) &&
@@ -3714,7 +3726,7 @@ FlushDatabaseBuffers(Oid dbid)
 
 		/* Make sure we can handle the pin */
 		ReservePrivateRefCountEntry();
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		buf_state = LockBufHdr(bufHdr);
 		if (bufHdr->tag.rnode.dbNode == dbid &&
@@ -3797,7 +3809,7 @@ void
 IncrBufferRefCount(Buffer buffer)
 {
 	Assert(BufferIsPinned(buffer));
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	if (BufferIsLocal(buffer))
 		LocalRefCount[-buffer - 1]++;
 	else
@@ -4844,3 +4856,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 f7c15ea8a4..3f22d7127b 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -22,7 +22,7 @@
 #include "storage/bufmgr.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 
 
 /*#define LBDEBUG*/
@@ -124,7 +124,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 cb1a8dd34f..3991d7b2ba 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -100,7 +100,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)
@@ -351,6 +351,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
  */
@@ -1530,7 +1548,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
@@ -1714,7 +1732,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
@@ -1846,7 +1864,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
@@ -1886,7 +1904,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);
 
@@ -3889,3 +3907,19 @@ pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
 
 	return sum;
 }
+
+/*
+ * 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 b461a5f7e9..77b6e58fd9 100644
--- a/src/backend/storage/ipc/dsm.c
+++ b/src/backend/storage/ipc/dsm.c
@@ -37,13 +37,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
 
@@ -139,6 +141,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.
  *
@@ -900,7 +921,7 @@ void
 dsm_unpin_mapping(dsm_segment *seg)
 {
 	Assert(seg->resowner == NULL);
-	ResourceOwnerEnlargeDSMs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	seg->resowner = CurrentResourceOwner;
 	ResourceOwnerRememberDSM(seg->resowner, seg);
 }
@@ -1167,7 +1188,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);
@@ -1246,3 +1267,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 c25af7fe09..a73abacf68 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 13eed58760..ed046db57e 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
@@ -1272,7 +1309,7 @@ SearchCatCacheInternal(CatCache *cache,
 		 */
 		if (!ct->negative)
 		{
-			ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner);
+			ResourceOwnerEnlarge(CurrentResourceOwner);
 			ct->refcount++;
 			ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple);
 
@@ -1373,7 +1410,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 */
@@ -1585,7 +1622,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);
 
@@ -1697,7 +1734,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);
@@ -2071,14 +2108,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));
 
@@ -2092,9 +2134,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 6767eae8f2..a10362629f 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;
 
@@ -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);
 	}
@@ -2205,3 +2225,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 9fa9e671a1..1f7382ad32 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -77,13 +77,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"
 
@@ -2107,6 +2108,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.
@@ -2118,7 +2137,7 @@ RelationIdGetRelation(Oid relationId)
 void
 RelationIncrementReferenceCount(Relation rel)
 {
-	ResourceOwnerEnlargeRelationRefs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	rel->rd_refcnt += 1;
 	if (!IsBootstrapProcessingMode())
 		ResourceOwnerRememberRelationRef(CurrentResourceOwner, rel);
@@ -6656,3 +6675,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 f94c9700df..adaa4a9d00 100644
--- a/src/backend/utils/resowner/README
+++ b/src/backend/utils/resowner/README
@@ -54,12 +54,17 @@ 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.  This used to be the only way to register new kinds
+of objects with a resource owner; 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 e24f00f060..35e824d453 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 8
 
 /*
- * 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 32
 
 /*
- * 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,332 @@ 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
- *
- * Returns true on success, false if resource was not found.
+ * Make sure there is room for at least one more resource in an array.
  *
- * Note: if same resource ID appears more than once, one instance is removed.
+ * 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.
  */
-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.
+ * Remember that an object is owner by a ReourceOwner
  *
- * "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.
- *
- * 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++;
-		}
-	}
+	Assert(owner->narr < RESOWNER_ARRAY_SIZE);
 
-	*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 +514,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
@@ -501,7 +534,6 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 	ResourceOwner child;
 	ResourceOwner save;
 	ResourceReleaseCallbackItem *item;
-	Datum		foundres;
 
 	/* Recurse to handle descendants */
 	for (child = owner->firstchild; child != NULL; child = child->nextchild)
@@ -517,71 +549,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 *) PointerGetDatum(foundres);
-
-			jit_release_context(context);
-		}
-
-		/* Ditto for cryptohash contexts */
-		while (ResourceArrayGetAny(&(owner->cryptohasharr), &foundres))
-		{
-			pg_cryptohash_ctx *context =
-			(pg_cryptohash_ctx *) PointerGetDatum(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 *) PointerGetDatum(foundres);
-
-			if (isCommit)
-				PrintHMACLeakWarning(foundres);
-			pg_hmac_free(context);
-		}
+		ResourceOwnerReleaseAll(owner, phase, isCommit);
 	}
 	else if (phase == RESOURCE_RELEASE_LOCKS)
 	{
@@ -634,70 +608,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 */
@@ -717,13 +630,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);
 
-		ReleaseCachedPlan(res, owner);
+			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);
+		}
 	}
 }
 
@@ -740,20 +682,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.
@@ -769,19 +706,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);
 }
 
@@ -839,11 +765,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)
@@ -934,58 +859,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 */
@@ -1023,469 +910,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 5001efdf7a..1a8a3dd105 100644
--- a/src/backend/utils/time/snapmgr.c
+++ b/src/backend/utils/time/snapmgr.c
@@ -66,7 +66,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"
@@ -174,6 +174,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.
  *
@@ -846,7 +864,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);
 
@@ -2364,3 +2382,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 643cc7aea2..cf5fc9eacb 100644
--- a/src/common/cryptohash_openssl.c
+++ b/src/common/cryptohash_openssl.c
@@ -30,7 +30,6 @@
 #ifndef FRONTEND
 #include "utils/memutils.h"
 #include "utils/resowner.h"
-#include "utils/resowner_private.h"
 #endif
 
 /*
@@ -63,6 +62,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
+
 /*
  * pg_cryptohash_create
  *
@@ -80,7 +100,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));
@@ -109,8 +129,7 @@ pg_cryptohash_create(pg_cryptohash_type type)
 
 #ifndef FRONTEND
 	ctx->resowner = CurrentResourceOwner;
-	ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
-									PointerGetDatum(ctx));
+	ResourceOwnerRememberCryptoHash(CurrentResourceOwner, ctx);
 #endif
 
 	return ctx;
@@ -241,10 +260,28 @@ 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));
 	FREE(ctx);
 }
+
+
+/*
+ * 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 1acf59476e..0a65284bae 100644
--- a/src/common/hmac_openssl.c
+++ b/src/common/hmac_openssl.c
@@ -29,7 +29,6 @@
 #ifndef FRONTEND
 #include "utils/memutils.h"
 #include "utils/resowner.h"
-#include "utils/resowner_private.h"
 #endif
 
 /*
@@ -63,6 +62,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
+
 /*
  * pg_hmac_create
  *
@@ -86,7 +106,7 @@ pg_hmac_create(pg_cryptohash_type type)
 	 */
 #ifdef HAVE_HMAC_CTX_NEW
 #ifndef FRONTEND
-	ResourceOwnerEnlargeHMAC(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 #endif
 	ctx->hmacctx = HMAC_CTX_new();
 #else
@@ -108,7 +128,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));
@@ -244,7 +264,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));
@@ -254,3 +274,22 @@ pg_hmac_free(pg_hmac_ctx *ctx)
 	explicit_bzero(ctx, sizeof(pg_hmac_ctx));
 	FREE(ctx);
 }
+
+
+/*
+ * 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 33fcaf5c9a..10ef8d850e 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -300,6 +300,15 @@ typedef struct CkptSortItem
 
 extern 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 ddc2762eb3..2bf95c22e8 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 ff09c63a02..f3e3e50fb2 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 109ac31b24..d59d14c0d0 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 an resource 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 6dafc87e28..0000000000
--- 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-2021, 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 da6ac8ed83..115f9983f6 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2234,8 +2234,10 @@ ReplicationStateOnDisk
 ResTarget
 ReservoirState
 ReservoirStateData
-ResourceArray
+ResourceElem
 ResourceOwner
+ResourceOwnerData
+ResourceOwnerFuncs
 ResourceReleaseCallback
 ResourceReleaseCallbackItem
 ResourceReleasePhase
v11-0003-Optimize-hash-function.patchapplication/octet-stream; name=v11-0003-Optimize-hash-function.patchDownload
From 89d41607292cd2430bffb258ab42cd7045335ded Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Mon, 18 Oct 2021 13:59:11 +0300
Subject: [PATCH v10 3/3] Optimize hash function

---
 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 35e824d4534..118bd2b34c9 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -169,15 +169,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 resources types 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 c634cc067a1..009ffbbdd38 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

#43Aleksander Alekseev
aleksander@timescale.com
In reply to: Aleksander Alekseev (#42)
Re: ResourceOwner refactoring

Hi Heikki,

I will submit the actual code review in the follow-up email

The patchset is in a good shape. I'm changing the status to "Ready for
Committer".

--
Best regards,
Aleksander Alekseev

#44Julien Rouhaud
rjuju123@gmail.com
In reply to: Aleksander Alekseev (#43)
Re: ResourceOwner refactoring

Hi,

On Fri, Nov 26, 2021 at 8:41 PM Aleksander Alekseev
<aleksander@timescale.com> wrote:

I will submit the actual code review in the follow-up email

The patchset is in a good shape. I'm changing the status to "Ready for
Committer".

The 2nd patch doesn't apply anymore due to a conflict on
resowner_private.h: http://cfbot.cputube.org/patch_36_3364.log.

I'm switching the entry to Waiting on Author.

#45Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Julien Rouhaud (#44)
3 attachment(s)
Re: ResourceOwner refactoring

On 12/01/2022 07:57, Julien Rouhaud wrote:

On Fri, Nov 26, 2021 at 8:41 PM Aleksander Alekseev
<aleksander@timescale.com> wrote:

The patchset is in a good shape. I'm changing the status to "Ready for
Committer".

The 2nd patch doesn't apply anymore due to a conflict on
resowner_private.h: http://cfbot.cputube.org/patch_36_3364.log.

Rebased version attached. Given that Aleksander marked this as Ready for
Committer earlier, I'll add this to the next commitfest in that state,
and will commit in the next few days, barring any new objections.

- Heikki

Attachments:

v12-0001-Move-a-few-ResourceOwnerEnlarge-calls-for-safety.patchtext/x-patch; charset=UTF-8; name=v12-0001-Move-a-few-ResourceOwnerEnlarge-calls-for-safety.patchDownload
From 14d05ab81a3615a0772bf34297fafce406e160ce Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Mon, 31 Oct 2022 10:33:36 +0100
Subject: [PATCH v12 1/3] 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 73d30bf6191..d12e81097c5 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -828,9 +828,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,
@@ -1192,9 +1189,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
@@ -1695,8 +1694,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.
  */
@@ -1707,6 +1704,8 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
 	bool		result;
 	PrivateRefCountEntry *ref;
 
+	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+
 	ref = GetPrivateRefCountEntry(b, true);
 
 	if (ref == NULL)
@@ -1787,7 +1786,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.
@@ -1960,9 +1960,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
@@ -2436,9 +2433,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;
@@ -2520,8 +2514,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)
@@ -2531,7 +2523,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.
@@ -3562,9 +3556,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;
@@ -3578,7 +3569,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) &&
@@ -3635,9 +3628,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;
@@ -3676,7 +3666,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) &&
@@ -3870,9 +3862,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;
@@ -3886,7 +3875,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 30d67d1c40d..58e7c32c1e3 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -123,6 +123,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, (void *) &newTag, HASH_FIND, NULL);
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 38e943fab2b..e7b5b20ea29 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1604,8 +1604,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();
@@ -1693,13 +1691,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

v12-0002-Make-resowners-more-easily-extensible.patchtext/x-patch; charset=UTF-8; name=v12-0002-Make-resowners-more-easily-extensible.patchDownload
From e0701d958b1fb55ae0d0cea79221ca1653ee0182 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Mon, 31 Oct 2022 10:48:57 +0100
Subject: [PATCH v12 2/3] 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        |   46 +-
 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(+), 1131 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 b7f918c877b..9636abb4ff7 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 91a6b2b63ad..9354ba34c3b 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 9aca7fc7a47..f0e15bc6345 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,8 +121,26 @@ static LLVMOrcLLJITRef llvm_create_jit_instance(LLVMTargetMachineRef tm);
 static char *llvm_error_message(LLVMErrorRef error);
 #endif							/* LLVM_VERSION_MAJOR > 11 */
 
-PG_MODULE_MAGIC;
+/* 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;
 
 /*
  * Initialize LLVM JIT provider.
@@ -151,7 +169,7 @@ llvm_create_context(int jitFlags)
 
 	llvm_session_initialize();
 
-	ResourceOwnerEnlargeJIT(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	context = MemoryContextAllocZero(TopMemoryContext,
 									 sizeof(LLVMJitContext));
@@ -159,7 +177,7 @@ llvm_create_context(int jitFlags)
 
 	/* ensure cleanup */
 	context->base.resowner = CurrentResourceOwner;
-	ResourceOwnerRememberJIT(CurrentResourceOwner, PointerGetDatum(context));
+	ResourceOwnerRememberJIT(CurrentResourceOwner, context);
 
 	return context;
 }
@@ -221,6 +239,8 @@ llvm_release_context(JitContext *context)
 	}
 	list_free(llvm_context->handles);
 	llvm_context->handles = NIL;
+
+	ResourceOwnerForgetJIT(context->resowner, context);
 }
 
 /*
@@ -1266,3 +1286,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 d12e81097c5..0f0cedc8b39 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
@@ -630,7 +642,7 @@ ReadRecentBuffer(RelFileLocator rlocator, ForkNumber forkNum, BlockNumber blockN
 
 	Assert(BufferIsValid(recent_buffer));
 
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	ReservePrivateRefCountEntry();
 	InitBufferTag(&tag, &rlocator, forkNum, blockNum);
 
@@ -1193,7 +1205,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
@@ -1704,7 +1716,7 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
 	bool		result;
 	PrivateRefCountEntry *ref;
 
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	ref = GetPrivateRefCountEntry(b, true);
 
@@ -1787,7 +1799,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.
@@ -2525,7 +2537,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.
@@ -3571,7 +3583,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) &&
@@ -3668,7 +3680,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) &&
@@ -3877,7 +3889,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 &&
@@ -3960,7 +3972,7 @@ void
 IncrBufferRefCount(Buffer buffer)
 {
 	Assert(BufferIsPinned(buffer));
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	if (BufferIsLocal(buffer))
 		LocalRefCount[-buffer - 1]++;
 	else
@@ -5021,3 +5033,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 58e7c32c1e3..f83722173ed 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -22,7 +22,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*/
@@ -124,7 +124,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 4151cafec54..41a5386f512 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 a360325f864..d1eea705ebc 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.
  *
@@ -908,7 +929,7 @@ void
 dsm_unpin_mapping(dsm_segment *seg)
 {
 	Assert(seg->resowner == NULL);
-	ResourceOwnerEnlargeDSMs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	seg->resowner = CurrentResourceOwner;
 	ResourceOwnerRememberDSM(seg->resowner, seg);
 }
@@ -1175,7 +1196,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);
@@ -1254,3 +1275,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 3d1049cf756..bf363001e0a 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 e7b5b20ea29..607ddb6f6f9 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -32,12 +32,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"
 
 
@@ -105,6 +106,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
@@ -1267,7 +1304,7 @@ SearchCatCacheInternal(CatCache *cache,
 		 */
 		if (!ct->negative)
 		{
-			ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner);
+			ResourceOwnerEnlarge(CurrentResourceOwner);
 			ct->refcount++;
 			ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple);
 
@@ -1368,7 +1405,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 */
@@ -1580,7 +1617,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);
 
@@ -1692,7 +1729,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);
@@ -2064,14 +2101,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));
 
@@ -2085,9 +2127,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 cc943205d34..319256c99a4 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);
 	}
@@ -2205,3 +2225,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 bd6cd4e47b5..ab68df1277e 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);
@@ -6776,3 +6795,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 37b43ee1f8c..3a7f08ea2bf 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 f1f2ddac17c..ceff63a3ecf 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 2fb29b1f855..5ed746e7d69 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 8874d6a240c..dfe617327ed 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 406db6be783..a4ca7c9ee6e 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -382,6 +382,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 d81e6fabb73..4b945576655 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 0499635f594..5e4e1526904 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 4aff7015f92..a3612b848c2 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 d01cccc27c1..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-2022, 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 2f02cc8f422..a0ca1f20ac4 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2320,8 +2320,10 @@ ReplicationStateOnDisk
 ResTarget
 ReservoirState
 ReservoirStateData
-ResourceArray
+ResourceElem
 ResourceOwner
+ResourceOwnerData
+ResourceOwnerFuncs
 ResourceReleaseCallback
 ResourceReleaseCallbackItem
 ResourceReleasePhase
-- 
2.30.2

v12-0003-Use-a-faster-hash-function-in-resource-owners.patchtext/x-patch; charset=UTF-8; name=v12-0003-Use-a-faster-hash-function-in-resource-owners.patchDownload
From 12cc7e568ae97ffd6fc57f4024b81d5da480ab4f Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Mon, 31 Oct 2022 10:49:06 +0100
Subject: [PATCH v12 3/3] 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 3a7f08ea2bf..c640ab0d227 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -169,15 +169,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 8d539c0a994..c44b4e5f864 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

#46Aleksander Alekseev
aleksander@timescale.com
In reply to: Heikki Linnakangas (#45)
Re: ResourceOwner refactoring

Hi Heikki,

Rebased version attached. Given that Aleksander marked this as Ready for
Committer earlier, I'll add this to the next commitfest in that state,
and will commit in the next few days, barring any new objections.

Thanks for resurrecting this patch.

While taking a fresh look at the code I noticed a few things.

In 0002 we have:

```
+ .name = "buffer"
...
+ .name = "File",
```

Not sure why "File" starts with an uppercase letter while "buffer"
starts with a lowercase one. This is naturally not a big deal but
could be worth changing for consistency.

In 0003:

```
+#if SIZEOF_DATUM == 8
+    return hash_combine64(murmurhash64((uint64) value), (uint64) kind);
+#else
+    return hash_combine(murmurhash32((uint32) value), (uint32) kind);
+#endif
```

Maybe it's worth using PointerGetDatum() + DatumGetInt32() /
DatumGetInt64() inline functions instead of casting Datums and
pointers directly.

These are arguably nitpicks though and shouldn't stop you from merging
the patches as is.

--
Best regards,
Aleksander Alekseev

#47Aleksander Alekseev
aleksander@timescale.com
In reply to: Aleksander Alekseev (#46)
Re: ResourceOwner refactoring

Hi hackers,

Rebased version attached. Given that Aleksander marked this as Ready for
Committer earlier, I'll add this to the next commitfest in that state,
and will commit in the next few days, barring any new objections.

Thanks for resurrecting this patch.

Additionally I decided to run `make installcheck-world` on Raspberry
Pi 3. It just terminated successfully.

--
Best regards,
Aleksander Alekseev

#48Andres Freund
andres@anarazel.de
In reply to: Heikki Linnakangas (#45)
Re: ResourceOwner refactoring

Hi,

On 2022-10-31 10:51:36 +0100, Heikki Linnakangas wrote:

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.

This seems to work towards a future where only one kind of resource can be
reserved ahead of time. That doesn't strike me as great.

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.

As a goal I like this.

However, I'm not quite sold on the implementation. Two main worries:

1) As far as I can tell, the way ResourceOwnerReleaseAll() now works seems to
assume that within a phase the reset order does not matter. I don't think
that's a good assumption. I e.g. have a patch to replace InProgressBuf with
resowner handling, and in-progress IO errors should be processed before
before pins are released.

2) There's quite a few resource types where we actually don't need an entry in
an array, because we can instead add a dlist_node to the resource -
avoiding memory overhead and making removal cheap. I have a few pending
patches that use that approach, and this doesn't really provide a path for
that anymore.

I did try out the benchmark from
/messages/by-id/20221029200025.w7bvlgvamjfo6z44@awork3.anarazel.de and
the patches performed well, slightly better than my approach of allocating
some initial memory for each resarray.

Greetings,

Andres Freund

#49Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Andres Freund (#48)
Re: ResourceOwner refactoring

On 01/11/2022 02:15, Andres Freund wrote:

On 2022-10-31 10:51:36 +0100, Heikki Linnakangas wrote:

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.

This seems to work towards a future where only one kind of resource can be
reserved ahead of time. That doesn't strike me as great.

True. It is enough for all the current callers AFAICS, though. I don't
remember wanting to reserve multiple resources at the same time.
Usually, the distance between the Enlarge and Remember calls is very
short. If it's not, it's error-prone anyway, so we should try to keep
the distance short.

While we're at it, who says that it's enough to reserve space for only
one resource of a kind either? For example, what if you want to pin two
buffers?

If we really need to support that, I propose something like this:

/*
* Reserve a slot in the resource owner.
*
* 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.
*/
ResOwnerReservation *
ResourceOwnerReserve(ResourceOwner owner)

/*
* Remember that an object is owner by a ResourceOwner
*
* 'reservation' is a slot in the resource owner that was pre-reserved
* by ResOwnerReservation.
*/
void
ResOwnerRemember(ResOwnerReservaton *reservation, Datum value,
ResourceOwnerFuncs *kind)

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.

As a goal I like this.

However, I'm not quite sold on the implementation. Two main worries:

1) As far as I can tell, the way ResourceOwnerReleaseAll() now works seems to
assume that within a phase the reset order does not matter. I don't think
that's a good assumption. I e.g. have a patch to replace InProgressBuf with
resowner handling, and in-progress IO errors should be processed before
before pins are released.

Hmm. Currently, you're not supposed to hold any resources at commit. You
get warnings about resource leaks if a resource owner is not empty on
ResourceOwnerReleaseAll(). On abort, does the order matter? I'm not
familiar with your InProgressBuf patch, but I guess you could handle the
in-progress IO errors in ReleaseBuffer().

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.

2) There's quite a few resource types where we actually don't need an entry in
an array, because we can instead add a dlist_node to the resource -
avoiding memory overhead and making removal cheap. I have a few pending
patches that use that approach, and this doesn't really provide a path for
that anymore.

Is that materially better than using the array? The fast path with an
array is very fast. If it is better, perhaps we should bite the bullet
and require a dlist node and use that mechanism for all resource types?

I did try out the benchmark from
/messages/by-id/20221029200025.w7bvlgvamjfo6z44@awork3.anarazel.de and
the patches performed well, slightly better than my approach of allocating
some initial memory for each resarray.

Thank you, glad to hear that!

- Heikki

#50Robert Haas
robertmhaas@gmail.com
In reply to: Heikki Linnakangas (#49)
Re: ResourceOwner refactoring

On Tue, Nov 1, 2022 at 6:39 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

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.

I don't really see it that way, because with the current
implementation, we do all resources of a particular type together,
before moving on to the next type. That seems like a valuable property
to preserve, and I think we should.

--
Robert Haas
EDB: http://www.enterprisedb.com

#51Andres Freund
andres@anarazel.de
In reply to: Heikki Linnakangas (#49)
Re: ResourceOwner refactoring

Hi,

On 2022-11-01 12:39:39 +0200, Heikki Linnakangas wrote:

1) As far as I can tell, the way ResourceOwnerReleaseAll() now works seems to
assume that within a phase the reset order does not matter. I don't think
that's a good assumption. I e.g. have a patch to replace InProgressBuf with
resowner handling, and in-progress IO errors should be processed before
before pins are released.

Hmm. Currently, you're not supposed to hold any resources at commit. You get
warnings about resource leaks if a resource owner is not empty on
ResourceOwnerReleaseAll(). On abort, does the order matter? I'm not familiar
with your InProgressBuf patch, but I guess you could handle the in-progress
IO errors in ReleaseBuffer().

I was thinking about doing that as well, but it's not really trivial to know
about the in-progress IO at that time, without additional tracking (which
isn't free).

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...

2) There's quite a few resource types where we actually don't need an entry in
an array, because we can instead add a dlist_node to the resource -
avoiding memory overhead and making removal cheap. I have a few pending
patches that use that approach, and this doesn't really provide a path for
that anymore.

Is that materially better than using the array?

It's safe in critical sections. I have a, not really baked but promising,
patch to make WAL writes use AIO. There's no way to know the number of
"asynchronous IOs" needed before entering the critical section.

The fast path with an array is very fast. If it is better, perhaps we should
bite the bullet and require a dlist node and use that mechanism for all
resource types?

I don't think it's suitable for all - you need an exclusively owned region of
memory to embed a list in. That works nicely for some things, but not others
(e.g. buffer pins).

Greetings,

Andres Freund

#52Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Andres Freund (#51)
5 attachment(s)
Re: ResourceOwner refactoring

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

Attachments:

v13-0001-Move-a-few-ResourceOwnerEnlarge-calls-for-safety.patchtext/x-patch; charset=UTF-8; name=v13-0001-Move-a-few-ResourceOwnerEnlarge-calls-for-safety.patchDownload
From 40b863ffeb0bbb776dc1b1ee013ff8f08b2e3b6a Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
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

v13-0002-Make-resowners-more-easily-extensible.patchtext/x-patch; charset=UTF-8; name=v13-0002-Make-resowners-more-easily-extensible.patchDownload
From a2d3409bbc84ffff6be9f366cf1b39488bb76b22 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
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

v13-0003-Release-resources-in-priority-order.patchtext/x-patch; charset=UTF-8; name=v13-0003-Release-resources-in-priority-order.patchDownload
From ceb84cd6551887f55d540e295e71f82fce157df0 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
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

v13-0004-Use-a-faster-hash-function-in-resource-owners.patchtext/x-patch; charset=UTF-8; name=v13-0004-Use-a-faster-hash-function-in-resource-owners.patchDownload
From 437d58f4790a682d2cc67db6a61855da9423f19a Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
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

v13-0005-Change-pgcrypto-to-use-the-new-ResourceOwner-mec.patchtext/x-patch; charset=UTF-8; name=v13-0005-Change-pgcrypto-to-use-the-new-ResourceOwner-mec.patchDownload
From 8aac8a741742816521c5cd272ac22f67b1477278 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
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

#53Aleksander Alekseev
aleksander@timescale.com
In reply to: Heikki Linnakangas (#52)
Re: ResourceOwner refactoring

Hi,

I noticed that the patchset didn't make much progress since February
and decided to give it another round of code review.

[...]. 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.

+1

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

This is much appreciated, as well as extensive changes made to READMEs
and the comments.

[...] 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.

My examination, which besides reading the code included running it
under sanitizer and checking the code coverage, didn't reveal any
major problems with the patchset.

Certain "should never happen in practice" scenarios seem not to be
test-covered in resowner.c, particularly:

```
elog(ERROR, "ResourceOwnerEnlarge called after release started");
elog(ERROR, "ResourceOwnerRemember called but array was full");
elog(ERROR, "ResourceOwnerForget called for %s after release started",
kind->name);
elog(ERROR, "%s %p is not owned by resource owner %s",
elog(ERROR, "ResourceOwnerForget called for %s after release started",
kind->name);
elog(ERROR, "lock reference %p is not owned by resource owner %s"
```

I didn't check whether these or similar code paths were tested before
the patch and I don't have a strong opinion on whether we should test
these scenarios. Personally I'm OK with the fact that these few lines
are not covered with tests.

The following procedures are never executed:

* RegisterResourceReleaseCallback()
* UnregisterResourceReleaseCallback()

And are never actually called anymore due to changes in 0005.
Previously they were used by contrib/pgcrypto. I suggest dropping this
part of the API since it seems to be redundant now. This will simplify
the implementation even further.

This patch, although moderately complicated, was moved between several
commitfests. I think it would be great if it made it to PG16. I'm
inclined to change the status of the patchset to RfC in a bit, unless
anyone has a second opinion.

--
Best regards,
Aleksander Alekseev

#54Aleksander Alekseev
aleksander@timescale.com
In reply to: Aleksander Alekseev (#53)
Re: ResourceOwner refactoring

Hi,

This patch, although moderately complicated, was moved between several
commitfests. I think it would be great if it made it to PG16. I'm
inclined to change the status of the patchset to RfC in a bit, unless
anyone has a second opinion.

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

Hmm... it looks like a file is missing in the patchset. When building
with Autotools:

```
============== running regression test queries ==============
test test_resowner ... /bin/sh: 1: cannot open
/home/eax/projects/postgresql/src/test/modules/test_resowner/sql/test_resowner.sql:
No such file
```

I wonder why the tests pass when using Meson.

--
Best regards,
Aleksander Alekseev

#55Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Aleksander Alekseev (#53)
4 attachment(s)
Re: ResourceOwner refactoring

Here's another version of this patchset:

- I squashed the resource release priority changes with the main patch.

- In 0001-Move-a-few-ResourceOwnerEnlarge-calls-for-safety.patch, I
moved things around a little differently. In previous version, I changed
PinBuffer() so that it does ResourceOwnerEnlargeBuffers, so that the
callers don't need to do it. In this version, I went the other
direction, and made the caller responsible for calling both
ResourceOwnerEnlargeBuffers and ReservePrivateRefCountEntry. There were
some callers that had to call those functions before calling PinBuffer()
anyway, because they wanted to avoid the possibility of elog(ERROR), so
it seems better to always make it the caller's responsibility.

More comments below:

On 22/03/2023 16:23, Aleksander Alekseev wrote:

Certain "should never happen in practice" scenarios seem not to be
test-covered in resowner.c, particularly:

```
elog(ERROR, "ResourceOwnerEnlarge called after release started");
elog(ERROR, "ResourceOwnerRemember called but array was full");
elog(ERROR, "ResourceOwnerForget called for %s after release started",
kind->name);
elog(ERROR, "%s %p is not owned by resource owner %s",
elog(ERROR, "ResourceOwnerForget called for %s after release started",
kind->name);
elog(ERROR, "lock reference %p is not owned by resource owner %s"
```

I didn't check whether these or similar code paths were tested before
the patch and I don't have a strong opinion on whether we should test
these scenarios. Personally I'm OK with the fact that these few lines
are not covered with tests.

They were not covered before. Might make sense to add coverage, I'll
look into that.

The following procedures are never executed:

* RegisterResourceReleaseCallback()
* UnregisterResourceReleaseCallback()

And are never actually called anymore due to changes in 0005.
Previously they were used by contrib/pgcrypto. I suggest dropping this
part of the API since it seems to be redundant now. This will simplify
the implementation even further.

There might be extensions out there that use those callbacks, that's why
I left them in place. I'll add a test for those functions in
test_resowner now, so that we still have some coverage for them in core.

Hmm... it looks like a file is missing in the patchset. When building
with Autotools:

```
============== running regression test queries ==============
test test_resowner ... /bin/sh: 1: cannot open
/home/eax/projects/postgresql/src/test/modules/test_resowner/sql/test_resowner.sql:
No such file
```

Oops, added.

I wonder why the tests pass when using Meson.

A-ha, it's because I didn't add the new test_resowner directory to the
src/test/modules/meson.build file. Fixed.

Thanks for the review!

--
Heikki Linnakangas
Neon (https://neon.tech)

Attachments:

v14-0001-Move-a-few-ResourceOwnerEnlarge-calls-for-safety.patchtext/x-patch; charset=UTF-8; name=v14-0001-Move-a-few-ResourceOwnerEnlarge-calls-for-safety.patchDownload
From eb7724b314456598af5679734e0bfc737e4eeee4 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Mon, 31 Oct 2022 10:33:36 +0100
Subject: [PATCH v14 1/4] 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   | 49 ++++++++++++---------------
 src/backend/storage/buffer/localbuf.c |  2 ++
 src/backend/utils/cache/catcache.c    |  5 +--
 3 files changed, 27 insertions(+), 29 deletions(-)

diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 3c59bbd04ea..7cc124e37bb 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -1023,9 +1023,6 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 								 forkNum, strategy, flags);
 	}
 
-	/* Make sure we will have room to remember the buffer pin */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
-
 	TRACE_POSTGRESQL_BUFFER_READ_START(forkNum, blockNum,
 									   smgr->smgr_rlocator.locator.spcOid,
 									   smgr->smgr_rlocator.locator.dbOid,
@@ -1230,6 +1227,10 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 	BufferDesc *victim_buf_hdr;
 	uint32		victim_buf_state;
 
+	/* Make sure we will have room to remember the buffer pin */
+	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ReservePrivateRefCountEntry();
+
 	/* create a tag so we can lookup the buffer */
 	InitBufferTag(&newTag, &smgr->smgr_rlocator.locator, forkNum, blockNum);
 
@@ -1591,7 +1592,7 @@ GetVictimBuffer(BufferAccessStrategy strategy, IOContext io_context)
 
 	/*
 	 * Ensure, while the spinlock's not yet held, that there's a free refcount
-	 * entry.
+	 * entry, and a resource owner slot for the pin.
 	 */
 	ReservePrivateRefCountEntry();
 	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
@@ -1859,9 +1860,6 @@ ExtendBufferedRelShared(ExtendBufferedWhat eb,
 		MemSet((char *) buf_block, 0, BLCKSZ);
 	}
 
-	/* in case we need to pin an existing buffer below */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
-
 	/*
 	 * Lock relation against concurrent extensions, unless requested not to.
 	 *
@@ -1947,6 +1945,10 @@ ExtendBufferedRelShared(ExtendBufferedWhat eb,
 		LWLock	   *partition_lock;
 		int			existing_id;
 
+		/* in case we need to pin an existing buffer below */
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ReservePrivateRefCountEntry();
+
 		InitBufferTag(&tag, &eb.smgr->smgr_rlocator.locator, fork, first_block + i);
 		hash = BufTableHashCode(&tag);
 		partition_lock = BufMappingPartitionLock(hash);
@@ -2222,7 +2224,8 @@ 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.
+ * Note that ResourceOwnerEnlargeBuffers and ReservePrivateRefCountEntry()
+ * 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.
@@ -2235,6 +2238,7 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
 	PrivateRefCountEntry *ref;
 
 	Assert(!BufferIsLocal(b));
+	Assert(ReservedRefCountEntry != NULL);
 
 	ref = GetPrivateRefCountEntry(b, true);
 
@@ -2243,7 +2247,6 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
 		uint32		buf_state;
 		uint32		old_buf_state;
 
-		ReservePrivateRefCountEntry();
 		ref = NewPrivateRefCountEntry(b);
 
 		old_buf_state = pg_atomic_read_u32(&buf->state);
@@ -2316,7 +2319,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.
@@ -2491,9 +2495,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
@@ -2970,9 +2971,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;
@@ -3054,8 +3052,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)
@@ -3065,7 +3061,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.
@@ -4110,9 +4108,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;
@@ -4126,7 +4121,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) &&
@@ -4183,9 +4180,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;
@@ -4224,7 +4218,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) &&
@@ -4419,9 +4415,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;
@@ -4435,7 +4428,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 f684862d98c..19ef10f434e 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -130,6 +130,8 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
 	if (LocalBufHash == NULL)
 		InitLocalBuffers();
 
+	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..dcc56b04c8f 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,6 +1689,9 @@ 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);
-- 
2.30.2

v14-0002-Make-resowners-more-easily-extensible.patchtext/x-patch; charset=UTF-8; name=v14-0002-Make-resowners-more-easily-extensible.patchDownload
From 1cdb12f6c26dcae3cb3d4e3d07d88b56e0e41f16 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Mon, 31 Oct 2022 10:48:57 +0100
Subject: [PATCH v14 2/4] 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.

Each resource kind specifies a release priority, and
ResourceOwnerReleaseAll releases the resources in priority 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

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           |   46 +-
 src/backend/access/transam/xact.c             |   15 +
 src/backend/jit/jit.c                         |    2 -
 src/backend/jit/llvm/llvmjit.c                |   49 +-
 src/backend/storage/buffer/bufmgr.c           |  105 +-
 src/backend/storage/buffer/localbuf.c         |   15 +-
 src/backend/storage/file/fd.c                 |   52 +-
 src/backend/storage/ipc/dsm.c                 |   47 +-
 src/backend/storage/lmgr/lock.c               |    2 +-
 src/backend/utils/cache/catcache.c            |   97 +-
 src/backend/utils/cache/plancache.c           |   52 +-
 src/backend/utils/cache/relcache.c            |   59 +-
 src/backend/utils/resowner/README             |   78 +-
 src/backend/utils/resowner/resowner.c         | 1537 ++++++-----------
 src/backend/utils/time/snapmgr.c              |   49 +-
 src/common/cryptohash_openssl.c               |   51 +-
 src/common/hmac_openssl.c                     |   49 +-
 src/include/storage/buf_internals.h           |   15 +
 src/include/utils/catcache.h                  |    3 -
 src/include/utils/plancache.h                 |    2 +
 src/include/utils/resowner.h                  |   76 +-
 src/pl/plpgsql/src/pl_exec.c                  |    2 +-
 src/pl/plpgsql/src/pl_handler.c               |    6 +-
 src/test/modules/Makefile                     |    1 +
 src/test/modules/meson.build                  |    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    |   34 +
 .../test_resowner/sql/test_resowner.sql       |   25 +
 .../test_resowner/test_resowner--1.0.sql      |   30 +
 .../test_resowner/test_resowner.control       |    4 +
 .../test_resowner/test_resowner_basic.c       |  212 +++
 .../test_resowner/test_resowner_many.c        |  290 ++++
 src/tools/pgindent/typedefs.list              |    4 +-
 35 files changed, 2132 insertions(+), 1103 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/sql/test_resowner.sql
 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 7c5c390503b..a40f1ae113a 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 =
+{
+	.name = "tupdesc reference",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_TUPDESC_REFS,
+	.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);
 }
@@ -927,3 +945,27 @@ BuildDescFromLists(List *names, List *types, List *typmods, List *collations)
 
 	return desc;
 }
+
+
+/* ResourceOwner callbacks */
+
+static void
+ResOwnerReleaseTupleDesc(Datum 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
+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/access/transam/xact.c b/src/backend/access/transam/xact.c
index 8daaa535edf..0fdcf769cf0 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -5171,9 +5171,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/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 04ae3052a82..82813ed3cff 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 =
+{
+	.name = "LLVM JIT context",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_JIT_CONTEXTS,
+	.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,9 @@ llvm_release_context(JitContext *context)
 	}
 	list_free(llvm_context->handles);
 	llvm_context->handles = NIL;
+
+	if (context->resowner)
+		ResourceOwnerForgetJIT(context->resowner, context);
 }
 
 /*
@@ -1266,3 +1288,24 @@ llvm_error_message(LLVMErrorRef error)
 }
 
 #endif							/* LLVM_VERSION_MAJOR > 11 */
+
+/*
+ * ResourceOwner callbacks
+ */
+static void
+ResOwnerReleaseJitContext(Datum res)
+{
+	JitContext *context = (JitContext *) DatumGetPointer(res);
+
+	context->resowner = NULL;
+	jit_release_context(context);
+}
+
+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 7cc124e37bb..949fb58cda5 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -47,6 +47,7 @@
 #include "postmaster/bgwriter.h"
 #include "storage/buf_internals.h"
 #include "storage/bufmgr.h"
+#include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/proc.h"
@@ -55,7 +56,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"
 
 
@@ -205,6 +206,30 @@ static PrivateRefCountEntry *GetPrivateRefCountEntry(Buffer buffer, bool do_move
 static inline int32 GetPrivateRefCount(Buffer buffer);
 static void ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref);
 
+/* ResourceOwner callbacks to hold in-progress I/Os and buffer pins */
+static void ResOwnerReleaseBufferIO(Datum res);
+static void ResOwnerPrintBufferIOLeakWarning(Datum res);
+static void ResOwnerReleaseBufferPin(Datum res);
+static void ResOwnerPrintBufferPinLeakWarning(Datum res);
+
+ResourceOwnerFuncs buffer_io_resowner_funcs =
+{
+	.name = "buffer_io",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_BUFFER_IOS,
+	.ReleaseResource = ResOwnerReleaseBufferIO,
+	.PrintLeakWarning = ResOwnerPrintBufferIOLeakWarning
+};
+
+ResourceOwnerFuncs buffer_pin_resowner_funcs =
+{
+	.name = "buffer_pin",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_BUFFER_PINS,
+	.ReleaseResource = ResOwnerReleaseBufferPin,
+	.PrintLeakWarning = ResOwnerPrintBufferPinLeakWarning
+};
+
 /*
  * Ensure that the PrivateRefCountArray has sufficient space to store one more
  * entry. This has to be called before using NewPrivateRefCountEntry() to fill
@@ -470,6 +495,7 @@ static BlockNumber ExtendBufferedRelShared(ExtendBufferedWhat eb,
 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,
@@ -639,7 +665,7 @@ ReadRecentBuffer(RelFileLocator rlocator, ForkNumber forkNum, BlockNumber blockN
 
 	Assert(BufferIsValid(recent_buffer));
 
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	ReservePrivateRefCountEntry();
 	InitBufferTag(&tag, &rlocator, forkNum, blockNum);
 
@@ -1228,7 +1254,7 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 	uint32		victim_buf_state;
 
 	/* Make sure we will have room to remember the buffer pin */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	ReservePrivateRefCountEntry();
 
 	/* create a tag so we can lookup the buffer */
@@ -1315,7 +1341,7 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 		 * use.
 		 *
 		 * We could do this after releasing the partition lock, but then we'd
-		 * have to call ResourceOwnerEnlargeBuffers() &
+		 * have to call ResourceOwnerEnlarge() &
 		 * ReservePrivateRefCountEntry() before acquiring the lock, for the
 		 * rare case of such a collision.
 		 */
@@ -1595,7 +1621,7 @@ GetVictimBuffer(BufferAccessStrategy strategy, IOContext io_context)
 	 * entry, and a resource owner slot for the pin.
 	 */
 	ReservePrivateRefCountEntry();
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	/* we return here if a prospective victim buffer gets used concurrently */
 again:
@@ -1946,7 +1972,7 @@ ExtendBufferedRelShared(ExtendBufferedWhat eb,
 		int			existing_id;
 
 		/* in case we need to pin an existing buffer below */
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 		ReservePrivateRefCountEntry();
 
 		InitBufferTag(&tag, &eb.smgr->smgr_rlocator.locator, fork, first_block + i);
@@ -2224,7 +2250,7 @@ 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 and ReservePrivateRefCountEntry()
+ * Note that ResourceOwnerEnlarge() and ReservePrivateRefCountEntry()
  * must have been done already.
  *
  * Returns true if buffer is BM_VALID, else false.  This provision allows
@@ -2320,7 +2346,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.
@@ -2381,6 +2407,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);
@@ -2390,9 +2425,6 @@ 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)
@@ -3063,7 +3095,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.
@@ -4123,7 +4155,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) &&
@@ -4220,7 +4252,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) &&
@@ -4430,7 +4462,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 &&
@@ -4507,7 +4539,7 @@ void
 IncrBufferRefCount(Buffer buffer)
 {
 	Assert(BufferIsPinned(buffer));
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	if (BufferIsLocal(buffer))
 		LocalRefCount[-buffer - 1]++;
 	else
@@ -5105,7 +5137,7 @@ StartBufferIO(BufferDesc *buf, bool forInput)
 {
 	uint32		buf_state;
 
-	ResourceOwnerEnlargeBufferIOs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	for (;;)
 	{
@@ -5587,3 +5619,42 @@ TestForOldSnapshot_impl(Snapshot snapshot, Relation relation)
 				(errcode(ERRCODE_SNAPSHOT_TOO_OLD),
 				 errmsg("snapshot too old")));
 }
+
+/* ResourceOwner callbacks */
+
+static void
+ResOwnerReleaseBufferIO(Datum res)
+{
+	Buffer		buffer = DatumGetInt32(res);
+
+	AbortBufferIO(buffer);
+}
+
+static void
+ResOwnerPrintBufferIOLeakWarning(Datum res)
+{
+	Buffer		buffer = DatumGetInt32(res);
+
+	elog(PANIC, "lost track of buffer IO on buffer %d", buffer);
+}
+
+static void
+ResOwnerReleaseBufferPin(Datum 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))
+		UnpinLocalBufferNoOwner(buffer);
+	else
+		UnpinBufferNoOwner(GetBufferDescriptor(buffer - 1));
+}
+
+static void
+ResOwnerPrintBufferPinLeakWarning(Datum res)
+{
+	PrintBufferLeakWarning(DatumGetInt32(res));
+}
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index 19ef10f434e..27efba8c6e3 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -21,9 +21,10 @@
 #include "pgstat.h"
 #include "storage/buf_internals.h"
 #include "storage/bufmgr.h"
+#include "storage/fd.h"
 #include "utils/guc_hooks.h"
 #include "utils/memutils.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 
 
 /*#define LBDEBUG*/
@@ -130,7 +131,7 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
 	if (LocalBufHash == NULL)
 		InitLocalBuffers();
 
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	/* See if the desired buffer already exists */
 	hresult = (LocalBufferLookupEnt *)
@@ -182,7 +183,7 @@ GetLocalVictimBuffer(void)
 	uint32		buf_state;
 	BufferDesc *bufHdr;
 
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	/*
 	 * Need to get a new buffer.  We use a clock sweep algorithm (essentially
@@ -674,6 +675,13 @@ PinLocalBuffer(BufferDesc *buf_hdr, bool adjust_usagecount)
 
 void
 UnpinLocalBuffer(Buffer buffer)
+{
+	UnpinLocalBufferNoOwner(buffer);
+	ResourceOwnerForgetBuffer(CurrentResourceOwner, buffer);
+}
+
+void
+UnpinLocalBufferNoOwner(Buffer buffer)
 {
 	int			buffid = -buffer - 1;
 
@@ -681,7 +689,6 @@ UnpinLocalBuffer(Buffer buffer)
 	Assert(LocalRefCount[buffid] > 0);
 	Assert(NLocalPinnedBuffers > 0);
 
-	ResourceOwnerForgetBuffer(CurrentResourceOwner, buffer);
 	if (--LocalRefCount[buffid] == 0)
 		NLocalPinnedBuffers--;
 }
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 173476789c7..76a83239178 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -99,7 +99,7 @@
 #include "storage/ipc.h"
 #include "utils/guc.h"
 #include "utils/guc_hooks.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 #include "utils/varlena.h"
 
 /* Define PG_FLUSH_DATA_WORKS if we have an implementation for pg_flush_data */
@@ -354,6 +354,25 @@ 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",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_FILES,
+	.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
  */
@@ -1451,7 +1470,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
@@ -1643,7 +1662,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
@@ -1775,7 +1794,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
@@ -1815,7 +1834,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);
 
@@ -3924,3 +3943,26 @@ assign_io_direct(const char *newval, void *extra)
 
 	io_direct_flags = *flags;
 }
+
+/* ResourceOwner callbacks */
+
+static void
+ResOwnerReleaseFile(Datum res)
+{
+	File		file = (File) DatumGetInt32(res);
+	Vfd		   *vfdP;
+
+	Assert(FileIsValid(file));
+
+	vfdP = &VfdCache[file];
+	vfdP->resowner = NULL;
+
+	FileClose(file);
+}
+
+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..21c53bc6ae4 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,26 @@ 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",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_DSMS,
+	.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 +929,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 +1196,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 +1275,22 @@ is_main_region_dsm_handle(dsm_handle handle)
 {
 	return handle & 1;
 }
+
+/* ResourceOwner callbacks */
+
+static void
+ResOwnerReleaseDSM(Datum res)
+{
+	dsm_segment *seg = (dsm_segment *) res;
+
+	seg->resowner = NULL;
+	dsm_detach(seg);
+}
+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 193f50fc0f4..f0e9d79b9a1 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 dcc56b04c8f..4f2af4a5b72 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"
 
 
@@ -94,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,
@@ -104,6 +107,44 @@ 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",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_CATCACHE_REFS,
+	.ReleaseResource = ResOwnerReleaseCatCache,
+	.PrintLeakWarning = ResOwnerPrintCatCacheLeakWarning
+};
+
+static ResourceOwnerFuncs catlistref_resowner_funcs =
+{
+	/* catcache-list pins */
+	.name = "catcache list reference",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_CATCACHE_LIST_REFS,
+	.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 +1306,7 @@ SearchCatCacheInternal(CatCache *cache,
 		 */
 		if (!ct->negative)
 		{
-			ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner);
+			ResourceOwnerEnlarge(CurrentResourceOwner);
 			ct->refcount++;
 			ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple);
 
@@ -1366,7 +1407,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 */
@@ -1433,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));
@@ -1442,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
@@ -1578,7 +1626,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 +1738,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);
@@ -1775,12 +1823,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
@@ -2056,14 +2111,18 @@ PrepareToInvalidateCacheTuple(Relation relation,
 	}
 }
 
+/* ResourceOwner callbacks */
 
-/*
- * Subroutines for warning about reference leaks.  These are exported so
- * that resowner.c can call them.
- */
-void
-PrintCatCacheLeakWarning(HeapTuple tuple)
+static void
+ResOwnerReleaseCatCache(Datum res)
+{
+	ReleaseCatCacheWithOwner((HeapTuple) DatumGetPointer(res), NULL);
+}
+
+static void
+ResOwnerPrintCatCacheLeakWarning(Datum res)
 {
+	HeapTuple	tuple = (HeapTuple) DatumGetPointer(res);
 	CatCTup    *ct = (CatCTup *) (((char *) tuple) -
 								  offsetof(CatCTup, tuple));
 
@@ -2077,9 +2136,17 @@ PrintCatCacheLeakWarning(HeapTuple tuple)
 		 ct->refcount);
 }
 
-void
-PrintCatCacheListLeakWarning(CatCList *list)
+static void
+ResOwnerReleaseCatCacheList(Datum res)
 {
+	ReleaseCatCacheListWithOwner((CatCList *) DatumGetPointer(res), NULL);
+}
+
+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 87210fcf627..a6710c2bab9 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);
+
+static ResourceOwnerFuncs planref_resowner_funcs =
+{
+	.name = "plancache reference",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_PLANCACHE_REFS,
+	.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);
 	}
@@ -2214,3 +2234,27 @@ ResetPlanCache(void)
 		cexpr->is_valid = false;
 	}
 }
+
+/*
+ * 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), NULL);
+}
+
+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 8a08463c2b7..d3980849153 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"
 
@@ -273,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);
 
@@ -2115,6 +2117,25 @@ 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",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_RELCACHE_REFS,
+	.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 +2147,7 @@ RelationIdGetRelation(Oid relationId)
 void
 RelationIncrementReferenceCount(Relation rel)
 {
-	ResourceOwnerEnlargeRelationRefs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	rel->rd_refcnt += 1;
 	if (!IsBootstrapProcessingMode())
 		ResourceOwnerRememberRelationRef(CurrentResourceOwner, rel);
@@ -2162,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
@@ -6813,3 +6840,31 @@ unlink_initfile(const char *initfilename, int elevel)
 							initfilename)));
 	}
 }
+
+/*
+ * ResourceOwner callbacks
+ */
+static void
+ResOwnerPrintRelCacheLeakWarning(Datum res)
+{
+	Relation	rel = (Relation) DatumGetPointer(res);
+
+	elog(WARNING, "relcache reference leak: relation \"%s\" not closed",
+		 RelationGetRelationName(rel));
+}
+
+static void
+ResOwnerReleaseRelation(Datum 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 f94c9700df4..2a703a395dc 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
@@ -79,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 f926f1faad3..c385c996e19 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 the 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
@@ -20,85 +44,54 @@
  */
 #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/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 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 table.  Must be power of
+ * two because we 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.
- * 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 RESARRAY_MAX_ITEMS(capacity) \
-	((capacity) <= RESARRAY_MAX_ARRAY ? (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,
+				 "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
@@ -119,25 +112,35 @@ 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 bufferioarr;	/* in-progress buffer IO */
-	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 */
-
-	/* We can remember up to MAX_RESOWNER_LOCKS references to local locks. */
-	int			nlocks;			/* number of owned locks */
+	bool		releasing;		/* has ResourceOwnerRelease been called? */
+
+	/*
+	 * The fixed-size array for recent resources.
+	 *
+	 * If 'releasing' is set, the contents are sorted by release priority.
+	 */
+	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 */
+
+	/* The local locks cache. */
+	uint32		nlocks;			/* number of owned locks */
 	LOCALLOCK  *locks[MAX_RESOWNER_LOCKS];	/* list of owned locks */
-}			ResourceOwnerData;
+} ResourceOwnerData;
 
 
 /*****************************************************************************
@@ -149,6 +152,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
  */
@@ -163,253 +178,195 @@ 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 int	resource_priority_cmp(const void *a, const void *b);
+static void ResourceOwnerSort(ResourceOwner owner);
+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														 *
  *****************************************************************************/
 
-
-/*
- * Initialize a ResourceArray
- */
-static void
-ResourceArrayInit(ResourceArray *resarr, Datum invalidval)
+static inline uint32
+hash_resource_elem(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 */
+	Datum		data[2];
+
+	data[0] = value;
+	data[1] = PointerGetDatum(kind);
+
+	return hash_bytes((unsigned char *) &data, 2 * SIZEOF_DATUM);
 }
 
 /*
- * 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.
+ * Adds 'value' of given 'kind' to the ResourceOwner's hash table
  */
 static void
-ResourceArrayEnlarge(ResourceArray *resarr)
+ResourceOwnerAddToHash(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind)
 {
-	uint32		i,
-				oldcap,
-				newcap;
-	Datum	   *olditemsarr;
-	Datum	   *newitemsarr;
-
-	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;
+	uint32		mask = owner->capacity - 1;
+	uint32		idx;
 
-	/* 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;
+	Assert(kind != NULL);
 
-	if (olditemsarr != NULL)
+	/* Insert into first free slot at or after hash location. */
+	idx = hash_resource_elem(value, kind) & mask;
+	for (;;)
 	{
-		/*
-		 * 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.
-		 */
-		for (i = 0; i < oldcap; i++)
-		{
-			if (olditemsarr[i] != resarr->invalidval)
-				ResourceArrayAdd(resarr, olditemsarr[i]);
-		}
-
-		/* And release old array. */
-		pfree(olditemsarr);
+		if (owner->hash[idx].kind == NULL)
+			break;				/* found a free slot */
+		idx = (idx + 1) & mask;
 	}
-
-	Assert(resarr->nitems < resarr->maxitems);
+	owner->hash[idx].item = value;
+	owner->hash[idx].kind = kind;
+	owner->nhash++;
 }
 
 /*
- * Add a resource to ResourceArray
- *
- * Caller must have previously done ResourceArrayEnlarge()
+ * Comparison function to sort by release phase and priority
  */
-static void
-ResourceArrayAdd(ResourceArray *resarr, Datum value)
+static int
+resource_priority_cmp(const void *a, const void *b)
 {
-	uint32		idx;
+	const ResourceElem *ra = (const ResourceElem *) a;
+	const ResourceElem *rb = (const ResourceElem *) b;
 
-	Assert(value != resarr->invalidval);
-	Assert(resarr->nitems < resarr->maxitems);
-
-	if (RESARRAY_IS_ARRAY(resarr))
+	/* Note: reverse order */
+	if (ra->kind->release_phase == rb->kind->release_phase)
 	{
-		/* Append to linear array. */
-		idx = resarr->nitems;
+		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
-	{
-		/* 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++;
+		return 1;
 }
 
 /*
- * Remove a resource from ResourceArray
+ * Sort resources in reverse release priority.
  *
- * Returns true on success, false if resource was not found.
- *
- * Note: if same resource ID appears more than once, one instance is removed.
+ * 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 bool
-ResourceArrayRemove(ResourceArray *resarr, Datum value)
+static void
+ResourceOwnerSort(ResourceOwner owner)
 {
-	uint32		i,
-				idx,
-				lastidx = resarr->lastidx;
+	ResourceElem *items;
+	uint32		nitems;
 
-	Assert(value != resarr->invalidval);
-
-	/* Search through all items, but try lastidx first. */
-	if (RESARRAY_IS_ARRAY(resarr))
+	if (!owner->hash)
 	{
-		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++)
-		{
-			if (resarr->itemsarr[i] == value)
-			{
-				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;
-			}
-		}
+		items = owner->arr;
+		nitems = owner->narr;
 	}
 	else
 	{
-		uint32		mask = resarr->capacity - 1;
+		/*
+		 * Compact the hash table, so that all the elements are in the
+		 * beginning of the 'hash' array, with no empty elements.
+		 */
+		uint32		dst = 0;
 
-		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++)
+		for (int idx = 0; idx < owner->capacity; idx++)
 		{
-			if (resarr->itemsarr[idx] == value)
+			if (owner->hash[idx].kind != NULL)
 			{
-				resarr->itemsarr[idx] = resarr->invalidval;
-				resarr->nitems--;
-				return true;
+				if (dst != idx)
+					owner->hash[dst] = owner->hash[idx];
+				dst++;
 			}
-			idx = (idx + 1) & mask;
 		}
+
+		/*
+		 * 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.
+		 */
+		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;
 	}
 
-	return false;
+	qsort(items, nitems, sizeof(ResourceElem), resource_priority_cmp);
 }
 
 /*
- * 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.
- *
- * Returns true if we found an element, or false if the array is empty.
+ * Call the ReleaseResource callback on entries with given 'phase'.
  */
-static bool
-ResourceArrayGetAny(ResourceArray *resarr, Datum *value)
+static void
+ResourceOwnerReleaseAll(ResourceOwner owner, ResourceReleasePhase phase,
+						bool printLeakWarnings)
 {
-	if (resarr->nitems == 0)
-		return false;
+	ResourceElem *items;
+	uint32		nitems;
 
-	if (RESARRAY_IS_ARRAY(resarr))
+	/* ResourceOwnerSort must've been called already */
+	Assert(owner->releasing);
+	if (!owner->hash)
 	{
-		/* Linear array: just return the first element. */
-		resarr->lastidx = 0;
+		items = owner->arr;
+		nitems = owner->narr;
 	}
 	else
 	{
-		/* Hash: search forward from wherever we were last. */
-		uint32		mask = resarr->capacity - 1;
-
-		for (;;)
-		{
-			resarr->lastidx &= mask;
-			if (resarr->itemsarr[resarr->lastidx] != resarr->invalidval)
-				break;
-			resarr->lastidx++;
-		}
+		Assert(owner->narr == 0);
+		items = owner->hash;
+		nitems = owner->nhash;
 	}
 
-	*value = resarr->itemsarr[resarr->lastidx];
-	return true;
-}
+	/*
+	 * 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.
+	 */
+	while (nitems > 0)
+	{
+		uint32		idx = nitems - 1;
+		Datum		value = items[idx].item;
+		ResourceOwnerFuncs *kind = items[idx].kind;
 
-/*
- * Trash a ResourceArray (we don't care about its state after this)
- */
-static void
-ResourceArrayFree(ResourceArray *resarr)
-{
-	if (resarr->itemsarr)
-		pfree(resarr->itemsarr);
+		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;
 }
 
 
@@ -441,23 +398,199 @@ ResourceOwnerCreate(ResourceOwner parent, const char *name)
 		parent->firstchild = owner;
 	}
 
-	ResourceArrayInit(&(owner->bufferarr), BufferGetDatum(InvalidBuffer));
-	ResourceArrayInit(&(owner->bufferioarr), 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));
+#ifdef RESOWNER_TRACE
+	elog(LOG, "CREATE %d: %p %s",
+		 resowner_trace_counter++, owner, name);
+#endif
 
 	return owner;
 }
 
+/*
+ * 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.
+ *
+ * NB: Make sure there are no unrelated ResourceOwnerRemember() calls between
+ * your ResourceOwnerEnlarge() call and the ResourceOwnerRemember() call that
+ * you reserved the space for!
+ */
+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.
+	 */
+	if (owner->narr + owner->nhash >= owner->grow_at)
+	{
+		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)
+		{
+			/*
+			 * 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++)
+			{
+				if (oldhash[i].kind != NULL)
+					ResourceOwnerAddToHash(owner, oldhash[i].item, oldhash[i].kind);
+			}
+
+			/* And release old hash table. */
+			pfree(oldhash);
+		}
+	}
+
+	/* Move items from the array to the hash */
+	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);
+}
+
+/*
+ * Remember that an object is owner by a ReourceOwner
+ *
+ * Caller must have previously done ResourceOwnerEnlarge()
+ */
+void
+ResourceOwnerRemember(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind)
+{
+	uint32		idx;
+
+#ifdef RESOWNER_TRACE
+	elog(LOG, "REMEMBER %d: owner %p value " UINT64_FORMAT ", kind: %s",
+		 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 the array. */
+	idx = owner->narr;
+	owner->arr[idx].item = value;
+	owner->arr[idx].kind = kind;
+	owner->narr++;
+}
+
+/*
+ * 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.
+ */
+void
+ResourceOwnerForget(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind)
+{
+#ifdef RESOWNER_TRACE
+	elog(LOG, "FORGET %d: owner %p value " UINT64_FORMAT ", kind: %s",
+		 resowner_trace_counter++, owner, DatumGetUInt64(value), kind->name);
+#endif
+
+	/*
+	 * 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)
+		{
+			owner->arr[i] = owner->arr[owner->narr - 1];
+			owner->narr--;
+
+#ifdef RESOWNER_STATS
+			narray_lookups++;
+#endif
+
+			return;
+		}
+	}
+
+	/* Search hash */
+	if (owner->nhash > 0)
+	{
+		uint32		mask = owner->capacity - 1;
+		uint32		idx;
+
+		idx = hash_resource_elem(value, kind) & mask;
+		for (uint32 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;
+		}
+	}
+
+	/*
+	 * 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);
+}
+
 /*
  * ResourceOwnerRelease
  *		Release all resources owned by a ResourceOwner and its descendants,
@@ -483,6 +616,12 @@ ResourceOwnerCreate(ResourceOwner parent, const char *name)
  * 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,
@@ -492,6 +631,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
@@ -504,105 +652,52 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 	ResourceOwner save;
 	ResourceReleaseCallbackItem *item;
 	ResourceReleaseCallbackItem *next;
-	Datum		foundres;
 
 	/* Recurse to handle descendants */
 	for (child = owner->firstchild; child != NULL; child = child->nextchild)
 		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.
 	 */
-	save = CurrentResourceOwner;
-	CurrentResourceOwner = owner;
-
-	if (phase == RESOURCE_RELEASE_BEFORE_LOCKS)
+	if (!owner->releasing)
+	{
+		Assert(phase == RESOURCE_RELEASE_BEFORE_LOCKS);
+		ResourceOwnerSort(owner);
+		owner->releasing = true;
+	}
+	else
 	{
 		/*
-		 * Abort failed buffer IO. AbortBufferIO()->TerminateBufferIO() calls
-		 * ResourceOwnerForgetBufferIO(), so we just have to iterate till
-		 * there are none.
-		 *
-		 * Needs to be before we release buffer pins.
-		 *
-		 * During a commit, there shouldn't be any in-progress IO.
+		 * 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.
 		 */
-		while (ResourceArrayGetAny(&(owner->bufferioarr), &foundres))
-		{
-			Buffer		res = DatumGetBuffer(foundres);
+	}
 
-			if (isCommit)
-				elog(PANIC, "lost track of buffer IO on buffer %d", res);
-			AbortBufferIO(res);
-		}
+	/*
+	 * Make CurrentResourceOwner point to me, so that the release callback
+	 * functions know which resource owner is been released.
+	 */
+	save = CurrentResourceOwner;
+	CurrentResourceOwner = 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)
 	{
@@ -655,101 +750,70 @@ 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);
+		ResourceOwnerReleaseAll(owner, phase, isCommit);
+	}
 
-			if (isCommit)
-				PrintCatCacheLeakWarning(res);
-			ReleaseCatCache(res);
-		}
+	/* Let add-on modules get a chance too */
+	for (item = ResourceRelease_callbacks; item; item = next)
+	{
+		/* allow callbacks to unregister themselves when called */
+		next = item->next;
+		item->callback(phase, isCommit, isTopLevel, item->arg);
+	}
 
-		/* Ditto for catcache lists */
-		while (ResourceArrayGetAny(&(owner->catlistrefarr), &foundres))
-		{
-			CatCList   *res = (CatCList *) DatumGetPointer(foundres);
+	CurrentResourceOwner = save;
+}
 
-			if (isCommit)
-				PrintCatCacheListLeakWarning(res);
-			ReleaseCatCacheList(res);
-		}
+/*
+ * ResourceOwnerReleaseAllOfKind
+ *		Release all resources of a certain type held by this owner.
+ */
+void
+ResourceOwnerReleaseAllOfKind(ResourceOwner owner, ResourceOwnerFuncs *kind)
+{
+	/* 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);
 
-		/* Ditto for plancache references */
-		while (ResourceArrayGetAny(&(owner->planrefarr), &foundres))
+	/*
+	 * 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 == kind)
 		{
-			CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres);
+			Datum		value = owner->arr[i].item;
 
-			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);
+			owner->arr[i] = owner->arr[owner->narr - 1];
+			owner->narr--;
+			i--;
 
-			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);
+			kind->ReleaseResource(value);
 		}
 	}
 
-	/* Let add-on modules get a chance too */
-	for (item = ResourceRelease_callbacks; item; item = next)
+	/* Then hash */
+	for (int i = 0; i < owner->capacity; i++)
 	{
-		/* allow callbacks to unregister themselves when called */
-		next = item->next;
-		item->callback(phase, isCommit, isTopLevel, item->arg);
-	}
-
-	CurrentResourceOwner = save;
-}
-
-/*
- * 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.
- */
-void
-ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner)
-{
-	Datum		foundres;
+		if (owner->hash[i].kind == kind)
+		{
+			Datum		value = owner->hash[i].item;
 
-	while (ResourceArrayGetAny(&(owner->planrefarr), &foundres))
-	{
-		CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres);
+			owner->hash[i].item = (Datum) 0;
+			owner->hash[i].kind = NULL;
+			owner->nhash--;
 
-		ReleaseCachedPlan(res, owner);
+			kind->ReleaseResource(value);
+		}
 	}
+	owner->releasing = false;
 }
 
 /*
@@ -765,21 +829,15 @@ ResourceOwnerDelete(ResourceOwner owner)
 	Assert(owner != CurrentResourceOwner);
 
 	/* And it better not own any resources, either */
-	Assert(owner->bufferarr.nitems == 0);
-	Assert(owner->bufferioarr.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.
@@ -795,20 +853,8 @@ ResourceOwnerDelete(ResourceOwner owner)
 	ResourceOwnerNewParent(owner, NULL);
 
 	/* And free the object. */
-	ResourceArrayFree(&(owner->bufferarr));
-	ResourceArrayFree(&(owner->bufferioarr));
-	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);
 }
 
@@ -866,11 +912,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)
@@ -946,6 +991,8 @@ ReleaseAuxProcessResources(bool isCommit)
 	ResourceOwnerRelease(AuxProcessResourceOwner,
 						 RESOURCE_RELEASE_AFTER_LOCKS,
 						 isCommit, true);
+	/* allow it to be reused */
+	AuxProcessResourceOwner->releasing = false;
 }
 
 /*
@@ -960,88 +1007,12 @@ ReleaseAuxProcessResourcesCallback(int code, Datum arg)
 	ReleaseAuxProcessResources(isCommit);
 }
 
-
-/*
- * Make sure there is room for at least one more entry in a ResourceOwner's
- * buffer 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
-ResourceOwnerEnlargeBuffers(ResourceOwner owner)
-{
-	/* 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);
-}
-
-
-/*
- * Make sure there is room for at least one more entry in a ResourceOwner's
- * buffer 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
-ResourceOwnerEnlargeBufferIOs(ResourceOwner owner)
-{
-	/* We used to allow pinning buffers without a resowner, but no more */
-	Assert(owner != NULL);
-	ResourceArrayEnlarge(&(owner->bufferioarr));
-}
-
-/*
- * Remember that a buffer IO is owned by a ResourceOwner
- *
- * Caller must have previously done ResourceOwnerEnlargeBufferIOs()
- */
-void
-ResourceOwnerRememberBufferIO(ResourceOwner owner, Buffer buffer)
-{
-	ResourceArrayAdd(&(owner->bufferioarr), BufferGetDatum(buffer));
-}
-
-/*
- * Forget that a buffer IO is owned by a ResourceOwner
- */
-void
-ResourceOwnerForgetBufferIO(ResourceOwner owner, Buffer buffer)
-{
-	if (!ResourceArrayRemove(&(owner->bufferioarr), BufferGetDatum(buffer)))
-		elog(PANIC, "buffer IO %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
+ * 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,
  * ResourceOwnerForgetLock doesn't need to scan through a large array to find
  * the entry.
@@ -1087,469 +1058,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 3a419e348fa..46c1d5368b4 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"
@@ -172,9 +173,29 @@ 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);
 
+/* ResourceOwner callbacks to track snapshot references */
+static void ResOwnerReleaseSnapshot(Datum res);
+static void ResOwnerPrintSnapshotLeakWarning(Datum res);
+
+static ResourceOwnerFuncs snapshot_resowner_funcs =
+{
+	.name = "snapshot reference",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_SNAPSHOT_REFS,
+	.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 +871,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);
 
@@ -886,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);
@@ -2379,3 +2405,18 @@ XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot)
 
 	return false;
 }
+
+/* ResourceOwner callbacks */
+
+static void
+ResOwnerReleaseSnapshot(Datum res)
+{
+	UnregisterSnapshotNoOwner((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..5254e53f114 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 =
+{
+	.name = "OpenSSL cryptohash context",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_CRYPTOHASH_CONTEXTS,
+	.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,8 @@ pg_cryptohash_free(pg_cryptohash_ctx *ctx)
 	EVP_MD_CTX_destroy(ctx->evpctx);
 
 #ifndef FRONTEND
-	ResourceOwnerForgetCryptoHash(ctx->resowner,
-								  PointerGetDatum(ctx));
+	if (ctx->resowner)
+		ResourceOwnerForgetCryptoHash(ctx->resowner, ctx);
 #endif
 
 	explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
@@ -351,3 +370,23 @@ 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_ctx *ctx = (pg_cryptohash_ctx *) DatumGetPointer(res);
+
+	ctx->resowner = NULL;
+	pg_cryptohash_free(ctx);
+}
+
+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..9b47ad63ba1 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 =
+{
+	.name = "OpenSSL HMAC context",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_HMAC_CONTEXTS,
+	.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,8 @@ pg_hmac_free(pg_hmac_ctx *ctx)
 #ifdef HAVE_HMAC_CTX_FREE
 	HMAC_CTX_free(ctx->hmacctx);
 #ifndef FRONTEND
-	ResourceOwnerForgetHMAC(ctx->resowner, PointerGetDatum(ctx));
+	if (ctx->resowner)
+		ResourceOwnerForgetHMAC(ctx->resowner, ctx);
 #endif
 #else
 	explicit_bzero(ctx->hmacctx, sizeof(HMAC_CTX));
@@ -346,3 +367,23 @@ 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_ctx *ctx = (pg_hmac_ctx *) DatumGetPointer(res);
+
+	ctx->resowner = NULL;
+	pg_hmac_free(ctx);
+}
+
+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 30807d5d97e..1e8ab084e19 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -383,6 +383,20 @@ typedef struct CkptSortItem
 
 extern PGDLLIMPORT CkptSortItem *CkptBufferIds;
 
+/* ResourceOwner callbacks to hold buffer I/Os and pins */
+extern ResourceOwnerFuncs buffer_io_resowner_funcs;
+extern ResourceOwnerFuncs buffer_pin_resowner_funcs;
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+#define ResourceOwnerRememberBuffer(owner, buffer) \
+	ResourceOwnerRemember(owner, Int32GetDatum(buffer), &buffer_pin_resowner_funcs)
+#define ResourceOwnerForgetBuffer(owner, buffer) \
+	ResourceOwnerForget(owner, Int32GetDatum(buffer), &buffer_pin_resowner_funcs)
+#define ResourceOwnerRememberBufferIO(owner, buffer) \
+	ResourceOwnerRemember(owner, Int32GetDatum(buffer), &buffer_io_resowner_funcs)
+#define ResourceOwnerForgetBufferIO(owner, buffer) \
+	ResourceOwnerForget(owner, Int32GetDatum(buffer), &buffer_io_resowner_funcs)
+
 /*
  * Internal buffer management routines
  */
@@ -418,6 +432,7 @@ extern void BufTableDelete(BufferTag *tagPtr, uint32 hashcode);
 /* localbuf.c */
 extern bool PinLocalBuffer(BufferDesc *buf_hdr, bool adjust_usagecount);
 extern void UnpinLocalBuffer(Buffer buffer);
+extern void UnpinLocalBufferNoOwner(Buffer buffer);
 extern PrefetchBufferResult PrefetchLocalBuffer(SMgrRelation smgr,
 												ForkNumber forkNum,
 												BlockNumber blockNum);
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..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);
diff --git a/src/include/utils/resowner.h b/src/include/utils/resowner.h
index cd070b6080e..f692dbb6274 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,63 @@ typedef enum
 	RESOURCE_RELEASE_AFTER_LOCKS
 } ResourceReleasePhase;
 
+typedef uint32 ResourceReleasePriority;
+
+/* priorities of built-in BEFORE_LOCKS resources */
+#define RELEASE_PRIO_BUFFER_IOS			    100
+#define RELEASE_PRIO_BUFFER_PINS		    200
+#define RELEASE_PRIO_RELCACHE_REFS			300
+#define RELEASE_PRIO_DSMS					400
+#define RELEASE_PRIO_JIT_CONTEXTS			500
+#define RELEASE_PRIO_CRYPTOHASH_CONTEXTS	600
+#define RELEASE_PRIO_HMAC_CONTEXTS			700
+
+/* 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
+ * 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 */
+
+	/* when are these objects released? */
+	ResourceReleasePhase release_phase;
+	ResourceReleasePriority release_priority;
+
+	/*
+	 * Release resource.
+	 *
+	 * 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);
+
+	/*
+	 * 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 +133,28 @@ 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 ResourceOwnerReleaseAllOfKind(ResourceOwner owner, 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);
+
 #endif							/* RESOWNER_H */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 4b76f7699a6..f8c7f487470 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -8470,7 +8470,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 6331c976dcb..c4d880bc361 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -29,6 +29,7 @@ SUBDIRS = \
 		  test_predtest \
 		  test_rbtree \
 		  test_regex \
+		  test_resowner \
 		  test_rls_hooks \
 		  test_shm_mq \
 		  test_slru \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index 17d369e3789..e0bb14c2eeb 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -26,6 +26,7 @@ subdir('test_pg_dump')
 subdir('test_predtest')
 subdir('test_rbtree')
 subdir('test_regex')
+subdir('test_resowner')
 subdir('test_rls_hooks')
 subdir('test_shm_mq')
 subdir('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..5f669505a65
--- /dev/null
+++ b/src/test/modules/test_resowner/meson.build
@@ -0,0 +1,34 @@
+# Copyright (c) 2022-2023, PostgreSQL Global Development Group
+
+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_test_mod_args,
+)
+test_install_libs += test_resowner
+
+test_install_data += files(
+  'test_resowner.control',
+  'test_resowner--1.0.sql',
+)
+
+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/sql/test_resowner.sql b/src/test/modules/test_resowner/sql/test_resowner.sql
new file mode 100644
index 00000000000..23284b7c8b9
--- /dev/null
+++ b/src/test/modules/test_resowner/sql/test_resowner.sql
@@ -0,0 +1,25 @@
+CREATE EXTENSION test_resowner;
+
+-- This is small enough that everything fits in the small array
+SELECT test_resowner_priorities(2, 3);
+
+-- Same test with more resources, to exercise the hash table
+SELECT test_resowner_priorities(2, 32);
+
+-- 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
+);
+
+-- Test resource leak warning
+SELECT test_resowner_leak();
+
+-- 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();
+SELECT test_resowner_forget_between_phases();
+reset client_min_messages;
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..7a4cc7d8ae2
--- /dev/null
+++ b/src/test/modules/test_resowner/test_resowner_basic.c
@@ -0,0 +1,212 @@
+/*--------------------------------------------------------------------------
+ *
+ * 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);
+
+/*
+ * A resource that tracks strings and prints the string when it's released.
+ * This makes the order that the resources are released visible.
+ */
+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");
+
+	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;
+	}
+
+	/* Add a bunch of resources to child, with different priorities */
+	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);
+	}
+
+	/* And also to the parent */
+	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..d8ee455df92
--- /dev/null
+++ b/src/test/modules/test_resowner/test_resowner_many.c
@@ -0,0 +1,290 @@
+/*--------------------------------------------------------------------------
+ *
+ * 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;
+	int			ntotal;
+
+	ntotal = GetTotalResourceCount(kinds, nkinds);
+	if (ntotal < nresources)
+		elog(PANIC, "cannot free %d resources, only %d remembered", nresources, ntotal);
+
+	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();
+}
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 260854747b4..dfba5f5bc05 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2354,8 +2354,10 @@ ReplicationStateOnDisk
 ResTarget
 ReservoirState
 ReservoirStateData
-ResourceArray
+ResourceElem
 ResourceOwner
+ResourceOwnerData
+ResourceOwnerFuncs
 ResourceReleaseCallback
 ResourceReleaseCallbackItem
 ResourceReleasePhase
-- 
2.30.2

v14-0003-Use-a-faster-hash-function-in-resource-owners.patchtext/x-patch; charset=UTF-8; name=v14-0003-Use-a-faster-hash-function-in-resource-owners.patchDownload
From 55298cb193aecf9280c02160bc8ea4430e7ba770 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Mon, 31 Oct 2022 10:49:06 +0100
Subject: [PATCH v14 3/4] 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 c385c996e19..b4e96ca9d63 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

v14-0004-Change-pgcrypto-to-use-the-new-ResourceOwner-mec.patchtext/x-patch; charset=UTF-8; name=v14-0004-Change-pgcrypto-to-use-the-new-ResourceOwner-mec.patchDownload
From 9bd358d3f789a96ce0f7048c0eee1779eb0595f8 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Sat, 11 Dec 2021 00:37:47 +0200
Subject: [PATCH v14 4/4] 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

#56Aleksander Alekseev
aleksander@timescale.com
In reply to: Heikki Linnakangas (#55)
Re: ResourceOwner refactoring

Hi,

[...]
A-ha, it's because I didn't add the new test_resowner directory to the
src/test/modules/meson.build file. Fixed.

Thanks for the review!

You probably already noticed, but for the record: cfbot seems to be
extremely unhappy with the patch.

--
Best regards,
Aleksander Alekseev

#57Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Aleksander Alekseev (#56)
4 attachment(s)
Re: ResourceOwner refactoring

On 23/05/2023 16:41, Aleksander Alekseev wrote:

Hi,

[...]
A-ha, it's because I didn't add the new test_resowner directory to the
src/test/modules/meson.build file. Fixed.

Thanks for the review!

You probably already noticed, but for the record: cfbot seems to be
extremely unhappy with the patch.

Thanks, here's a fixed version. (ResourceOwner resource release
callbacks mustn't call ResourceOwnerForget anymore, but AbortBufferIO
was still doing that)

--
Heikki Linnakangas
Neon (https://neon.tech)

Attachments:

v15-0001-Move-a-few-ResourceOwnerEnlarge-calls-for-safety.patchtext/x-patch; charset=UTF-8; name=v15-0001-Move-a-few-ResourceOwnerEnlarge-calls-for-safety.patchDownload
From 7e96fa2e7d644a9071a1702f560a346d1f2ed4b4 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Mon, 31 Oct 2022 10:33:36 +0100
Subject: [PATCH v15 1/4] 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   | 49 ++++++++++++---------------
 src/backend/storage/buffer/localbuf.c |  2 ++
 src/backend/utils/cache/catcache.c    |  5 +--
 3 files changed, 27 insertions(+), 29 deletions(-)

diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 3c59bbd04ea..7cc124e37bb 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -1023,9 +1023,6 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 								 forkNum, strategy, flags);
 	}
 
-	/* Make sure we will have room to remember the buffer pin */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
-
 	TRACE_POSTGRESQL_BUFFER_READ_START(forkNum, blockNum,
 									   smgr->smgr_rlocator.locator.spcOid,
 									   smgr->smgr_rlocator.locator.dbOid,
@@ -1230,6 +1227,10 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 	BufferDesc *victim_buf_hdr;
 	uint32		victim_buf_state;
 
+	/* Make sure we will have room to remember the buffer pin */
+	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ReservePrivateRefCountEntry();
+
 	/* create a tag so we can lookup the buffer */
 	InitBufferTag(&newTag, &smgr->smgr_rlocator.locator, forkNum, blockNum);
 
@@ -1591,7 +1592,7 @@ GetVictimBuffer(BufferAccessStrategy strategy, IOContext io_context)
 
 	/*
 	 * Ensure, while the spinlock's not yet held, that there's a free refcount
-	 * entry.
+	 * entry, and a resource owner slot for the pin.
 	 */
 	ReservePrivateRefCountEntry();
 	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
@@ -1859,9 +1860,6 @@ ExtendBufferedRelShared(ExtendBufferedWhat eb,
 		MemSet((char *) buf_block, 0, BLCKSZ);
 	}
 
-	/* in case we need to pin an existing buffer below */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
-
 	/*
 	 * Lock relation against concurrent extensions, unless requested not to.
 	 *
@@ -1947,6 +1945,10 @@ ExtendBufferedRelShared(ExtendBufferedWhat eb,
 		LWLock	   *partition_lock;
 		int			existing_id;
 
+		/* in case we need to pin an existing buffer below */
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ReservePrivateRefCountEntry();
+
 		InitBufferTag(&tag, &eb.smgr->smgr_rlocator.locator, fork, first_block + i);
 		hash = BufTableHashCode(&tag);
 		partition_lock = BufMappingPartitionLock(hash);
@@ -2222,7 +2224,8 @@ 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.
+ * Note that ResourceOwnerEnlargeBuffers and ReservePrivateRefCountEntry()
+ * 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.
@@ -2235,6 +2238,7 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
 	PrivateRefCountEntry *ref;
 
 	Assert(!BufferIsLocal(b));
+	Assert(ReservedRefCountEntry != NULL);
 
 	ref = GetPrivateRefCountEntry(b, true);
 
@@ -2243,7 +2247,6 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
 		uint32		buf_state;
 		uint32		old_buf_state;
 
-		ReservePrivateRefCountEntry();
 		ref = NewPrivateRefCountEntry(b);
 
 		old_buf_state = pg_atomic_read_u32(&buf->state);
@@ -2316,7 +2319,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.
@@ -2491,9 +2495,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
@@ -2970,9 +2971,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;
@@ -3054,8 +3052,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)
@@ -3065,7 +3061,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.
@@ -4110,9 +4108,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;
@@ -4126,7 +4121,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) &&
@@ -4183,9 +4180,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;
@@ -4224,7 +4218,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) &&
@@ -4419,9 +4415,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;
@@ -4435,7 +4428,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 f684862d98c..19ef10f434e 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -130,6 +130,8 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
 	if (LocalBufHash == NULL)
 		InitLocalBuffers();
 
+	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..dcc56b04c8f 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,6 +1689,9 @@ 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);
-- 
2.30.2

v15-0002-Make-resowners-more-easily-extensible.patchtext/x-patch; charset=UTF-8; name=v15-0002-Make-resowners-more-easily-extensible.patchDownload
From d220f9faa2d6c00c05e114d5052ec240f7a44a4e Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Mon, 31 Oct 2022 10:48:57 +0100
Subject: [PATCH v15 2/4] 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.

Each resource kind specifies a release priority, and
ResourceOwnerReleaseAll releases the resources in priority 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

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           |   46 +-
 src/backend/access/transam/xact.c             |   15 +
 src/backend/jit/jit.c                         |    2 -
 src/backend/jit/llvm/llvmjit.c                |   49 +-
 src/backend/storage/buffer/bufmgr.c           |  132 +-
 src/backend/storage/buffer/localbuf.c         |   15 +-
 src/backend/storage/file/fd.c                 |   52 +-
 src/backend/storage/ipc/dsm.c                 |   47 +-
 src/backend/storage/lmgr/lock.c               |    2 +-
 src/backend/utils/cache/catcache.c            |   97 +-
 src/backend/utils/cache/plancache.c           |   52 +-
 src/backend/utils/cache/relcache.c            |   59 +-
 src/backend/utils/resowner/README             |   78 +-
 src/backend/utils/resowner/resowner.c         | 1537 ++++++-----------
 src/backend/utils/time/snapmgr.c              |   49 +-
 src/common/cryptohash_openssl.c               |   51 +-
 src/common/hmac_openssl.c                     |   49 +-
 src/include/storage/buf_internals.h           |   15 +
 src/include/storage/bufmgr.h                  |    2 -
 src/include/utils/catcache.h                  |    3 -
 src/include/utils/plancache.h                 |    2 +
 src/include/utils/resowner.h                  |   76 +-
 src/pl/plpgsql/src/pl_exec.c                  |    2 +-
 src/pl/plpgsql/src/pl_handler.c               |    6 +-
 src/test/modules/Makefile                     |    1 +
 src/test/modules/meson.build                  |    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    |   34 +
 .../test_resowner/sql/test_resowner.sql       |   25 +
 .../test_resowner/test_resowner--1.0.sql      |   30 +
 .../test_resowner/test_resowner.control       |    4 +
 .../test_resowner/test_resowner_basic.c       |  212 +++
 .../test_resowner/test_resowner_many.c        |  290 ++++
 src/tools/pgindent/typedefs.list              |    4 +-
 36 files changed, 2149 insertions(+), 1115 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/sql/test_resowner.sql
 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 7c5c390503b..a40f1ae113a 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 =
+{
+	.name = "tupdesc reference",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_TUPDESC_REFS,
+	.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);
 }
@@ -927,3 +945,27 @@ BuildDescFromLists(List *names, List *types, List *typmods, List *collations)
 
 	return desc;
 }
+
+
+/* ResourceOwner callbacks */
+
+static void
+ResOwnerReleaseTupleDesc(Datum 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
+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/access/transam/xact.c b/src/backend/access/transam/xact.c
index 8daaa535edf..0fdcf769cf0 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -5171,9 +5171,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/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 04ae3052a82..82813ed3cff 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 =
+{
+	.name = "LLVM JIT context",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_JIT_CONTEXTS,
+	.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,9 @@ llvm_release_context(JitContext *context)
 	}
 	list_free(llvm_context->handles);
 	llvm_context->handles = NIL;
+
+	if (context->resowner)
+		ResourceOwnerForgetJIT(context->resowner, context);
 }
 
 /*
@@ -1266,3 +1288,24 @@ llvm_error_message(LLVMErrorRef error)
 }
 
 #endif							/* LLVM_VERSION_MAJOR > 11 */
+
+/*
+ * ResourceOwner callbacks
+ */
+static void
+ResOwnerReleaseJitContext(Datum res)
+{
+	JitContext *context = (JitContext *) DatumGetPointer(res);
+
+	context->resowner = NULL;
+	jit_release_context(context);
+}
+
+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 7cc124e37bb..eff32b911c7 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -47,6 +47,7 @@
 #include "postmaster/bgwriter.h"
 #include "storage/buf_internals.h"
 #include "storage/bufmgr.h"
+#include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/proc.h"
@@ -55,7 +56,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"
 
 
@@ -205,6 +206,30 @@ static PrivateRefCountEntry *GetPrivateRefCountEntry(Buffer buffer, bool do_move
 static inline int32 GetPrivateRefCount(Buffer buffer);
 static void ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref);
 
+/* ResourceOwner callbacks to hold in-progress I/Os and buffer pins */
+static void ResOwnerReleaseBufferIO(Datum res);
+static void ResOwnerPrintBufferIOLeakWarning(Datum res);
+static void ResOwnerReleaseBufferPin(Datum res);
+static void ResOwnerPrintBufferPinLeakWarning(Datum res);
+
+ResourceOwnerFuncs buffer_io_resowner_funcs =
+{
+	.name = "buffer_io",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_BUFFER_IOS,
+	.ReleaseResource = ResOwnerReleaseBufferIO,
+	.PrintLeakWarning = ResOwnerPrintBufferIOLeakWarning
+};
+
+ResourceOwnerFuncs buffer_pin_resowner_funcs =
+{
+	.name = "buffer_pin",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_BUFFER_PINS,
+	.ReleaseResource = ResOwnerReleaseBufferPin,
+	.PrintLeakWarning = ResOwnerPrintBufferPinLeakWarning
+};
+
 /*
  * Ensure that the PrivateRefCountArray has sufficient space to store one more
  * entry. This has to be called before using NewPrivateRefCountEntry() to fill
@@ -470,6 +495,7 @@ static BlockNumber ExtendBufferedRelShared(ExtendBufferedWhat eb,
 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,
@@ -477,7 +503,8 @@ static int	SyncOneBuffer(int buf_id, bool skip_recently_used,
 static void WaitIO(BufferDesc *buf);
 static bool StartBufferIO(BufferDesc *buf, bool forInput);
 static void TerminateBufferIO(BufferDesc *buf, bool clear_dirty,
-							  uint32 set_flag_bits);
+							  uint32 set_flag_bits, bool forget_owner);
+static void AbortBufferIO(Buffer buffer, bool forget_owner);
 static void shared_buffer_write_error_callback(void *arg);
 static void local_buffer_write_error_callback(void *arg);
 static BufferDesc *BufferAlloc(SMgrRelation smgr,
@@ -639,7 +666,7 @@ ReadRecentBuffer(RelFileLocator rlocator, ForkNumber forkNum, BlockNumber blockN
 
 	Assert(BufferIsValid(recent_buffer));
 
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	ReservePrivateRefCountEntry();
 	InitBufferTag(&tag, &rlocator, forkNum, blockNum);
 
@@ -1173,7 +1200,7 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 	else
 	{
 		/* Set BM_VALID, terminate IO, and wake up any waiters */
-		TerminateBufferIO(bufHdr, false, BM_VALID);
+		TerminateBufferIO(bufHdr, false, BM_VALID, true);
 	}
 
 	VacuumPageMiss++;
@@ -1228,7 +1255,7 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 	uint32		victim_buf_state;
 
 	/* Make sure we will have room to remember the buffer pin */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	ReservePrivateRefCountEntry();
 
 	/* create a tag so we can lookup the buffer */
@@ -1315,7 +1342,7 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 		 * use.
 		 *
 		 * We could do this after releasing the partition lock, but then we'd
-		 * have to call ResourceOwnerEnlargeBuffers() &
+		 * have to call ResourceOwnerEnlarge() &
 		 * ReservePrivateRefCountEntry() before acquiring the lock, for the
 		 * rare case of such a collision.
 		 */
@@ -1595,7 +1622,7 @@ GetVictimBuffer(BufferAccessStrategy strategy, IOContext io_context)
 	 * entry, and a resource owner slot for the pin.
 	 */
 	ReservePrivateRefCountEntry();
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	/* we return here if a prospective victim buffer gets used concurrently */
 again:
@@ -1946,7 +1973,7 @@ ExtendBufferedRelShared(ExtendBufferedWhat eb,
 		int			existing_id;
 
 		/* in case we need to pin an existing buffer below */
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 		ReservePrivateRefCountEntry();
 
 		InitBufferTag(&tag, &eb.smgr->smgr_rlocator.locator, fork, first_block + i);
@@ -2090,7 +2117,7 @@ ExtendBufferedRelShared(ExtendBufferedWhat eb,
 		if (lock)
 			LWLockAcquire(BufferDescriptorGetContentLock(buf_hdr), LW_EXCLUSIVE);
 
-		TerminateBufferIO(buf_hdr, false, BM_VALID);
+		TerminateBufferIO(buf_hdr, false, BM_VALID, true);
 	}
 
 	pgBufferUsage.shared_blks_written += extend_by;
@@ -2224,7 +2251,7 @@ 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 and ReservePrivateRefCountEntry()
+ * Note that ResourceOwnerEnlarge() and ReservePrivateRefCountEntry()
  * must have been done already.
  *
  * Returns true if buffer is BM_VALID, else false.  This provision allows
@@ -2320,7 +2347,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.
@@ -2381,6 +2408,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);
@@ -2390,9 +2426,6 @@ 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)
@@ -3063,7 +3096,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.
@@ -3463,7 +3496,7 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln, IOObject io_object,
 	 * Mark the buffer as clean (unless BM_JUST_DIRTIED has become set) and
 	 * end the BM_IO_IN_PROGRESS state.
 	 */
-	TerminateBufferIO(buf, true, 0);
+	TerminateBufferIO(buf, true, 0, true);
 
 	TRACE_POSTGRESQL_BUFFER_FLUSH_DONE(BufTagGetForkNum(&buf->tag),
 									   buf->tag.blockNum,
@@ -4123,7 +4156,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) &&
@@ -4220,7 +4253,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) &&
@@ -4430,7 +4463,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 &&
@@ -4507,7 +4540,7 @@ void
 IncrBufferRefCount(Buffer buffer)
 {
 	Assert(BufferIsPinned(buffer));
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	if (BufferIsLocal(buffer))
 		LocalRefCount[-buffer - 1]++;
 	else
@@ -5105,7 +5138,7 @@ StartBufferIO(BufferDesc *buf, bool forInput)
 {
 	uint32		buf_state;
 
-	ResourceOwnerEnlargeBufferIOs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	for (;;)
 	{
@@ -5150,9 +5183,14 @@ StartBufferIO(BufferDesc *buf, bool forInput)
  * set_flag_bits gets ORed into the buffer's flags.  It must include
  * BM_IO_ERROR in a failure case.  For successful completion it could
  * be 0, or BM_VALID if we just finished reading in the page.
+ *
+ * If forget_owner is true, we release the buffer I/O from the current
+ * resource owner. (forget_owner=false is used when the resource owner itself
+ * is being released)
  */
 static void
-TerminateBufferIO(BufferDesc *buf, bool clear_dirty, uint32 set_flag_bits)
+TerminateBufferIO(BufferDesc *buf, bool clear_dirty, uint32 set_flag_bits,
+				  bool forget_owner)
 {
 	uint32		buf_state;
 
@@ -5167,8 +5205,9 @@ TerminateBufferIO(BufferDesc *buf, bool clear_dirty, uint32 set_flag_bits)
 	buf_state |= set_flag_bits;
 	UnlockBufHdr(buf, buf_state);
 
-	ResourceOwnerForgetBufferIO(CurrentResourceOwner,
-								BufferDescriptorGetBuffer(buf));
+	if (forget_owner)
+		ResourceOwnerForgetBufferIO(CurrentResourceOwner,
+									BufferDescriptorGetBuffer(buf));
 
 	ConditionVariableBroadcast(BufferDescriptorGetIOCV(buf));
 }
@@ -5182,8 +5221,8 @@ TerminateBufferIO(BufferDesc *buf, bool clear_dirty, uint32 set_flag_bits)
  *	If I/O was in progress, we always set BM_IO_ERROR, even though it's
  *	possible the error condition wasn't related to the I/O.
  */
-void
-AbortBufferIO(Buffer buffer)
+static void
+AbortBufferIO(Buffer buffer, bool forget_owner)
 {
 	BufferDesc *buf_hdr = GetBufferDescriptor(buffer - 1);
 	uint32		buf_state;
@@ -5218,7 +5257,7 @@ AbortBufferIO(Buffer buffer)
 		}
 	}
 
-	TerminateBufferIO(buf_hdr, false, BM_IO_ERROR);
+	TerminateBufferIO(buf_hdr, false, BM_IO_ERROR, forget_owner);
 }
 
 /*
@@ -5587,3 +5626,42 @@ TestForOldSnapshot_impl(Snapshot snapshot, Relation relation)
 				(errcode(ERRCODE_SNAPSHOT_TOO_OLD),
 				 errmsg("snapshot too old")));
 }
+
+/* ResourceOwner callbacks */
+
+static void
+ResOwnerReleaseBufferIO(Datum res)
+{
+	Buffer		buffer = DatumGetInt32(res);
+
+	AbortBufferIO(buffer, false);
+}
+
+static void
+ResOwnerPrintBufferIOLeakWarning(Datum res)
+{
+	Buffer		buffer = DatumGetInt32(res);
+
+	elog(PANIC, "lost track of buffer IO on buffer %d", buffer);
+}
+
+static void
+ResOwnerReleaseBufferPin(Datum 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))
+		UnpinLocalBufferNoOwner(buffer);
+	else
+		UnpinBufferNoOwner(GetBufferDescriptor(buffer - 1));
+}
+
+static void
+ResOwnerPrintBufferPinLeakWarning(Datum res)
+{
+	PrintBufferLeakWarning(DatumGetInt32(res));
+}
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index 19ef10f434e..27efba8c6e3 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -21,9 +21,10 @@
 #include "pgstat.h"
 #include "storage/buf_internals.h"
 #include "storage/bufmgr.h"
+#include "storage/fd.h"
 #include "utils/guc_hooks.h"
 #include "utils/memutils.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 
 
 /*#define LBDEBUG*/
@@ -130,7 +131,7 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
 	if (LocalBufHash == NULL)
 		InitLocalBuffers();
 
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	/* See if the desired buffer already exists */
 	hresult = (LocalBufferLookupEnt *)
@@ -182,7 +183,7 @@ GetLocalVictimBuffer(void)
 	uint32		buf_state;
 	BufferDesc *bufHdr;
 
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	/*
 	 * Need to get a new buffer.  We use a clock sweep algorithm (essentially
@@ -674,6 +675,13 @@ PinLocalBuffer(BufferDesc *buf_hdr, bool adjust_usagecount)
 
 void
 UnpinLocalBuffer(Buffer buffer)
+{
+	UnpinLocalBufferNoOwner(buffer);
+	ResourceOwnerForgetBuffer(CurrentResourceOwner, buffer);
+}
+
+void
+UnpinLocalBufferNoOwner(Buffer buffer)
 {
 	int			buffid = -buffer - 1;
 
@@ -681,7 +689,6 @@ UnpinLocalBuffer(Buffer buffer)
 	Assert(LocalRefCount[buffid] > 0);
 	Assert(NLocalPinnedBuffers > 0);
 
-	ResourceOwnerForgetBuffer(CurrentResourceOwner, buffer);
 	if (--LocalRefCount[buffid] == 0)
 		NLocalPinnedBuffers--;
 }
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 173476789c7..76a83239178 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -99,7 +99,7 @@
 #include "storage/ipc.h"
 #include "utils/guc.h"
 #include "utils/guc_hooks.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 #include "utils/varlena.h"
 
 /* Define PG_FLUSH_DATA_WORKS if we have an implementation for pg_flush_data */
@@ -354,6 +354,25 @@ 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",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_FILES,
+	.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
  */
@@ -1451,7 +1470,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
@@ -1643,7 +1662,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
@@ -1775,7 +1794,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
@@ -1815,7 +1834,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);
 
@@ -3924,3 +3943,26 @@ assign_io_direct(const char *newval, void *extra)
 
 	io_direct_flags = *flags;
 }
+
+/* ResourceOwner callbacks */
+
+static void
+ResOwnerReleaseFile(Datum res)
+{
+	File		file = (File) DatumGetInt32(res);
+	Vfd		   *vfdP;
+
+	Assert(FileIsValid(file));
+
+	vfdP = &VfdCache[file];
+	vfdP->resowner = NULL;
+
+	FileClose(file);
+}
+
+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..21c53bc6ae4 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,26 @@ 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",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_DSMS,
+	.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 +929,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 +1196,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 +1275,22 @@ is_main_region_dsm_handle(dsm_handle handle)
 {
 	return handle & 1;
 }
+
+/* ResourceOwner callbacks */
+
+static void
+ResOwnerReleaseDSM(Datum res)
+{
+	dsm_segment *seg = (dsm_segment *) res;
+
+	seg->resowner = NULL;
+	dsm_detach(seg);
+}
+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 193f50fc0f4..f0e9d79b9a1 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 dcc56b04c8f..4f2af4a5b72 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"
 
 
@@ -94,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,
@@ -104,6 +107,44 @@ 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",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_CATCACHE_REFS,
+	.ReleaseResource = ResOwnerReleaseCatCache,
+	.PrintLeakWarning = ResOwnerPrintCatCacheLeakWarning
+};
+
+static ResourceOwnerFuncs catlistref_resowner_funcs =
+{
+	/* catcache-list pins */
+	.name = "catcache list reference",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_CATCACHE_LIST_REFS,
+	.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 +1306,7 @@ SearchCatCacheInternal(CatCache *cache,
 		 */
 		if (!ct->negative)
 		{
-			ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner);
+			ResourceOwnerEnlarge(CurrentResourceOwner);
 			ct->refcount++;
 			ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple);
 
@@ -1366,7 +1407,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 */
@@ -1433,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));
@@ -1442,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
@@ -1578,7 +1626,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 +1738,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);
@@ -1775,12 +1823,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
@@ -2056,14 +2111,18 @@ PrepareToInvalidateCacheTuple(Relation relation,
 	}
 }
 
+/* ResourceOwner callbacks */
 
-/*
- * Subroutines for warning about reference leaks.  These are exported so
- * that resowner.c can call them.
- */
-void
-PrintCatCacheLeakWarning(HeapTuple tuple)
+static void
+ResOwnerReleaseCatCache(Datum res)
+{
+	ReleaseCatCacheWithOwner((HeapTuple) DatumGetPointer(res), NULL);
+}
+
+static void
+ResOwnerPrintCatCacheLeakWarning(Datum res)
 {
+	HeapTuple	tuple = (HeapTuple) DatumGetPointer(res);
 	CatCTup    *ct = (CatCTup *) (((char *) tuple) -
 								  offsetof(CatCTup, tuple));
 
@@ -2077,9 +2136,17 @@ PrintCatCacheLeakWarning(HeapTuple tuple)
 		 ct->refcount);
 }
 
-void
-PrintCatCacheListLeakWarning(CatCList *list)
+static void
+ResOwnerReleaseCatCacheList(Datum res)
 {
+	ReleaseCatCacheListWithOwner((CatCList *) DatumGetPointer(res), NULL);
+}
+
+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 87210fcf627..a6710c2bab9 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);
+
+static ResourceOwnerFuncs planref_resowner_funcs =
+{
+	.name = "plancache reference",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_PLANCACHE_REFS,
+	.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);
 	}
@@ -2214,3 +2234,27 @@ ResetPlanCache(void)
 		cexpr->is_valid = false;
 	}
 }
+
+/*
+ * 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), NULL);
+}
+
+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 8a08463c2b7..d3980849153 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"
 
@@ -273,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);
 
@@ -2115,6 +2117,25 @@ 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",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_RELCACHE_REFS,
+	.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 +2147,7 @@ RelationIdGetRelation(Oid relationId)
 void
 RelationIncrementReferenceCount(Relation rel)
 {
-	ResourceOwnerEnlargeRelationRefs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	rel->rd_refcnt += 1;
 	if (!IsBootstrapProcessingMode())
 		ResourceOwnerRememberRelationRef(CurrentResourceOwner, rel);
@@ -2162,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
@@ -6813,3 +6840,31 @@ unlink_initfile(const char *initfilename, int elevel)
 							initfilename)));
 	}
 }
+
+/*
+ * ResourceOwner callbacks
+ */
+static void
+ResOwnerPrintRelCacheLeakWarning(Datum res)
+{
+	Relation	rel = (Relation) DatumGetPointer(res);
+
+	elog(WARNING, "relcache reference leak: relation \"%s\" not closed",
+		 RelationGetRelationName(rel));
+}
+
+static void
+ResOwnerReleaseRelation(Datum 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 f94c9700df4..2a703a395dc 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
@@ -79,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 f926f1faad3..c385c996e19 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 the 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
@@ -20,85 +44,54 @@
  */
 #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/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 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 table.  Must be power of
+ * two because we 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.
- * 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 RESARRAY_MAX_ITEMS(capacity) \
-	((capacity) <= RESARRAY_MAX_ARRAY ? (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,
+				 "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
@@ -119,25 +112,35 @@ 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 bufferioarr;	/* in-progress buffer IO */
-	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 */
-
-	/* We can remember up to MAX_RESOWNER_LOCKS references to local locks. */
-	int			nlocks;			/* number of owned locks */
+	bool		releasing;		/* has ResourceOwnerRelease been called? */
+
+	/*
+	 * The fixed-size array for recent resources.
+	 *
+	 * If 'releasing' is set, the contents are sorted by release priority.
+	 */
+	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 */
+
+	/* The local locks cache. */
+	uint32		nlocks;			/* number of owned locks */
 	LOCALLOCK  *locks[MAX_RESOWNER_LOCKS];	/* list of owned locks */
-}			ResourceOwnerData;
+} ResourceOwnerData;
 
 
 /*****************************************************************************
@@ -149,6 +152,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
  */
@@ -163,253 +178,195 @@ 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 int	resource_priority_cmp(const void *a, const void *b);
+static void ResourceOwnerSort(ResourceOwner owner);
+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														 *
  *****************************************************************************/
 
-
-/*
- * Initialize a ResourceArray
- */
-static void
-ResourceArrayInit(ResourceArray *resarr, Datum invalidval)
+static inline uint32
+hash_resource_elem(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 */
+	Datum		data[2];
+
+	data[0] = value;
+	data[1] = PointerGetDatum(kind);
+
+	return hash_bytes((unsigned char *) &data, 2 * SIZEOF_DATUM);
 }
 
 /*
- * 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.
+ * Adds 'value' of given 'kind' to the ResourceOwner's hash table
  */
 static void
-ResourceArrayEnlarge(ResourceArray *resarr)
+ResourceOwnerAddToHash(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind)
 {
-	uint32		i,
-				oldcap,
-				newcap;
-	Datum	   *olditemsarr;
-	Datum	   *newitemsarr;
-
-	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;
+	uint32		mask = owner->capacity - 1;
+	uint32		idx;
 
-	/* 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;
+	Assert(kind != NULL);
 
-	if (olditemsarr != NULL)
+	/* Insert into first free slot at or after hash location. */
+	idx = hash_resource_elem(value, kind) & mask;
+	for (;;)
 	{
-		/*
-		 * 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.
-		 */
-		for (i = 0; i < oldcap; i++)
-		{
-			if (olditemsarr[i] != resarr->invalidval)
-				ResourceArrayAdd(resarr, olditemsarr[i]);
-		}
-
-		/* And release old array. */
-		pfree(olditemsarr);
+		if (owner->hash[idx].kind == NULL)
+			break;				/* found a free slot */
+		idx = (idx + 1) & mask;
 	}
-
-	Assert(resarr->nitems < resarr->maxitems);
+	owner->hash[idx].item = value;
+	owner->hash[idx].kind = kind;
+	owner->nhash++;
 }
 
 /*
- * Add a resource to ResourceArray
- *
- * Caller must have previously done ResourceArrayEnlarge()
+ * Comparison function to sort by release phase and priority
  */
-static void
-ResourceArrayAdd(ResourceArray *resarr, Datum value)
+static int
+resource_priority_cmp(const void *a, const void *b)
 {
-	uint32		idx;
+	const ResourceElem *ra = (const ResourceElem *) a;
+	const ResourceElem *rb = (const ResourceElem *) b;
 
-	Assert(value != resarr->invalidval);
-	Assert(resarr->nitems < resarr->maxitems);
-
-	if (RESARRAY_IS_ARRAY(resarr))
+	/* Note: reverse order */
+	if (ra->kind->release_phase == rb->kind->release_phase)
 	{
-		/* Append to linear array. */
-		idx = resarr->nitems;
+		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
-	{
-		/* 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++;
+		return 1;
 }
 
 /*
- * Remove a resource from ResourceArray
+ * Sort resources in reverse release priority.
  *
- * Returns true on success, false if resource was not found.
- *
- * Note: if same resource ID appears more than once, one instance is removed.
+ * 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 bool
-ResourceArrayRemove(ResourceArray *resarr, Datum value)
+static void
+ResourceOwnerSort(ResourceOwner owner)
 {
-	uint32		i,
-				idx,
-				lastidx = resarr->lastidx;
+	ResourceElem *items;
+	uint32		nitems;
 
-	Assert(value != resarr->invalidval);
-
-	/* Search through all items, but try lastidx first. */
-	if (RESARRAY_IS_ARRAY(resarr))
+	if (!owner->hash)
 	{
-		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++)
-		{
-			if (resarr->itemsarr[i] == value)
-			{
-				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;
-			}
-		}
+		items = owner->arr;
+		nitems = owner->narr;
 	}
 	else
 	{
-		uint32		mask = resarr->capacity - 1;
+		/*
+		 * Compact the hash table, so that all the elements are in the
+		 * beginning of the 'hash' array, with no empty elements.
+		 */
+		uint32		dst = 0;
 
-		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++)
+		for (int idx = 0; idx < owner->capacity; idx++)
 		{
-			if (resarr->itemsarr[idx] == value)
+			if (owner->hash[idx].kind != NULL)
 			{
-				resarr->itemsarr[idx] = resarr->invalidval;
-				resarr->nitems--;
-				return true;
+				if (dst != idx)
+					owner->hash[dst] = owner->hash[idx];
+				dst++;
 			}
-			idx = (idx + 1) & mask;
 		}
+
+		/*
+		 * 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.
+		 */
+		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;
 	}
 
-	return false;
+	qsort(items, nitems, sizeof(ResourceElem), resource_priority_cmp);
 }
 
 /*
- * 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.
- *
- * Returns true if we found an element, or false if the array is empty.
+ * Call the ReleaseResource callback on entries with given 'phase'.
  */
-static bool
-ResourceArrayGetAny(ResourceArray *resarr, Datum *value)
+static void
+ResourceOwnerReleaseAll(ResourceOwner owner, ResourceReleasePhase phase,
+						bool printLeakWarnings)
 {
-	if (resarr->nitems == 0)
-		return false;
+	ResourceElem *items;
+	uint32		nitems;
 
-	if (RESARRAY_IS_ARRAY(resarr))
+	/* ResourceOwnerSort must've been called already */
+	Assert(owner->releasing);
+	if (!owner->hash)
 	{
-		/* Linear array: just return the first element. */
-		resarr->lastidx = 0;
+		items = owner->arr;
+		nitems = owner->narr;
 	}
 	else
 	{
-		/* Hash: search forward from wherever we were last. */
-		uint32		mask = resarr->capacity - 1;
-
-		for (;;)
-		{
-			resarr->lastidx &= mask;
-			if (resarr->itemsarr[resarr->lastidx] != resarr->invalidval)
-				break;
-			resarr->lastidx++;
-		}
+		Assert(owner->narr == 0);
+		items = owner->hash;
+		nitems = owner->nhash;
 	}
 
-	*value = resarr->itemsarr[resarr->lastidx];
-	return true;
-}
+	/*
+	 * 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.
+	 */
+	while (nitems > 0)
+	{
+		uint32		idx = nitems - 1;
+		Datum		value = items[idx].item;
+		ResourceOwnerFuncs *kind = items[idx].kind;
 
-/*
- * Trash a ResourceArray (we don't care about its state after this)
- */
-static void
-ResourceArrayFree(ResourceArray *resarr)
-{
-	if (resarr->itemsarr)
-		pfree(resarr->itemsarr);
+		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;
 }
 
 
@@ -441,23 +398,199 @@ ResourceOwnerCreate(ResourceOwner parent, const char *name)
 		parent->firstchild = owner;
 	}
 
-	ResourceArrayInit(&(owner->bufferarr), BufferGetDatum(InvalidBuffer));
-	ResourceArrayInit(&(owner->bufferioarr), 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));
+#ifdef RESOWNER_TRACE
+	elog(LOG, "CREATE %d: %p %s",
+		 resowner_trace_counter++, owner, name);
+#endif
 
 	return owner;
 }
 
+/*
+ * 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.
+ *
+ * NB: Make sure there are no unrelated ResourceOwnerRemember() calls between
+ * your ResourceOwnerEnlarge() call and the ResourceOwnerRemember() call that
+ * you reserved the space for!
+ */
+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.
+	 */
+	if (owner->narr + owner->nhash >= owner->grow_at)
+	{
+		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)
+		{
+			/*
+			 * 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++)
+			{
+				if (oldhash[i].kind != NULL)
+					ResourceOwnerAddToHash(owner, oldhash[i].item, oldhash[i].kind);
+			}
+
+			/* And release old hash table. */
+			pfree(oldhash);
+		}
+	}
+
+	/* Move items from the array to the hash */
+	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);
+}
+
+/*
+ * Remember that an object is owner by a ReourceOwner
+ *
+ * Caller must have previously done ResourceOwnerEnlarge()
+ */
+void
+ResourceOwnerRemember(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind)
+{
+	uint32		idx;
+
+#ifdef RESOWNER_TRACE
+	elog(LOG, "REMEMBER %d: owner %p value " UINT64_FORMAT ", kind: %s",
+		 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 the array. */
+	idx = owner->narr;
+	owner->arr[idx].item = value;
+	owner->arr[idx].kind = kind;
+	owner->narr++;
+}
+
+/*
+ * 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.
+ */
+void
+ResourceOwnerForget(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind)
+{
+#ifdef RESOWNER_TRACE
+	elog(LOG, "FORGET %d: owner %p value " UINT64_FORMAT ", kind: %s",
+		 resowner_trace_counter++, owner, DatumGetUInt64(value), kind->name);
+#endif
+
+	/*
+	 * 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)
+		{
+			owner->arr[i] = owner->arr[owner->narr - 1];
+			owner->narr--;
+
+#ifdef RESOWNER_STATS
+			narray_lookups++;
+#endif
+
+			return;
+		}
+	}
+
+	/* Search hash */
+	if (owner->nhash > 0)
+	{
+		uint32		mask = owner->capacity - 1;
+		uint32		idx;
+
+		idx = hash_resource_elem(value, kind) & mask;
+		for (uint32 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;
+		}
+	}
+
+	/*
+	 * 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);
+}
+
 /*
  * ResourceOwnerRelease
  *		Release all resources owned by a ResourceOwner and its descendants,
@@ -483,6 +616,12 @@ ResourceOwnerCreate(ResourceOwner parent, const char *name)
  * 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,
@@ -492,6 +631,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
@@ -504,105 +652,52 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 	ResourceOwner save;
 	ResourceReleaseCallbackItem *item;
 	ResourceReleaseCallbackItem *next;
-	Datum		foundres;
 
 	/* Recurse to handle descendants */
 	for (child = owner->firstchild; child != NULL; child = child->nextchild)
 		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.
 	 */
-	save = CurrentResourceOwner;
-	CurrentResourceOwner = owner;
-
-	if (phase == RESOURCE_RELEASE_BEFORE_LOCKS)
+	if (!owner->releasing)
+	{
+		Assert(phase == RESOURCE_RELEASE_BEFORE_LOCKS);
+		ResourceOwnerSort(owner);
+		owner->releasing = true;
+	}
+	else
 	{
 		/*
-		 * Abort failed buffer IO. AbortBufferIO()->TerminateBufferIO() calls
-		 * ResourceOwnerForgetBufferIO(), so we just have to iterate till
-		 * there are none.
-		 *
-		 * Needs to be before we release buffer pins.
-		 *
-		 * During a commit, there shouldn't be any in-progress IO.
+		 * 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.
 		 */
-		while (ResourceArrayGetAny(&(owner->bufferioarr), &foundres))
-		{
-			Buffer		res = DatumGetBuffer(foundres);
+	}
 
-			if (isCommit)
-				elog(PANIC, "lost track of buffer IO on buffer %d", res);
-			AbortBufferIO(res);
-		}
+	/*
+	 * Make CurrentResourceOwner point to me, so that the release callback
+	 * functions know which resource owner is been released.
+	 */
+	save = CurrentResourceOwner;
+	CurrentResourceOwner = 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)
 	{
@@ -655,101 +750,70 @@ 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);
+		ResourceOwnerReleaseAll(owner, phase, isCommit);
+	}
 
-			if (isCommit)
-				PrintCatCacheLeakWarning(res);
-			ReleaseCatCache(res);
-		}
+	/* Let add-on modules get a chance too */
+	for (item = ResourceRelease_callbacks; item; item = next)
+	{
+		/* allow callbacks to unregister themselves when called */
+		next = item->next;
+		item->callback(phase, isCommit, isTopLevel, item->arg);
+	}
 
-		/* Ditto for catcache lists */
-		while (ResourceArrayGetAny(&(owner->catlistrefarr), &foundres))
-		{
-			CatCList   *res = (CatCList *) DatumGetPointer(foundres);
+	CurrentResourceOwner = save;
+}
 
-			if (isCommit)
-				PrintCatCacheListLeakWarning(res);
-			ReleaseCatCacheList(res);
-		}
+/*
+ * ResourceOwnerReleaseAllOfKind
+ *		Release all resources of a certain type held by this owner.
+ */
+void
+ResourceOwnerReleaseAllOfKind(ResourceOwner owner, ResourceOwnerFuncs *kind)
+{
+	/* 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);
 
-		/* Ditto for plancache references */
-		while (ResourceArrayGetAny(&(owner->planrefarr), &foundres))
+	/*
+	 * 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 == kind)
 		{
-			CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres);
+			Datum		value = owner->arr[i].item;
 
-			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);
+			owner->arr[i] = owner->arr[owner->narr - 1];
+			owner->narr--;
+			i--;
 
-			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);
+			kind->ReleaseResource(value);
 		}
 	}
 
-	/* Let add-on modules get a chance too */
-	for (item = ResourceRelease_callbacks; item; item = next)
+	/* Then hash */
+	for (int i = 0; i < owner->capacity; i++)
 	{
-		/* allow callbacks to unregister themselves when called */
-		next = item->next;
-		item->callback(phase, isCommit, isTopLevel, item->arg);
-	}
-
-	CurrentResourceOwner = save;
-}
-
-/*
- * 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.
- */
-void
-ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner)
-{
-	Datum		foundres;
+		if (owner->hash[i].kind == kind)
+		{
+			Datum		value = owner->hash[i].item;
 
-	while (ResourceArrayGetAny(&(owner->planrefarr), &foundres))
-	{
-		CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres);
+			owner->hash[i].item = (Datum) 0;
+			owner->hash[i].kind = NULL;
+			owner->nhash--;
 
-		ReleaseCachedPlan(res, owner);
+			kind->ReleaseResource(value);
+		}
 	}
+	owner->releasing = false;
 }
 
 /*
@@ -765,21 +829,15 @@ ResourceOwnerDelete(ResourceOwner owner)
 	Assert(owner != CurrentResourceOwner);
 
 	/* And it better not own any resources, either */
-	Assert(owner->bufferarr.nitems == 0);
-	Assert(owner->bufferioarr.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.
@@ -795,20 +853,8 @@ ResourceOwnerDelete(ResourceOwner owner)
 	ResourceOwnerNewParent(owner, NULL);
 
 	/* And free the object. */
-	ResourceArrayFree(&(owner->bufferarr));
-	ResourceArrayFree(&(owner->bufferioarr));
-	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);
 }
 
@@ -866,11 +912,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)
@@ -946,6 +991,8 @@ ReleaseAuxProcessResources(bool isCommit)
 	ResourceOwnerRelease(AuxProcessResourceOwner,
 						 RESOURCE_RELEASE_AFTER_LOCKS,
 						 isCommit, true);
+	/* allow it to be reused */
+	AuxProcessResourceOwner->releasing = false;
 }
 
 /*
@@ -960,88 +1007,12 @@ ReleaseAuxProcessResourcesCallback(int code, Datum arg)
 	ReleaseAuxProcessResources(isCommit);
 }
 
-
-/*
- * Make sure there is room for at least one more entry in a ResourceOwner's
- * buffer 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
-ResourceOwnerEnlargeBuffers(ResourceOwner owner)
-{
-	/* 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);
-}
-
-
-/*
- * Make sure there is room for at least one more entry in a ResourceOwner's
- * buffer 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
-ResourceOwnerEnlargeBufferIOs(ResourceOwner owner)
-{
-	/* We used to allow pinning buffers without a resowner, but no more */
-	Assert(owner != NULL);
-	ResourceArrayEnlarge(&(owner->bufferioarr));
-}
-
-/*
- * Remember that a buffer IO is owned by a ResourceOwner
- *
- * Caller must have previously done ResourceOwnerEnlargeBufferIOs()
- */
-void
-ResourceOwnerRememberBufferIO(ResourceOwner owner, Buffer buffer)
-{
-	ResourceArrayAdd(&(owner->bufferioarr), BufferGetDatum(buffer));
-}
-
-/*
- * Forget that a buffer IO is owned by a ResourceOwner
- */
-void
-ResourceOwnerForgetBufferIO(ResourceOwner owner, Buffer buffer)
-{
-	if (!ResourceArrayRemove(&(owner->bufferioarr), BufferGetDatum(buffer)))
-		elog(PANIC, "buffer IO %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
+ * 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,
  * ResourceOwnerForgetLock doesn't need to scan through a large array to find
  * the entry.
@@ -1087,469 +1058,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 3a419e348fa..46c1d5368b4 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"
@@ -172,9 +173,29 @@ 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);
 
+/* ResourceOwner callbacks to track snapshot references */
+static void ResOwnerReleaseSnapshot(Datum res);
+static void ResOwnerPrintSnapshotLeakWarning(Datum res);
+
+static ResourceOwnerFuncs snapshot_resowner_funcs =
+{
+	.name = "snapshot reference",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_SNAPSHOT_REFS,
+	.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 +871,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);
 
@@ -886,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);
@@ -2379,3 +2405,18 @@ XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot)
 
 	return false;
 }
+
+/* ResourceOwner callbacks */
+
+static void
+ResOwnerReleaseSnapshot(Datum res)
+{
+	UnregisterSnapshotNoOwner((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..5254e53f114 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 =
+{
+	.name = "OpenSSL cryptohash context",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_CRYPTOHASH_CONTEXTS,
+	.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,8 @@ pg_cryptohash_free(pg_cryptohash_ctx *ctx)
 	EVP_MD_CTX_destroy(ctx->evpctx);
 
 #ifndef FRONTEND
-	ResourceOwnerForgetCryptoHash(ctx->resowner,
-								  PointerGetDatum(ctx));
+	if (ctx->resowner)
+		ResourceOwnerForgetCryptoHash(ctx->resowner, ctx);
 #endif
 
 	explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
@@ -351,3 +370,23 @@ 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_ctx *ctx = (pg_cryptohash_ctx *) DatumGetPointer(res);
+
+	ctx->resowner = NULL;
+	pg_cryptohash_free(ctx);
+}
+
+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..9b47ad63ba1 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 =
+{
+	.name = "OpenSSL HMAC context",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_HMAC_CONTEXTS,
+	.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,8 @@ pg_hmac_free(pg_hmac_ctx *ctx)
 #ifdef HAVE_HMAC_CTX_FREE
 	HMAC_CTX_free(ctx->hmacctx);
 #ifndef FRONTEND
-	ResourceOwnerForgetHMAC(ctx->resowner, PointerGetDatum(ctx));
+	if (ctx->resowner)
+		ResourceOwnerForgetHMAC(ctx->resowner, ctx);
 #endif
 #else
 	explicit_bzero(ctx->hmacctx, sizeof(HMAC_CTX));
@@ -346,3 +367,23 @@ 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_ctx *ctx = (pg_hmac_ctx *) DatumGetPointer(res);
+
+	ctx->resowner = NULL;
+	pg_hmac_free(ctx);
+}
+
+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 30807d5d97e..1e8ab084e19 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -383,6 +383,20 @@ typedef struct CkptSortItem
 
 extern PGDLLIMPORT CkptSortItem *CkptBufferIds;
 
+/* ResourceOwner callbacks to hold buffer I/Os and pins */
+extern ResourceOwnerFuncs buffer_io_resowner_funcs;
+extern ResourceOwnerFuncs buffer_pin_resowner_funcs;
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+#define ResourceOwnerRememberBuffer(owner, buffer) \
+	ResourceOwnerRemember(owner, Int32GetDatum(buffer), &buffer_pin_resowner_funcs)
+#define ResourceOwnerForgetBuffer(owner, buffer) \
+	ResourceOwnerForget(owner, Int32GetDatum(buffer), &buffer_pin_resowner_funcs)
+#define ResourceOwnerRememberBufferIO(owner, buffer) \
+	ResourceOwnerRemember(owner, Int32GetDatum(buffer), &buffer_io_resowner_funcs)
+#define ResourceOwnerForgetBufferIO(owner, buffer) \
+	ResourceOwnerForget(owner, Int32GetDatum(buffer), &buffer_io_resowner_funcs)
+
 /*
  * Internal buffer management routines
  */
@@ -418,6 +432,7 @@ extern void BufTableDelete(BufferTag *tagPtr, uint32 hashcode);
 /* localbuf.c */
 extern bool PinLocalBuffer(BufferDesc *buf_hdr, bool adjust_usagecount);
 extern void UnpinLocalBuffer(Buffer buffer);
+extern void UnpinLocalBufferNoOwner(Buffer buffer);
 extern PrefetchBufferResult PrefetchLocalBuffer(SMgrRelation smgr,
 												ForkNumber forkNum,
 												BlockNumber blockNum);
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 0f5fb6be00e..950449bd3fe 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -246,8 +246,6 @@ extern bool ConditionalLockBufferForCleanup(Buffer buffer);
 extern bool IsBufferCleanupOK(Buffer buffer);
 extern bool HoldingBufferPinThatDelaysRecovery(void);
 
-extern void AbortBufferIO(Buffer buffer);
-
 extern bool BgBufferSync(struct WritebackContext *wb_context);
 
 extern void TestForOldSnapshot_impl(Snapshot snapshot, Relation relation);
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..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);
diff --git a/src/include/utils/resowner.h b/src/include/utils/resowner.h
index cd070b6080e..f692dbb6274 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,63 @@ typedef enum
 	RESOURCE_RELEASE_AFTER_LOCKS
 } ResourceReleasePhase;
 
+typedef uint32 ResourceReleasePriority;
+
+/* priorities of built-in BEFORE_LOCKS resources */
+#define RELEASE_PRIO_BUFFER_IOS			    100
+#define RELEASE_PRIO_BUFFER_PINS		    200
+#define RELEASE_PRIO_RELCACHE_REFS			300
+#define RELEASE_PRIO_DSMS					400
+#define RELEASE_PRIO_JIT_CONTEXTS			500
+#define RELEASE_PRIO_CRYPTOHASH_CONTEXTS	600
+#define RELEASE_PRIO_HMAC_CONTEXTS			700
+
+/* 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
+ * 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 */
+
+	/* when are these objects released? */
+	ResourceReleasePhase release_phase;
+	ResourceReleasePriority release_priority;
+
+	/*
+	 * Release resource.
+	 *
+	 * 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);
+
+	/*
+	 * 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 +133,28 @@ 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 ResourceOwnerReleaseAllOfKind(ResourceOwner owner, 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);
+
 #endif							/* RESOWNER_H */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 4b76f7699a6..f8c7f487470 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -8470,7 +8470,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 6331c976dcb..c4d880bc361 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -29,6 +29,7 @@ SUBDIRS = \
 		  test_predtest \
 		  test_rbtree \
 		  test_regex \
+		  test_resowner \
 		  test_rls_hooks \
 		  test_shm_mq \
 		  test_slru \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index 17d369e3789..e0bb14c2eeb 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -26,6 +26,7 @@ subdir('test_pg_dump')
 subdir('test_predtest')
 subdir('test_rbtree')
 subdir('test_regex')
+subdir('test_resowner')
 subdir('test_rls_hooks')
 subdir('test_shm_mq')
 subdir('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..5f669505a65
--- /dev/null
+++ b/src/test/modules/test_resowner/meson.build
@@ -0,0 +1,34 @@
+# Copyright (c) 2022-2023, PostgreSQL Global Development Group
+
+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_test_mod_args,
+)
+test_install_libs += test_resowner
+
+test_install_data += files(
+  'test_resowner.control',
+  'test_resowner--1.0.sql',
+)
+
+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/sql/test_resowner.sql b/src/test/modules/test_resowner/sql/test_resowner.sql
new file mode 100644
index 00000000000..23284b7c8b9
--- /dev/null
+++ b/src/test/modules/test_resowner/sql/test_resowner.sql
@@ -0,0 +1,25 @@
+CREATE EXTENSION test_resowner;
+
+-- This is small enough that everything fits in the small array
+SELECT test_resowner_priorities(2, 3);
+
+-- Same test with more resources, to exercise the hash table
+SELECT test_resowner_priorities(2, 32);
+
+-- 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
+);
+
+-- Test resource leak warning
+SELECT test_resowner_leak();
+
+-- 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();
+SELECT test_resowner_forget_between_phases();
+reset client_min_messages;
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..7a4cc7d8ae2
--- /dev/null
+++ b/src/test/modules/test_resowner/test_resowner_basic.c
@@ -0,0 +1,212 @@
+/*--------------------------------------------------------------------------
+ *
+ * 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);
+
+/*
+ * A resource that tracks strings and prints the string when it's released.
+ * This makes the order that the resources are released visible.
+ */
+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");
+
+	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;
+	}
+
+	/* Add a bunch of resources to child, with different priorities */
+	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);
+	}
+
+	/* And also to the parent */
+	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..d8ee455df92
--- /dev/null
+++ b/src/test/modules/test_resowner/test_resowner_many.c
@@ -0,0 +1,290 @@
+/*--------------------------------------------------------------------------
+ *
+ * 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;
+	int			ntotal;
+
+	ntotal = GetTotalResourceCount(kinds, nkinds);
+	if (ntotal < nresources)
+		elog(PANIC, "cannot free %d resources, only %d remembered", nresources, ntotal);
+
+	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();
+}
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 260854747b4..dfba5f5bc05 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2354,8 +2354,10 @@ ReplicationStateOnDisk
 ResTarget
 ReservoirState
 ReservoirStateData
-ResourceArray
+ResourceElem
 ResourceOwner
+ResourceOwnerData
+ResourceOwnerFuncs
 ResourceReleaseCallback
 ResourceReleaseCallbackItem
 ResourceReleasePhase
-- 
2.30.2

v15-0003-Use-a-faster-hash-function-in-resource-owners.patchtext/x-patch; charset=UTF-8; name=v15-0003-Use-a-faster-hash-function-in-resource-owners.patchDownload
From ba310d3e4aa6b52112f37692a03853de6d0719c7 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Mon, 31 Oct 2022 10:49:06 +0100
Subject: [PATCH v15 3/4] 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 c385c996e19..b4e96ca9d63 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

v15-0004-Change-pgcrypto-to-use-the-new-ResourceOwner-mec.patchtext/x-patch; charset=UTF-8; name=v15-0004-Change-pgcrypto-to-use-the-new-ResourceOwner-mec.patchDownload
From 4e0b3503171e809f23f6c1fdb768f441154b3f5a Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Sat, 11 Dec 2021 00:37:47 +0200
Subject: [PATCH v15 4/4] 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

#58Aleksander Alekseev
aleksander@timescale.com
In reply to: Heikki Linnakangas (#57)
Re: ResourceOwner refactoring

Hi,

Thanks, here's a fixed version. (ResourceOwner resource release
callbacks mustn't call ResourceOwnerForget anymore, but AbortBufferIO
was still doing that)

I believe v15 is ready to be merged. I suggest we merge it early in
the PG17 release cycle.

--
Best regards,
Aleksander Alekseev

#59Peter Eisentraut
peter@eisentraut.org
In reply to: Heikki Linnakangas (#57)
Re: ResourceOwner refactoring

A few suggestions on the API:

+static ResourceOwnerFuncs tupdesc_resowner_funcs =

These aren't all "functions", so maybe another word like "info" or
"description" would be more appropriate?

+   .release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+   .release_priority = RELEASE_PRIO_TUPDESC_REFS,

I suggest assigning explicit non-zero numbers to the RESOURCE_RELEASE_*
constants, as well as changing RELEASE_PRIO_FIRST to 1 and adding an
assertion that the priority is not zero. Otherwise, someone might
forget to assign these fields and would implicitly get phase 0 and
priority 0, which would probably work in most cases, but wouldn't be the
explicit intention.

Then again, you are using RELEASE_PRIO_FIRST for the pgcrypto patch. Is
it the recommendation that if there are no other requirements, external
users should use that? If we are going to open this up to external
users, we should probably have some documented guidance around this.

+ .PrintLeakWarning = ResOwnerPrintTupleDescLeakWarning

I think this can be refactored further. We really just need a function
to describe a resource, like maybe

static char *
ResOwnerDescribeTupleDesc(Datum res)
{
TupleDesc tupdesc = (TupleDesc) DatumGetPointer(res);

return psprintf("TupleDesc %p (%u,%d)",
tupdesc, tupdesc->tdtypeid, tupdesc->tdtypmod);
}

and then the common code can generate the warnings from that like

elog(WARNING,
"%s leak: %s still referenced",
kind->name, kind->describe(value));

That way, we could more easily develop further options, such as turning
this warning into an error (useful for TAP tests).

Also, maybe, you could supply a default description that just prints
"%p", which appears to be applicable in about half the places.

Possible bonus project: I'm not sure these descriptions are so useful
anyway. It doesn't help me much in debugging if "File 55 was not
released" or some such. If would be cool if ResourceOwnerRemember() in
some debug mode could remember the source code location, and then print
that together with the leak warning.

+#define ResourceOwnerRememberTupleDesc(owner, tupdesc) \
+   ResourceOwnerRemember(owner, PointerGetDatum(tupdesc), 

&tupdesc_resowner_funcs)

+#define ResourceOwnerForgetTupleDesc(owner, tupdesc) \
+   ResourceOwnerForget(owner, PointerGetDatum(tupdesc), 

&tupdesc_resowner_funcs)

I would prefer that these kinds of things be made inline functions, so
that we maintain the type safety of the previous interface. And it's
also easier when reading code to see type decorations.

(But I suspect the above bonus project would require these to be macros?)

+   if (context->resowner)
+       ResourceOwnerForgetJIT(context->resowner, context);

It seems most ResourceOwnerForget*() calls have this kind of check
before them. Could this be moved inside the function?

#60Andres Freund
andres@anarazel.de
In reply to: Heikki Linnakangas (#57)
Re: ResourceOwner refactoring

Hi,

On 2023-05-25 20:14:23 +0300, Heikki Linnakangas wrote:

@@ -2491,9 +2495,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
@@ -2970,9 +2971,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;
@@ -3054,8 +3052,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)

@@ -3065,7 +3061,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.
@@ -4110,9 +4108,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;
@@ -4126,7 +4121,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) &&
@@ -4183,9 +4180,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;

Hm, a tad worried that increasing the number of ResourceOwnerEnlargeBuffers()
that drastically could have a visible overhead.

+static ResourceOwnerFuncs tupdesc_resowner_funcs =
+{
+	.name = "tupdesc reference",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_TUPDESC_REFS,
+	.ReleaseResource = ResOwnerReleaseTupleDesc,
+	.PrintLeakWarning = ResOwnerPrintTupleDescLeakWarning
+};

I think these should all be marked const, there's no need to have them be in a
mutable section of the binary. Of course that will require adjusting a bunch
of the signatures too, but that seems fine.

--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -5171,9 +5171,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);

I might be a bit slow this morning, but why did this need to change as part of
this?

@@ -5182,8 +5221,8 @@ TerminateBufferIO(BufferDesc *buf, bool clear_dirty, uint32 set_flag_bits)
*	If I/O was in progress, we always set BM_IO_ERROR, even though it's
*	possible the error condition wasn't related to the I/O.
*/
-void
-AbortBufferIO(Buffer buffer)
+static void
+AbortBufferIO(Buffer buffer, bool forget_owner)

What is the forget_owner argument for? It's always false, afaics?

+/* 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)

Not specific to this type of resource owner, but I wonder if it'd not be
better to use inline functions for these - the PointerGetDatum() cast removes
nearly all type safety.

+Releasing
+---------
+
+Releasing the resources of a ResourceOwner happens in three phases:
+
+1. "Before-locks" resources
+
+2. Locks
+
+3. "After-locks" resources

Given that we now have priorites, I wonder if we shouldn't merge the phase and
the priorities? We have code like this:

ResourceOwnerRelease(TopTransactionResourceOwner,
RESOURCE_RELEASE_BEFORE_LOCKS,
true, true);
...
ResourceOwnerRelease(TopTransactionResourceOwner,
RESOURCE_RELEASE_LOCKS,
true, true);
ResourceOwnerRelease(TopTransactionResourceOwner,
RESOURCE_RELEASE_AFTER_LOCKS,
true, true);

Now, admittedly locks currently are "special" anyway, but we have a plan to
get rid of that. If we did, then we could do the last two as one as
ResourceOwnerRelease(from = RELEASE_PRIO_LOCKS, to: RELEASE_PRIO_LAST, ...)
or such.

+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.

I am not sure it's a good idea to encode that all kinds of resources ought to
have been released prior on commit. I can see that not always making sense -
in fact we already don't do it for locks, no? Perhaps just add a "commonly"?

+typedef struct ResourceElem
{
...
+	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 fixed-size array to hold most-recently remembered resources.
*/
-#define RESARRAY_INIT_SIZE 16
+#define RESOWNER_ARRAY_SIZE 32

That's 512 bytes, pretty large to be searched sequentially. It's somewhat sad
that the array needs to include 8 byte of ResourceOwnerFuncs...

+	bool		releasing;		/* has ResourceOwnerRelease been called? */
+
+	/*
+	 * The fixed-size array for recent resources.
+	 *
+	 * If 'releasing' is set, the contents are sorted by release priority.
+	 */
+	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 */
+
+	/* The local locks cache. */
+	uint32		nlocks;			/* number of owned locks */
LOCALLOCK  *locks[MAX_RESOWNER_LOCKS];	/* list of owned locks */

Due to 'narr' being before the large 'arr', 'nhash' is always on a separate
cacheline. I.e. the number of cachelines accessed in happy paths is higher
than necessary, also relevant because there's more dependencies on narr, nhash
than on the contents of those.

I'd probably order it so that both of the large elements (arr, locks) are at
the end.

It's hard to know with changes this small, but it does seem to yield a small
and repeatable performance benefit (pipelined pgbench -S).

-}			ResourceOwnerData;
+} ResourceOwnerData;

That one pgindent will likely undo again ;)

+/*
+ * 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.
+ */
+void
+ResourceOwnerForget(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind)
+{
+#ifdef RESOWNER_TRACE
+	elog(LOG, "FORGET %d: owner %p value " UINT64_FORMAT ", kind: %s",
+		 resowner_trace_counter++, owner, DatumGetUInt64(value), kind->name);
+#endif
+
+	/*
+	 * 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++)
+	{

Hm, I think we oftend up releasing resources in a lifo order. Would it
possibly make sense to search in reverse order?

Separately, I wonder if it'd be worth to check if there are any other matches
when assertions are enabled.

ResourceOwnerRelease(ResourceOwner owner,
@@ -492,6 +631,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
}

Why do we have ResourceOwnerRelease() vs ResourceOwnerReleaseInternal()? Looks
like that's older than your patch, but still confused.

+	/* Let add-on modules get a chance too */
+	for (item = ResourceRelease_callbacks; item; item = next)
+	{
+		/* allow callbacks to unregister themselves when called */
+		next = item->next;
+		item->callback(phase, isCommit, isTopLevel, item->arg);
+	}

I wonder if it's really a good idea to continue having this API. What you are
allowed to do in a resowner changed

+/*
+ * ResourceOwnerReleaseAllOfKind
+ *		Release all resources of a certain type held by this owner.
+ */
+void
+ResourceOwnerReleaseAllOfKind(ResourceOwner owner, ResourceOwnerFuncs *kind)
+{
+	/* 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);
-		/* Ditto for plancache references */
-		while (ResourceArrayGetAny(&(owner->planrefarr), &foundres))
+	/*
+	 * 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;

Is it a problem than a possible error would lead to ->releasing = true to be
"leaked"? I think it might be, because we haven't called ResourceOwnerSort(),
which means we'd not process resources in the right order during abort
processing.

+/*
+ * 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
}

Why are we using a 64bit hash to then just throw out the high bits?

Greetings,

Andres Freund

#61Andres Freund
andres@anarazel.de
In reply to: Andres Freund (#60)
Re: ResourceOwner refactoring

Hi,

On 2023-07-10 12:14:46 -0700, Andres Freund wrote:

/*
- * Initially allocated size of a ResourceArray.  Must be power of two since
- * we'll use (arraysize - 1) as mask for hashing.
+ * Size of the fixed-size array to hold most-recently remembered resources.
*/
-#define RESARRAY_INIT_SIZE 16
+#define RESOWNER_ARRAY_SIZE 32

That's 512 bytes, pretty large to be searched sequentially. It's somewhat sad
that the array needs to include 8 byte of ResourceOwnerFuncs...

On that note:

It's perhaps worth noting that this change actually *increases* the size of
ResourceOwnerData. Previously:

/* size: 576, cachelines: 9, members: 19 */
/* sum members: 572, holes: 1, sum holes: 4 */

now:
/* size: 696, cachelines: 11, members: 13 */
/* sum members: 693, holes: 1, sum holes: 3 */

It won't make a difference memory-usage wise, given aset.c's rounding
behaviour. But it does mean that more memory needs to be zeroed, as
ResourceOwnerCreate() uses MemoryContextAllocZero.

As there's really no need to initialize the long ->arr, ->locks, it seems
worth to just initialize explicitly instead of using MemoryContextAllocZero().

With the new representation, is there any point in having ->locks? We still
need ->nlocks to be able to switch to the lossy behaviour, but there doesn't
seem to be much point in having the separate array.

Greetings,

Andres Freund

#62Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Peter Eisentraut (#59)
Re: ResourceOwner refactoring

On 10/07/2023 15:37, Peter Eisentraut wrote:

A few suggestions on the API:

+static ResourceOwnerFuncs tupdesc_resowner_funcs =

These aren't all "functions", so maybe another word like "info" or
"description" would be more appropriate?

+   .release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+   .release_priority = RELEASE_PRIO_TUPDESC_REFS,

I suggest assigning explicit non-zero numbers to the RESOURCE_RELEASE_*
constants, as well as changing RELEASE_PRIO_FIRST to 1 and adding an
assertion that the priority is not zero. Otherwise, someone might
forget to assign these fields and would implicitly get phase 0 and
priority 0, which would probably work in most cases, but wouldn't be the
explicit intention.

Done.

Then again, you are using RELEASE_PRIO_FIRST for the pgcrypto patch. Is
it the recommendation that if there are no other requirements, external
users should use that? If we are going to open this up to external
users, we should probably have some documented guidance around this.

I added a brief comment about that in the ResourceReleasePhase typedef.

I also added a section to the README on how to define your own resource
kind. (The README doesn't go into any detail on how to choose the
release priority though).

+ .PrintLeakWarning = ResOwnerPrintTupleDescLeakWarning

I think this can be refactored further. We really just need a function
to describe a resource, like maybe

static char *
ResOwnerDescribeTupleDesc(Datum res)
{
TupleDesc tupdesc = (TupleDesc) DatumGetPointer(res);

return psprintf("TupleDesc %p (%u,%d)",
tupdesc, tupdesc->tdtypeid, tupdesc->tdtypmod);
}

and then the common code can generate the warnings from that like

elog(WARNING,
"%s leak: %s still referenced",
kind->name, kind->describe(value));

That way, we could more easily develop further options, such as turning
this warning into an error (useful for TAP tests).

Also, maybe, you could supply a default description that just prints
"%p", which appears to be applicable in about half the places.

Refactored it that way.

Possible bonus project: I'm not sure these descriptions are so useful
anyway. It doesn't help me much in debugging if "File 55 was not
released" or some such. If would be cool if ResourceOwnerRemember() in
some debug mode could remember the source code location, and then print
that together with the leak warning.

Yeah, that would be useful. I remember I've hacked something like that
as a one-off thing in the past, when debugging a leak.

+#define ResourceOwnerRememberTupleDesc(owner, tupdesc) \
+   ResourceOwnerRemember(owner, PointerGetDatum(tupdesc),

&tupdesc_resowner_funcs)

+#define ResourceOwnerForgetTupleDesc(owner, tupdesc) \
+   ResourceOwnerForget(owner, PointerGetDatum(tupdesc),

&tupdesc_resowner_funcs)

I would prefer that these kinds of things be made inline functions, so
that we maintain the type safety of the previous interface. And it's
also easier when reading code to see type decorations.

(But I suspect the above bonus project would require these to be macros?)

+   if (context->resowner)
+       ResourceOwnerForgetJIT(context->resowner, context);

It seems most ResourceOwnerForget*() calls have this kind of check
before them. Could this be moved inside the function?

Many do, but I don't know if it's the majority. We could make
ResurceOwnerForget(NULL) do nothing, but I think it's better to have the
check in the callers. You wouldn't want to silently do nothing when the
resource owner is not expected to be NULL.

(I'm attaching new patch version in my reply to Andres shortly)

--
Heikki Linnakangas
Neon (https://neon.tech)

#63Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Andres Freund (#60)
4 attachment(s)
Re: ResourceOwner refactoring

On 10/07/2023 22:14, Andres Freund wrote:

On 2023-05-25 20:14:23 +0300, Heikki Linnakangas wrote:

@@ -4183,9 +4180,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;

Hm, a tad worried that increasing the number of ResourceOwnerEnlargeBuffers()
that drastically could have a visible overhead.

You mean in FlushRelationsAllBuffers() in particular? Seems pretty cheap
compared to all the other work involved, and it's not that performance
critical anyway.

ExtendBufferedRelShared() gave me a pause, as it is in the critical
path. But there too, the ResourceOwnerEnlarge() call pales in comparison
to all the other work that it does for each buffer.

Other than that, I don't think I'm introducing new
ResourceOwnerEnlarge() calls to hot codepaths, just moving them around.

+static ResourceOwnerFuncs tupdesc_resowner_funcs =
+{
+	.name = "tupdesc reference",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_TUPDESC_REFS,
+	.ReleaseResource = ResOwnerReleaseTupleDesc,
+	.PrintLeakWarning = ResOwnerPrintTupleDescLeakWarning
+};

I think these should all be marked const, there's no need to have them be in a
mutable section of the binary. Of course that will require adjusting a bunch
of the signatures too, but that seems fine.

Done.

--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -5171,9 +5171,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);

I might be a bit slow this morning, but why did this need to change as part of
this?

I tried to explain it in the comment. AtEOSubXact_Inval() sometimes
needs to bump the refcount on a relcache entry, which is tracked in the
current resource owner. But we have already started to release it. On
master, you can allocate new resources in a ResourceOwner after you have
already called ResourceOwnerRelease(RESOURCE_RELEASE_BEFORE_LOCKS), but
with this patch, that's no longer allowed and you get an assertion
failure. I think it was always a bit questionable; it's not clear that
the resource would've been released correctly if an error occurred. In
practice, I don't think an error could actually occur while
AtEOXSubXact_Inval() briefly holds the relcache entry.

@@ -5182,8 +5221,8 @@ TerminateBufferIO(BufferDesc *buf, bool clear_dirty, uint32 set_flag_bits)
*	If I/O was in progress, we always set BM_IO_ERROR, even though it's
*	possible the error condition wasn't related to the I/O.
*/
-void
-AbortBufferIO(Buffer buffer)
+static void
+AbortBufferIO(Buffer buffer, bool forget_owner)

What is the forget_owner argument for? It's always false, afaics?

Removed. There used to be more callers of AbortBufferIO() which needed
that, but not anymore.

+/* 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)

Not specific to this type of resource owner, but I wonder if it'd not be
better to use inline functions for these - the PointerGetDatum() cast removes
nearly all type safety.

Peter just said the same thing. I guess you're right, changed.

+Releasing
+---------
+
+Releasing the resources of a ResourceOwner happens in three phases:
+
+1. "Before-locks" resources
+
+2. Locks
+
+3. "After-locks" resources

Given that we now have priorities, I wonder if we shouldn't merge the phase and
the priorities? We have code like this:

ResourceOwnerRelease(TopTransactionResourceOwner,
RESOURCE_RELEASE_BEFORE_LOCKS,
true, true);
...
ResourceOwnerRelease(TopTransactionResourceOwner,
RESOURCE_RELEASE_LOCKS,
true, true);
ResourceOwnerRelease(TopTransactionResourceOwner,
RESOURCE_RELEASE_AFTER_LOCKS,
true, true);

Now, admittedly locks currently are "special" anyway, but we have a plan to
get rid of that. If we did, then we could do the last two as one as
ResourceOwnerRelease(from = RELEASE_PRIO_LOCKS, to: RELEASE_PRIO_LAST, ...)
or such.

Currently, we always release parent's resources before a child's, within
each phase. I'm not sure if we rely on that parent-child ordering
anywhere though. I'd like to leave it as it is for now, to limit the
scope of this, and maybe revisit later.

+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.

I am not sure it's a good idea to encode that all kinds of resources ought to
have been released prior on commit. I can see that not always making sense -
in fact we already don't do it for locks, no? Perhaps just add a "commonly"?

Hmm, I'd still like to print the warnings for resources that we expect
to be released before commit, though. We could have a flag in the
ResourceOwnerDesc to give flexibility, but I'm inclined to wait until we
get the first case of someone actually needing it.

+typedef struct ResourceElem
{
...
+	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 fixed-size array to hold most-recently remembered resources.
*/
-#define RESARRAY_INIT_SIZE 16
+#define RESOWNER_ARRAY_SIZE 32

That's 512 bytes, pretty large to be searched sequentially. It's somewhat sad
that the array needs to include 8 byte of ResourceOwnerFuncs...

Yeah. Early on I considered having a RegisterResourceKind() function
that you need to call first, and then each resource kind could be
assigned a smaller ID. If we wanted to keep the old mechanism of
separate arrays for each different resource kind, that'd be the way to
go. But overall this seems simpler, and the performance is decent
despite that linear scan.

Note that the current code in master also uses a plain array, until it
grows to 512 bytes, when it switches to a hash table. Lot of the details
with the array/hash combo are different, but that threshold was the same.

+	bool		releasing;		/* has ResourceOwnerRelease been called? */
+
+	/*
+	 * The fixed-size array for recent resources.
+	 *
+	 * If 'releasing' is set, the contents are sorted by release priority.
+	 */
+	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 */
+
+	/* The local locks cache. */
+	uint32		nlocks;			/* number of owned locks */
LOCALLOCK  *locks[MAX_RESOWNER_LOCKS];	/* list of owned locks */

Due to 'narr' being before the large 'arr', 'nhash' is always on a separate
cacheline. I.e. the number of cachelines accessed in happy paths is higher
than necessary, also relevant because there's more dependencies on narr, nhash
than on the contents of those.

I'd probably order it so that both of the large elements (arr, locks) are at
the end.

It's hard to know with changes this small, but it does seem to yield a small
and repeatable performance benefit (pipelined pgbench -S).

Swapped the 'hash' and 'arr' parts of the struct, so that 'arr' and
'locks' are at the end.

+/*
+ * 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.
+ */
+void
+ResourceOwnerForget(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind)
+{
+#ifdef RESOWNER_TRACE
+	elog(LOG, "FORGET %d: owner %p value " UINT64_FORMAT ", kind: %s",
+		 resowner_trace_counter++, owner, DatumGetUInt64(value), kind->name);
+#endif
+
+	/*
+	 * 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++)
+	{

Hm, I think we oftend up releasing resources in a lifo order. Would it
possibly make sense to search in reverse order?

Changed. I can see a small speedup in a micro benchmark, when the array
is almost full and you repeatedly remember / forget one more resource.

Separately, I wonder if it'd be worth to check if there are any other matches
when assertions are enabled.

It is actually allowed to remember the same resource twice. There's a
comment in ResourceOwnerForget (previously in ResourceArrayRemove) that
each call releases one instance in that case. I'm not sure if we rely on
that anywhere currently.

ResourceOwnerRelease(ResourceOwner owner,
@@ -492,6 +631,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
}

Why do we have ResourceOwnerRelease() vs ResourceOwnerReleaseInternal()? Looks
like that's older than your patch, but still confused.

Dunno. It provides a place to do things on the top-level resource owner
that you don't want to do when you recurse to the children, as hinted by
the comment, but I don't know if we've ever had such things. It was
handy for printing the RESOWNER_STATS now, though :-).

+	/* Let add-on modules get a chance too */
+	for (item = ResourceRelease_callbacks; item; item = next)
+	{
+		/* allow callbacks to unregister themselves when called */
+		next = item->next;
+		item->callback(phase, isCommit, isTopLevel, item->arg);
+	}

I wonder if it's really a good idea to continue having this API. What you are
allowed to do in a resowner changed

Hmm, I don't think anything changed for users of this API. I'm not in a
hurry to remove this and force extensions to adapt, as it's not hard to
maintain this.

+/*
+ * ResourceOwnerReleaseAllOfKind
+ *		Release all resources of a certain type held by this owner.
+ */
+void
+ResourceOwnerReleaseAllOfKind(ResourceOwner owner, ResourceOwnerFuncs *kind)
+{
+	/* 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);
-		/* Ditto for plancache references */
-		while (ResourceArrayGetAny(&(owner->planrefarr), &foundres))
+	/*
+	 * 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;

Is it a problem than a possible error would lead to ->releasing = true to be
"leaked"? I think it might be, because we haven't called ResourceOwnerSort(),
which means we'd not process resources in the right order during abort
processing.

Good point. I added a separate 'sorted' flag to handle that gracefully.

+/*
+ * 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
}

Why are we using a 64bit hash to then just throw out the high bits?

It was just expedient when the inputs are 64-bit. Implementation of
murmurhash64 is tiny, and we already use murmurhash32 elsewhere, so it
seemed like an OK choice. I'm sure there are better algorithms out
there, though.

--
Heikki Linnakangas
Neon (https://neon.tech)

Attachments:

v16-0001-Move-a-few-ResourceOwnerEnlarge-calls-for-safety.patchtext/x-patch; charset=UTF-8; name=v16-0001-Move-a-few-ResourceOwnerEnlarge-calls-for-safety.patchDownload
From d4fd154738bb9a4aac118cd2f51ef06b032a6cae Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Mon, 31 Oct 2022 10:33:36 +0100
Subject: [PATCH v16 1/4] 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
Reviewed-by: Kyotaro Horiguchi, Hayato Kuroda, Álvaro Herrera, Zhihong Yu
Reviewed-by: Peter Eisentraut, Andres Freund
Discussion: https://www.postgresql.org/message-id/cbfabeb0-cd3c-e951-a572-19b365ed314d%40iki.fi
---
 src/backend/storage/buffer/bufmgr.c   | 49 ++++++++++++---------------
 src/backend/storage/buffer/localbuf.c |  2 ++
 src/backend/utils/cache/catcache.c    |  5 +--
 3 files changed, 27 insertions(+), 29 deletions(-)

diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 21a29f9081a..20ce0bbac6a 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -1023,9 +1023,6 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 								 forkNum, strategy, flags);
 	}
 
-	/* Make sure we will have room to remember the buffer pin */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
-
 	TRACE_POSTGRESQL_BUFFER_READ_START(forkNum, blockNum,
 									   smgr->smgr_rlocator.locator.spcOid,
 									   smgr->smgr_rlocator.locator.dbOid,
@@ -1230,6 +1227,10 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 	BufferDesc *victim_buf_hdr;
 	uint32		victim_buf_state;
 
+	/* Make sure we will have room to remember the buffer pin */
+	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ReservePrivateRefCountEntry();
+
 	/* create a tag so we can lookup the buffer */
 	InitBufferTag(&newTag, &smgr->smgr_rlocator.locator, forkNum, blockNum);
 
@@ -1591,7 +1592,7 @@ GetVictimBuffer(BufferAccessStrategy strategy, IOContext io_context)
 
 	/*
 	 * Ensure, while the spinlock's not yet held, that there's a free refcount
-	 * entry.
+	 * entry, and a resource owner slot for the pin.
 	 */
 	ReservePrivateRefCountEntry();
 	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
@@ -1859,9 +1860,6 @@ ExtendBufferedRelShared(BufferManagerRelation bmr,
 		MemSet((char *) buf_block, 0, BLCKSZ);
 	}
 
-	/* in case we need to pin an existing buffer below */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
-
 	/*
 	 * Lock relation against concurrent extensions, unless requested not to.
 	 *
@@ -1947,6 +1945,10 @@ ExtendBufferedRelShared(BufferManagerRelation bmr,
 		LWLock	   *partition_lock;
 		int			existing_id;
 
+		/* in case we need to pin an existing buffer below */
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ReservePrivateRefCountEntry();
+
 		InitBufferTag(&tag, &bmr.smgr->smgr_rlocator.locator, fork, first_block + i);
 		hash = BufTableHashCode(&tag);
 		partition_lock = BufMappingPartitionLock(hash);
@@ -2281,7 +2283,8 @@ 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.
+ * Note that ResourceOwnerEnlargeBuffers and ReservePrivateRefCountEntry()
+ * 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.
@@ -2294,6 +2297,7 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
 	PrivateRefCountEntry *ref;
 
 	Assert(!BufferIsLocal(b));
+	Assert(ReservedRefCountEntry != NULL);
 
 	ref = GetPrivateRefCountEntry(b, true);
 
@@ -2302,7 +2306,6 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
 		uint32		buf_state;
 		uint32		old_buf_state;
 
-		ReservePrivateRefCountEntry();
 		ref = NewPrivateRefCountEntry(b);
 
 		old_buf_state = pg_atomic_read_u32(&buf->state);
@@ -2375,7 +2378,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.
@@ -2550,9 +2554,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
@@ -3029,9 +3030,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;
@@ -3113,8 +3111,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)
@@ -3124,7 +3120,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.
@@ -4169,9 +4167,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;
@@ -4185,7 +4180,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) &&
@@ -4242,9 +4239,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;
@@ -4283,7 +4277,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) &&
@@ -4478,9 +4474,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;
@@ -4494,7 +4487,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 9f20dca1212..b8715b54651 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -130,6 +130,8 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
 	if (LocalBufHash == NULL)
 		InitLocalBuffers();
 
+	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 1aacb736c22..18db7e78e21 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1605,8 +1605,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();
@@ -1694,6 +1692,9 @@ 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);
-- 
2.39.2

v16-0002-Make-resowners-more-easily-extensible.patchtext/x-patch; charset=UTF-8; name=v16-0002-Make-resowners-more-easily-extensible.patchDownload
From 34aff4bec48dd4afd8c5a8f17792597f6212ecef Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Mon, 31 Oct 2022 10:48:57 +0100
Subject: [PATCH v16 2/4] 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.

Each resource kind specifies a release priority, and
ResourceOwnerReleaseAll releases the resources in priority 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.

There were two exceptions to how the resource leak WARNINGs on commit
were printed previously: llvmjit silently released the context without
printing the warning, and a leaked buffer io triggered a PANIC. Now
everything prints a WARNING, including those cases.

Add tests in src/test/modules/test_resowner.

Reviewed-by: Aleksander Alekseev, Michael Paquier, Julien Rouhaud
Reviewed-by: Kyotaro Horiguchi, Hayato Kuroda, Álvaro Herrera, Zhihong Yu
Reviewed-by: Peter Eisentraut, Andres Freund
Discussion: https://www.postgresql.org/message-id/cbfabeb0-cd3c-e951-a572-19b365ed314d%40iki.fi
---
 src/backend/access/common/tupdesc.c           |   51 +-
 src/backend/access/transam/xact.c             |   14 +
 src/backend/jit/jit.c                         |    2 -
 src/backend/jit/llvm/llvmjit.c                |   45 +-
 src/backend/storage/buffer/bufmgr.c           |  164 +-
 src/backend/storage/buffer/localbuf.c         |   21 +-
 src/backend/storage/file/fd.c                 |   57 +-
 src/backend/storage/ipc/dsm.c                 |   53 +-
 src/backend/storage/lmgr/lock.c               |    2 +-
 src/backend/utils/cache/catcache.c            |  125 +-
 src/backend/utils/cache/plancache.c           |   50 +-
 src/backend/utils/cache/relcache.c            |   64 +-
 src/backend/utils/resowner/README             |  114 +-
 src/backend/utils/resowner/resowner.c         | 1563 ++++++-----------
 src/backend/utils/time/snapmgr.c              |   47 +-
 src/common/cryptohash_openssl.c               |   49 +-
 src/common/hmac_openssl.c                     |   47 +-
 src/include/storage/buf_internals.h           |   28 +
 src/include/storage/bufmgr.h                  |    4 +-
 src/include/utils/catcache.h                  |    3 -
 src/include/utils/plancache.h                 |    2 +
 src/include/utils/resowner.h                  |   94 +-
 src/pl/plpgsql/src/pl_exec.c                  |    2 +-
 src/pl/plpgsql/src/pl_handler.c               |    6 +-
 src/test/modules/Makefile                     |    1 +
 src/test/modules/meson.build                  |    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    |   34 +
 .../test_resowner/sql/test_resowner.sql       |   25 +
 .../test_resowner/test_resowner--1.0.sql      |   30 +
 .../test_resowner/test_resowner.control       |    4 +
 .../test_resowner/test_resowner_basic.c       |  212 +++
 .../test_resowner/test_resowner_many.c        |  296 ++++
 src/tools/pgindent/typedefs.list              |    6 +-
 36 files changed, 2298 insertions(+), 1143 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/sql/test_resowner.sql
 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 d119cfafb56..8826519e5e9 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -27,9 +27,34 @@
 #include "common/hashfn.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 char *ResOwnerPrintTupleDesc(Datum res);
+
+static const ResourceOwnerDesc tupdesc_resowner_desc =
+{
+	.name = "tupdesc reference",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_TUPDESC_REFS,
+	.ReleaseResource = ResOwnerReleaseTupleDesc,
+	.DebugPrint = ResOwnerPrintTupleDesc
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(tupdesc), &tupdesc_resowner_desc);
+}
+
+static inline void
+ResourceOwnerForgetTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(tupdesc), &tupdesc_resowner_desc);
+}
 
 /*
  * CreateTemplateTupleDesc
@@ -364,7 +389,7 @@ IncrTupleDescRefCount(TupleDesc tupdesc)
 {
 	Assert(tupdesc->tdrefcount >= 0);
 
-	ResourceOwnerEnlargeTupleDescs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	tupdesc->tdrefcount++;
 	ResourceOwnerRememberTupleDesc(CurrentResourceOwner, tupdesc);
 }
@@ -847,3 +872,25 @@ TupleDescGetDefault(TupleDesc tupdesc, AttrNumber attnum)
 
 	return result;
 }
+
+/* ResourceOwner callbacks */
+
+static void
+ResOwnerReleaseTupleDesc(Datum res)
+{
+	TupleDesc	tupdesc = (TupleDesc) DatumGetPointer(res);
+
+	/* Like DecrTupleDescRefCount, but don't call ResourceOwnerForget() */
+	Assert(tupdesc->tdrefcount > 0);
+	if (--tupdesc->tdrefcount == 0)
+		FreeTupleDesc(tupdesc);
+}
+
+static char *
+ResOwnerPrintTupleDesc(Datum res)
+{
+	TupleDesc	tupdesc = (TupleDesc) DatumGetPointer(res);
+
+	return psprintf("TupleDesc %p (%u,%d)",
+					tupdesc, tupdesc->tdtypeid, tupdesc->tdtypmod);
+}
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 37c5e34ccea..2beace193f6 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -5172,9 +5172,23 @@ 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/jit.c b/src/backend/jit/jit.c
index 4da8fee20b4..07461b99630 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 58f638859a4..a9e3ffe989f 100644
--- a/src/backend/jit/llvm/llvmjit.c
+++ b/src/backend/jit/llvm/llvmjit.c
@@ -45,7 +45,7 @@
 #include "portability/instr_time.h"
 #include "storage/ipc.h"
 #include "utils/memutils.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 
 #define LLVMJIT_LLVM_CONTEXT_REUSE_MAX 100
 
@@ -131,6 +131,30 @@ 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 const ResourceOwnerDesc jit_resowner_desc =
+{
+	.name = "LLVM JIT context",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_JIT_CONTEXTS,
+	.ReleaseResource = ResOwnerReleaseJitContext,
+	.DebugPrint = NULL			/* the default message is fine */
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberJIT(ResourceOwner owner, LLVMJitContext *handle)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(handle), &jit_resowner_desc);
+}
+static inline void
+ResourceOwnerForgetJIT(ResourceOwner owner, LLVMJitContext *handle)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(handle), &jit_resowner_desc);
+}
+
 PG_MODULE_MAGIC;
 
 
@@ -220,7 +244,7 @@ llvm_create_context(int jitFlags)
 
 	llvm_recreate_llvm_context();
 
-	ResourceOwnerEnlargeJIT(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	context = MemoryContextAllocZero(TopMemoryContext,
 									 sizeof(LLVMJitContext));
@@ -228,7 +252,7 @@ llvm_create_context(int jitFlags)
 
 	/* ensure cleanup */
 	context->base.resowner = CurrentResourceOwner;
-	ResourceOwnerRememberJIT(CurrentResourceOwner, PointerGetDatum(context));
+	ResourceOwnerRememberJIT(CurrentResourceOwner, context);
 
 	llvm_jit_context_in_use_count++;
 
@@ -300,6 +324,9 @@ llvm_release_context(JitContext *context)
 	llvm_jit_context->handles = NIL;
 
 	llvm_leave_fatal_on_oom();
+
+	if (context->resowner)
+		ResourceOwnerForgetJIT(context->resowner, llvm_context);
 }
 
 /*
@@ -1394,3 +1421,15 @@ llvm_error_message(LLVMErrorRef error)
 }
 
 #endif							/* LLVM_VERSION_MAJOR > 11 */
+
+/*
+ * ResourceOwner callbacks
+ */
+static void
+ResOwnerReleaseJitContext(Datum res)
+{
+	JitContext *context = (JitContext *) DatumGetPointer(res);
+
+	context->resowner = NULL;
+	jit_release_context(context);
+}
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 20ce0bbac6a..3834b70d491 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -47,6 +47,7 @@
 #include "postmaster/bgwriter.h"
 #include "storage/buf_internals.h"
 #include "storage/bufmgr.h"
+#include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/proc.h"
@@ -55,7 +56,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"
 
 
@@ -205,6 +206,30 @@ static PrivateRefCountEntry *GetPrivateRefCountEntry(Buffer buffer, bool do_move
 static inline int32 GetPrivateRefCount(Buffer buffer);
 static void ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref);
 
+/* ResourceOwner callbacks to hold in-progress I/Os and buffer pins */
+static void ResOwnerReleaseBufferIO(Datum res);
+static char *ResOwnerPrintBufferIO(Datum res);
+static void ResOwnerReleaseBufferPin(Datum res);
+static char *ResOwnerPrintBufferPin(Datum res);
+
+const ResourceOwnerDesc buffer_io_resowner_desc =
+{
+	.name = "buffer io",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_BUFFER_IOS,
+	.ReleaseResource = ResOwnerReleaseBufferIO,
+	.DebugPrint = ResOwnerPrintBufferIO
+};
+
+const ResourceOwnerDesc buffer_pin_resowner_desc =
+{
+	.name = "buffer pin",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_BUFFER_PINS,
+	.ReleaseResource = ResOwnerReleaseBufferPin,
+	.DebugPrint = ResOwnerPrintBufferPin
+};
+
 /*
  * Ensure that the PrivateRefCountArray has sufficient space to store one more
  * entry. This has to be called before using NewPrivateRefCountEntry() to fill
@@ -470,6 +495,7 @@ static BlockNumber ExtendBufferedRelShared(BufferManagerRelation bmr,
 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,
@@ -477,7 +503,8 @@ static int	SyncOneBuffer(int buf_id, bool skip_recently_used,
 static void WaitIO(BufferDesc *buf);
 static bool StartBufferIO(BufferDesc *buf, bool forInput);
 static void TerminateBufferIO(BufferDesc *buf, bool clear_dirty,
-							  uint32 set_flag_bits);
+							  uint32 set_flag_bits, bool forget_owner);
+static void AbortBufferIO(Buffer buffer);
 static void shared_buffer_write_error_callback(void *arg);
 static void local_buffer_write_error_callback(void *arg);
 static BufferDesc *BufferAlloc(SMgrRelation smgr,
@@ -639,7 +666,7 @@ ReadRecentBuffer(RelFileLocator rlocator, ForkNumber forkNum, BlockNumber blockN
 
 	Assert(BufferIsValid(recent_buffer));
 
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	ReservePrivateRefCountEntry();
 	InitBufferTag(&tag, &rlocator, forkNum, blockNum);
 
@@ -1173,7 +1200,7 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 	else
 	{
 		/* Set BM_VALID, terminate IO, and wake up any waiters */
-		TerminateBufferIO(bufHdr, false, BM_VALID);
+		TerminateBufferIO(bufHdr, false, BM_VALID, true);
 	}
 
 	VacuumPageMiss++;
@@ -1228,7 +1255,7 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 	uint32		victim_buf_state;
 
 	/* Make sure we will have room to remember the buffer pin */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	ReservePrivateRefCountEntry();
 
 	/* create a tag so we can lookup the buffer */
@@ -1315,9 +1342,8 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 		 * use.
 		 *
 		 * We could do this after releasing the partition lock, but then we'd
-		 * have to call ResourceOwnerEnlargeBuffers() &
-		 * ReservePrivateRefCountEntry() before acquiring the lock, for the
-		 * rare case of such a collision.
+		 * have to call ResourceOwnerEnlarge() & ReservePrivateRefCountEntry()
+		 * before acquiring the lock, for the rare case of such a collision.
 		 */
 		UnpinBuffer(victim_buf_hdr);
 
@@ -1595,7 +1621,7 @@ GetVictimBuffer(BufferAccessStrategy strategy, IOContext io_context)
 	 * entry, and a resource owner slot for the pin.
 	 */
 	ReservePrivateRefCountEntry();
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	/* we return here if a prospective victim buffer gets used concurrently */
 again:
@@ -1946,7 +1972,7 @@ ExtendBufferedRelShared(BufferManagerRelation bmr,
 		int			existing_id;
 
 		/* in case we need to pin an existing buffer below */
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 		ReservePrivateRefCountEntry();
 
 		InitBufferTag(&tag, &bmr.smgr->smgr_rlocator.locator, fork, first_block + i);
@@ -2090,7 +2116,7 @@ ExtendBufferedRelShared(BufferManagerRelation bmr,
 		if (lock)
 			LWLockAcquire(BufferDescriptorGetContentLock(buf_hdr), LW_EXCLUSIVE);
 
-		TerminateBufferIO(buf_hdr, false, BM_VALID);
+		TerminateBufferIO(buf_hdr, false, BM_VALID, true);
 	}
 
 	pgBufferUsage.shared_blks_written += extend_by;
@@ -2283,7 +2309,7 @@ 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 and ReservePrivateRefCountEntry()
+ * Note that ResourceOwnerEnlarge() and ReservePrivateRefCountEntry()
  * must have been done already.
  *
  * Returns true if buffer is BM_VALID, else false.  This provision allows
@@ -2379,7 +2405,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.
@@ -2440,6 +2466,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);
@@ -2449,9 +2484,6 @@ 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)
@@ -3122,7 +3154,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.
@@ -3252,6 +3284,7 @@ CheckForBufferLeaks(void)
 	int			RefCountErrors = 0;
 	PrivateRefCountEntry *res;
 	int			i;
+	char	   *s;
 
 	/* check the array */
 	for (i = 0; i < REFCOUNT_ARRAY_ENTRIES; i++)
@@ -3260,7 +3293,10 @@ CheckForBufferLeaks(void)
 
 		if (res->buffer != InvalidBuffer)
 		{
-			PrintBufferLeakWarning(res->buffer);
+			s = DebugPrintBufferRefcount(res->buffer);
+			elog(WARNING, "buffer refcount leak: %s", s);
+			pfree(s);
+
 			RefCountErrors++;
 		}
 	}
@@ -3273,7 +3309,9 @@ CheckForBufferLeaks(void)
 		hash_seq_init(&hstat, PrivateRefCountHash);
 		while ((res = (PrivateRefCountEntry *) hash_seq_search(&hstat)) != NULL)
 		{
-			PrintBufferLeakWarning(res->buffer);
+			s = DebugPrintBufferRefcount(res->buffer);
+			elog(WARNING, "buffer refcount leak: %s", s);
+			pfree(s);
 			RefCountErrors++;
 		}
 	}
@@ -3285,12 +3323,13 @@ CheckForBufferLeaks(void)
 /*
  * Helper routine to issue warnings when a buffer is unexpectedly pinned
  */
-void
-PrintBufferLeakWarning(Buffer buffer)
+char *
+DebugPrintBufferRefcount(Buffer buffer)
 {
 	BufferDesc *buf;
 	int32		loccount;
 	char	   *path;
+	char	   *result;
 	BackendId	backend;
 	uint32		buf_state;
 
@@ -3312,13 +3351,13 @@ PrintBufferLeakWarning(Buffer buffer)
 	path = relpathbackend(BufTagGetRelFileLocator(&buf->tag), backend,
 						  BufTagGetForkNum(&buf->tag));
 	buf_state = pg_atomic_read_u32(&buf->state);
-	elog(WARNING,
-		 "buffer refcount leak: [%03d] "
-		 "(rel=%s, blockNum=%u, flags=0x%x, refcount=%u %d)",
-		 buffer, path,
-		 buf->tag.blockNum, buf_state & BUF_FLAG_MASK,
-		 BUF_STATE_GET_REFCOUNT(buf_state), loccount);
+
+	result = psprintf("[%03d] (rel=%s, blockNum=%u, flags=0x%x, refcount=%u %d)",
+					  buffer, path,
+					  buf->tag.blockNum, buf_state & BUF_FLAG_MASK,
+					  BUF_STATE_GET_REFCOUNT(buf_state), loccount);
 	pfree(path);
+	return result;
 }
 
 /*
@@ -3522,7 +3561,7 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln, IOObject io_object,
 	 * Mark the buffer as clean (unless BM_JUST_DIRTIED has become set) and
 	 * end the BM_IO_IN_PROGRESS state.
 	 */
-	TerminateBufferIO(buf, true, 0);
+	TerminateBufferIO(buf, true, 0, true);
 
 	TRACE_POSTGRESQL_BUFFER_FLUSH_DONE(BufTagGetForkNum(&buf->tag),
 									   buf->tag.blockNum,
@@ -4182,7 +4221,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) &&
@@ -4279,7 +4318,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) &&
@@ -4489,7 +4528,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 &&
@@ -4566,7 +4605,7 @@ void
 IncrBufferRefCount(Buffer buffer)
 {
 	Assert(BufferIsPinned(buffer));
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	if (BufferIsLocal(buffer))
 		LocalRefCount[-buffer - 1]++;
 	else
@@ -5164,7 +5203,7 @@ StartBufferIO(BufferDesc *buf, bool forInput)
 {
 	uint32		buf_state;
 
-	ResourceOwnerEnlargeBufferIOs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	for (;;)
 	{
@@ -5209,9 +5248,14 @@ StartBufferIO(BufferDesc *buf, bool forInput)
  * set_flag_bits gets ORed into the buffer's flags.  It must include
  * BM_IO_ERROR in a failure case.  For successful completion it could
  * be 0, or BM_VALID if we just finished reading in the page.
+ *
+ * If forget_owner is true, we release the buffer I/O from the current
+ * resource owner. (forget_owner=false is used when the resource owner itself
+ * is being released)
  */
 static void
-TerminateBufferIO(BufferDesc *buf, bool clear_dirty, uint32 set_flag_bits)
+TerminateBufferIO(BufferDesc *buf, bool clear_dirty, uint32 set_flag_bits,
+				  bool forget_owner)
 {
 	uint32		buf_state;
 
@@ -5226,8 +5270,9 @@ TerminateBufferIO(BufferDesc *buf, bool clear_dirty, uint32 set_flag_bits)
 	buf_state |= set_flag_bits;
 	UnlockBufHdr(buf, buf_state);
 
-	ResourceOwnerForgetBufferIO(CurrentResourceOwner,
-								BufferDescriptorGetBuffer(buf));
+	if (forget_owner)
+		ResourceOwnerForgetBufferIO(CurrentResourceOwner,
+									BufferDescriptorGetBuffer(buf));
 
 	ConditionVariableBroadcast(BufferDescriptorGetIOCV(buf));
 }
@@ -5240,8 +5285,12 @@ TerminateBufferIO(BufferDesc *buf, bool clear_dirty, uint32 set_flag_bits)
  *
  *	If I/O was in progress, we always set BM_IO_ERROR, even though it's
  *	possible the error condition wasn't related to the I/O.
+ *
+ *  Note: this does not remove the buffer I/O from the resource owner.
+ *  That's correct when we're releasing the whole resource owner, but
+ *  beware if you use this in other contexts.
  */
-void
+static void
 AbortBufferIO(Buffer buffer)
 {
 	BufferDesc *buf_hdr = GetBufferDescriptor(buffer - 1);
@@ -5277,7 +5326,7 @@ AbortBufferIO(Buffer buffer)
 		}
 	}
 
-	TerminateBufferIO(buf_hdr, false, BM_IO_ERROR);
+	TerminateBufferIO(buf_hdr, false, BM_IO_ERROR, false);
 }
 
 /*
@@ -5629,3 +5678,42 @@ IssuePendingWritebacks(WritebackContext *wb_context, IOContext io_context)
 
 	wb_context->nr_pending = 0;
 }
+
+/* ResourceOwner callbacks */
+
+static void
+ResOwnerReleaseBufferIO(Datum res)
+{
+	Buffer		buffer = DatumGetInt32(res);
+
+	AbortBufferIO(buffer);
+}
+
+static char *
+ResOwnerPrintBufferIO(Datum res)
+{
+	Buffer		buffer = DatumGetInt32(res);
+
+	return psprintf("lost track of buffer IO on buffer %d", buffer);
+}
+
+static void
+ResOwnerReleaseBufferPin(Datum 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))
+		UnpinLocalBufferNoOwner(buffer);
+	else
+		UnpinBufferNoOwner(GetBufferDescriptor(buffer - 1));
+}
+
+static char *
+ResOwnerPrintBufferPin(Datum res)
+{
+	return DebugPrintBufferRefcount(DatumGetInt32(res));
+}
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index b8715b54651..4efb34b75a1 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -21,9 +21,10 @@
 #include "pgstat.h"
 #include "storage/buf_internals.h"
 #include "storage/bufmgr.h"
+#include "storage/fd.h"
 #include "utils/guc_hooks.h"
 #include "utils/memutils.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 
 
 /*#define LBDEBUG*/
@@ -130,7 +131,7 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
 	if (LocalBufHash == NULL)
 		InitLocalBuffers();
 
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	/* See if the desired buffer already exists */
 	hresult = (LocalBufferLookupEnt *)
@@ -182,7 +183,7 @@ GetLocalVictimBuffer(void)
 	uint32		buf_state;
 	BufferDesc *bufHdr;
 
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	/*
 	 * Need to get a new buffer.  We use a clock sweep algorithm (essentially
@@ -674,6 +675,13 @@ PinLocalBuffer(BufferDesc *buf_hdr, bool adjust_usagecount)
 
 void
 UnpinLocalBuffer(Buffer buffer)
+{
+	UnpinLocalBufferNoOwner(buffer);
+	ResourceOwnerForgetBuffer(CurrentResourceOwner, buffer);
+}
+
+void
+UnpinLocalBufferNoOwner(Buffer buffer)
 {
 	int			buffid = -buffer - 1;
 
@@ -681,7 +689,6 @@ UnpinLocalBuffer(Buffer buffer)
 	Assert(LocalRefCount[buffid] > 0);
 	Assert(NLocalPinnedBuffers > 0);
 
-	ResourceOwnerForgetBuffer(CurrentResourceOwner, buffer);
 	if (--LocalRefCount[buffid] == 0)
 		NLocalPinnedBuffers--;
 }
@@ -785,8 +792,12 @@ CheckForLocalBufferLeaks(void)
 			if (LocalRefCount[i] != 0)
 			{
 				Buffer		b = -i - 1;
+				char	   *s;
+
+				s = DebugPrintBufferRefcount(b);
+				elog(WARNING, "local buffer refcount leak: %s", s);
+				pfree(s);
 
-				PrintBufferLeakWarning(b);
 				RefCountErrors++;
 			}
 		}
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index b7607aa1bec..38d7874df6d 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -99,7 +99,7 @@
 #include "storage/ipc.h"
 #include "utils/guc.h"
 #include "utils/guc_hooks.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 #include "utils/varlena.h"
 
 /* Define PG_FLUSH_DATA_WORKS if we have an implementation for pg_flush_data */
@@ -354,6 +354,31 @@ 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 char *ResOwnerPrintFile(Datum res);
+
+static const ResourceOwnerDesc file_resowner_desc =
+{
+	.name = "File",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_FILES,
+	.ReleaseResource = ResOwnerReleaseFile,
+	.DebugPrint = ResOwnerPrintFile
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberFile(ResourceOwner owner, File file)
+{
+	ResourceOwnerRemember(owner, Int32GetDatum(file), &file_resowner_desc);
+}
+static inline void
+ResourceOwnerForgetFile(ResourceOwner owner, File file)
+{
+	ResourceOwnerForget(owner, Int32GetDatum(file), &file_resowner_desc);
+}
+
 /*
  * pg_fsync --- do fsync with or without writethrough
  */
@@ -1492,7 +1517,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
@@ -1684,7 +1709,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
@@ -1816,7 +1841,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
@@ -1856,7 +1881,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);
 
@@ -3972,3 +3997,25 @@ assign_debug_io_direct(const char *newval, void *extra)
 
 	io_direct_flags = *flags;
 }
+
+/* ResourceOwner callbacks */
+
+static void
+ResOwnerReleaseFile(Datum res)
+{
+	File		file = (File) DatumGetInt32(res);
+	Vfd		   *vfdP;
+
+	Assert(FileIsValid(file));
+
+	vfdP = &VfdCache[file];
+	vfdP->resowner = NULL;
+
+	FileClose(file);
+}
+
+static char *
+ResOwnerPrintFile(Datum res)
+{
+	return psprintf("File %d", DatumGetInt32(res));
+}
diff --git a/src/backend/storage/ipc/dsm.c b/src/backend/storage/ipc/dsm.c
index 7e4e27810e8..628f3ecd3fb 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,32 @@ 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 char *ResOwnerPrintDSM(Datum res);
+
+static const ResourceOwnerDesc dsm_resowner_desc =
+{
+	.name = "dynamic shared memory segment",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_DSMS,
+	.ReleaseResource = ResOwnerReleaseDSM,
+	.DebugPrint = ResOwnerPrintDSM
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberDSM(ResourceOwner owner, dsm_segment *seg)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(seg), &dsm_resowner_desc);
+}
+static inline void
+ResourceOwnerForgetDSM(ResourceOwner owner, dsm_segment *seg)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(seg), &dsm_resowner_desc);
+}
+
 /*
  * Start up the dynamic shared memory system.
  *
@@ -907,7 +935,7 @@ void
 dsm_unpin_mapping(dsm_segment *seg)
 {
 	Assert(seg->resowner == NULL);
-	ResourceOwnerEnlargeDSMs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	seg->resowner = CurrentResourceOwner;
 	ResourceOwnerRememberDSM(seg->resowner, seg);
 }
@@ -1176,7 +1204,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);
@@ -1255,3 +1283,22 @@ is_main_region_dsm_handle(dsm_handle handle)
 {
 	return handle & 1;
 }
+
+/* ResourceOwner callbacks */
+
+static void
+ResOwnerReleaseDSM(Datum res)
+{
+	dsm_segment *seg = (dsm_segment *) DatumGetPointer(res);
+
+	seg->resowner = NULL;
+	dsm_detach(seg);
+}
+static char *
+ResOwnerPrintDSM(Datum res)
+{
+	dsm_segment *seg = (dsm_segment *) DatumGetPointer(res);
+
+	return psprintf("dynamic shared memory segment %u",
+					dsm_segment_handle(seg));
+}
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index ec6240fbaee..b8c57b3e16e 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 18db7e78e21..2e2e4d9f1f7 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"
 
 
@@ -94,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,
@@ -104,6 +107,56 @@ static void CatCacheCopyKeys(TupleDesc tupdesc, int nkeys, int *attnos,
  *					internal support functions
  */
 
+/* ResourceOwner callbacks to hold catcache references */
+
+static void ResOwnerReleaseCatCache(Datum res);
+static char *ResOwnerPrintCatCache(Datum res);
+static void ResOwnerReleaseCatCacheList(Datum res);
+static char *ResOwnerPrintCatCacheList(Datum res);
+
+static const ResourceOwnerDesc catcache_resowner_desc =
+{
+	/* catcache references */
+	.name = "catcache reference",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_CATCACHE_REFS,
+	.ReleaseResource = ResOwnerReleaseCatCache,
+	.DebugPrint = ResOwnerPrintCatCache
+};
+
+static const ResourceOwnerDesc catlistref_resowner_desc =
+{
+	/* catcache-list pins */
+	.name = "catcache list reference",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_CATCACHE_LIST_REFS,
+	.ReleaseResource = ResOwnerReleaseCatCacheList,
+	.DebugPrint = ResOwnerPrintCatCacheList
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberCatCacheRef(ResourceOwner owner, HeapTuple tuple)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(tuple), &catcache_resowner_desc);
+}
+static inline void
+ResourceOwnerForgetCatCacheRef(ResourceOwner owner, HeapTuple tuple)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(tuple), &catcache_resowner_desc);
+}
+static inline void
+ResourceOwnerRememberCatCacheListRef(ResourceOwner owner, CatCList *list)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(list), &catlistref_resowner_desc);
+}
+static inline void
+ResourceOwnerForgetCatCacheListRef(ResourceOwner owner, CatCList *list)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(list), &catlistref_resowner_desc);
+}
+
+
 /*
  * 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
@@ -1268,7 +1321,7 @@ SearchCatCacheInternal(CatCache *cache,
 		 */
 		if (!ct->negative)
 		{
-			ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner);
+			ResourceOwnerEnlarge(CurrentResourceOwner);
 			ct->refcount++;
 			ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple);
 
@@ -1369,7 +1422,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 */
@@ -1436,6 +1489,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));
@@ -1445,7 +1504,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
@@ -1581,7 +1641,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);
 
@@ -1693,7 +1753,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);
@@ -1778,12 +1838,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
@@ -2059,31 +2126,43 @@ PrepareToInvalidateCacheTuple(Relation relation,
 	}
 }
 
+/* ResourceOwner callbacks */
 
-/*
- * Subroutines for warning about reference leaks.  These are exported so
- * that resowner.c can call them.
- */
-void
-PrintCatCacheLeakWarning(HeapTuple tuple)
+static void
+ResOwnerReleaseCatCache(Datum res)
 {
+	ReleaseCatCacheWithOwner((HeapTuple) DatumGetPointer(res), NULL);
+}
+
+static char *
+ResOwnerPrintCatCache(Datum res)
+{
+	HeapTuple	tuple = (HeapTuple) DatumGetPointer(res);
 	CatCTup    *ct = (CatCTup *) (((char *) tuple) -
 								  offsetof(CatCTup, tuple));
 
 	/* Safety check to ensure we were handed a cache entry */
 	Assert(ct->ct_magic == CT_MAGIC);
 
-	elog(WARNING, "cache reference leak: cache %s (%d), tuple %u/%u has count %d",
-		 ct->my_cache->cc_relname, ct->my_cache->id,
-		 ItemPointerGetBlockNumber(&(tuple->t_self)),
-		 ItemPointerGetOffsetNumber(&(tuple->t_self)),
-		 ct->refcount);
+	return psprintf("cache %s (%d), tuple %u/%u has count %d",
+					ct->my_cache->cc_relname, ct->my_cache->id,
+					ItemPointerGetBlockNumber(&(tuple->t_self)),
+					ItemPointerGetOffsetNumber(&(tuple->t_self)),
+					ct->refcount);
 }
 
-void
-PrintCatCacheListLeakWarning(CatCList *list)
+static void
+ResOwnerReleaseCatCacheList(Datum 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);
+	ReleaseCatCacheListWithOwner((CatCList *) DatumGetPointer(res), NULL);
+}
+
+static char *
+ResOwnerPrintCatCacheList(Datum res)
+{
+	CatCList   *list = (CatCList *) DatumGetPointer(res);
+
+	return psprintf("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 7d4168f82f5..8f95520f2fb 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"
@@ -119,6 +119,31 @@ 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 const ResourceOwnerDesc planref_resowner_desc =
+{
+	.name = "plancache reference",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_PLANCACHE_REFS,
+	.ReleaseResource = ResOwnerReleaseCachedPlan,
+	.DebugPrint = NULL			/* the default message is fine */
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(plan), &planref_resowner_desc);
+}
+static inline void
+ResourceOwnerForgetPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(plan), &planref_resowner_desc);
+}
+
+
 /* GUC parameter */
 int			plan_cache_mode = PLAN_CACHE_MODE_AUTO;
 
@@ -1233,7 +1258,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);
@@ -1396,7 +1421,7 @@ CachedPlanAllowsSimpleValidityCheck(CachedPlanSource *plansource,
 	/* Bump refcount if requested. */
 	if (owner)
 	{
-		ResourceOwnerEnlargePlanCacheRefs(owner);
+		ResourceOwnerEnlarge(owner);
 		plan->refcount++;
 		ResourceOwnerRememberPlanCacheRef(owner, plan);
 	}
@@ -1457,7 +1482,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);
 	}
@@ -2203,3 +2228,20 @@ ResetPlanCache(void)
 		cexpr->is_valid = false;
 	}
 }
+
+/*
+ * Release all CachedPlans remembered by 'owner'
+ */
+void
+ReleaseAllPlanCacheRefsInOwner(ResourceOwner owner)
+{
+	ResourceOwnerReleaseAllOfKind(owner, &planref_resowner_desc);
+}
+
+/* ResourceOwner callbacks */
+
+static void
+ResOwnerReleaseCachedPlan(Datum res)
+{
+	ReleaseCachedPlan((CachedPlan *) DatumGetPointer(res), NULL);
+}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index a49ab465b36..6da02052804 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"
 
@@ -273,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);
 
@@ -2115,6 +2117,31 @@ RelationIdGetRelation(Oid relationId)
  * ----------------------------------------------------------------
  */
 
+/* ResourceOwner callbacks to track relcache references */
+static void ResOwnerReleaseRelation(Datum res);
+static char *ResOwnerPrintRelCache(Datum res);
+
+static const ResourceOwnerDesc relref_resowner_desc =
+{
+	.name = "relcache reference",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_RELCACHE_REFS,
+	.ReleaseResource = ResOwnerReleaseRelation,
+	.DebugPrint = ResOwnerPrintRelCache
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberRelationRef(ResourceOwner owner, Relation rel)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(rel), &relref_resowner_desc);
+}
+static inline void
+ResourceOwnerForgetRelationRef(ResourceOwner owner, Relation rel)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(rel), &relref_resowner_desc);
+}
+
 /*
  * RelationIncrementReferenceCount
  *		Increments relation reference count.
@@ -2126,7 +2153,7 @@ RelationIdGetRelation(Oid relationId)
 void
 RelationIncrementReferenceCount(Relation rel)
 {
-	ResourceOwnerEnlargeRelationRefs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	rel->rd_refcnt += 1;
 	if (!IsBootstrapProcessingMode())
 		ResourceOwnerRememberRelationRef(CurrentResourceOwner, rel);
@@ -2162,6 +2189,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
@@ -6813,3 +6846,30 @@ unlink_initfile(const char *initfilename, int elevel)
 							initfilename)));
 	}
 }
+
+/*
+ * ResourceOwner callbacks
+ */
+static char *
+ResOwnerPrintRelCache(Datum res)
+{
+	Relation	rel = (Relation) DatumGetPointer(res);
+
+	return psprintf("relation \"%s\"", RelationGetRelationName(rel));
+}
+
+static void
+ResOwnerReleaseRelation(Datum 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 f94c9700df4..d67df3faedb 100644
--- a/src/backend/utils/resowner/README
+++ b/src/backend/utils/resowner/README
@@ -39,8 +39,8 @@ because transactions may initiate operations that require resources (such
 as query parsing) when no associated Portal exists yet.
 
 
-API Overview
-------------
+Usage
+-----
 
 The basic operations on a ResourceOwner are:
 
@@ -54,13 +54,6 @@ 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.
-
 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
 subtransaction or portal.  Therefore, the "release" operation on a child
@@ -79,3 +72,106 @@ 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.
+
+Adding a new resource type
+--------------------------
+
+ResourceOwner can track 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.
+
+To add a new kind of resource, define a ResourceOwnerDesc to describe it.
+For example:
+
+static const ResourceOwnerDesc myresource_desc = {
+	.name = "My fancy resource",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_FIRST,
+	.ReleaseResource = ReleaseMyResource,
+	.DebugPrint = PrintMyResource
+};
+
+ResourceOwnerRemember() and ResourceOwnerForget() functions take a pointer
+to that struct, along with a Datum to represent the resource.  The meaning
+of the Datum depends on the resource type.  Most resource types use it to
+store a pointer to some struct, but it can also be a file descriptor or
+library handle, for example.
+
+The ReleaseResource callback is called when a resource owner is released or
+deleted.  It should release any resources (e.g. close files, free memory)
+associated with the resource.  Because the callback is called during
+transaction abort, it must perform only low-level cleanup with no user
+visible effects.  The callback should not perform operations that could
+fail, like allocate memory.
+
+The optional DebugPrint callback is used in the warning at transaction
+commit, if any resources are leaked.  If not specified, a generic
+implementation that prints the resource name and the resource as a pointer
+is used.
+
+There is another 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 define a custom
+ResourceOwnerDesc struct.
+
+
+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 print a
+WARNING 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 f926f1faad3..081342145b6 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 the 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
@@ -20,85 +44,54 @@
  */
 #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/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;
+	const ResourceOwnerDesc *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 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 table.  Must be power of
+ * two because we 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.
- * 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 RESARRAY_MAX_ITEMS(capacity) \
-	((capacity) <= RESARRAY_MAX_ARRAY ? (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,
+				 "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
@@ -119,25 +112,43 @@ 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 bufferioarr;	/* in-progress buffer IO */
-	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 */
-
-	/* We can remember up to MAX_RESOWNER_LOCKS references to local locks. */
-	int			nlocks;			/* number of owned locks */
+	/*
+	 * When ResourceOwnerRelease is called, we sort the 'hash' and 'arr' by
+	 * the release priority.  After that, no new resources can be remembered
+	 * or forgotten in retail.  We have separate flags because
+	 * ResourceOwnerReleaseAllOfKind() temporarily sets 'releasing' without
+	 * sorting the arrays.
+	 */
+	bool		releasing;
+	bool		sorted;			/* are 'hash' and 'arr' sorted by priority? */
+
+	/*
+	 * 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 'sorted' 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 */
+
+	/*
+	 * The fixed-size array for recent resources.
+	 *
+	 * If 'sorted' is set, the contents are sorted by release priority.
+	 */
+	uint32		narr;			/* how many items are stored in the array */
+	ResourceElem arr[RESOWNER_ARRAY_SIZE];
+
+	/* The local locks cache. */
+	uint32		nlocks;			/* number of owned locks */
 	LOCALLOCK  *locks[MAX_RESOWNER_LOCKS];	/* list of owned locks */
-}			ResourceOwnerData;
+} ResourceOwnerData;
 
 
 /*****************************************************************************
@@ -149,6 +160,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
  */
@@ -163,253 +186,204 @@ 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, const ResourceOwnerDesc *kind);
+static void ResourceOwnerAddToHash(ResourceOwner owner, Datum value,
+								   const ResourceOwnerDesc *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);
 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														 *
  *****************************************************************************/
 
-
-/*
- * Initialize a ResourceArray
- */
-static void
-ResourceArrayInit(ResourceArray *resarr, Datum invalidval)
+static inline uint32
+hash_resource_elem(Datum value, const ResourceOwnerDesc *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 */
+	Datum		data[2];
+
+	data[0] = value;
+	data[1] = PointerGetDatum(kind);
+
+	return hash_bytes((unsigned char *) &data, 2 * SIZEOF_DATUM);
 }
 
 /*
- * 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.
+ * Adds 'value' of given 'kind' to the ResourceOwner's hash table
  */
 static void
-ResourceArrayEnlarge(ResourceArray *resarr)
+ResourceOwnerAddToHash(ResourceOwner owner, Datum value, const ResourceOwnerDesc *kind)
 {
-	uint32		i,
-				oldcap,
-				newcap;
-	Datum	   *olditemsarr;
-	Datum	   *newitemsarr;
-
-	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;
+	uint32		mask = owner->capacity - 1;
+	uint32		idx;
 
-	/* 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;
+	Assert(kind != NULL);
 
-	if (olditemsarr != NULL)
+	/* Insert into first free slot at or after hash location. */
+	idx = hash_resource_elem(value, kind) & mask;
+	for (;;)
 	{
-		/*
-		 * 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.
-		 */
-		for (i = 0; i < oldcap; i++)
-		{
-			if (olditemsarr[i] != resarr->invalidval)
-				ResourceArrayAdd(resarr, olditemsarr[i]);
-		}
-
-		/* And release old array. */
-		pfree(olditemsarr);
+		if (owner->hash[idx].kind == NULL)
+			break;				/* found a free slot */
+		idx = (idx + 1) & mask;
 	}
-
-	Assert(resarr->nitems < resarr->maxitems);
+	owner->hash[idx].item = value;
+	owner->hash[idx].kind = kind;
+	owner->nhash++;
 }
 
 /*
- * Add a resource to ResourceArray
- *
- * Caller must have previously done ResourceArrayEnlarge()
+ * Comparison function to sort by release phase and priority
  */
-static void
-ResourceArrayAdd(ResourceArray *resarr, Datum value)
+static int
+resource_priority_cmp(const void *a, const void *b)
 {
-	uint32		idx;
+	const ResourceElem *ra = (const ResourceElem *) a;
+	const ResourceElem *rb = (const ResourceElem *) b;
 
-	Assert(value != resarr->invalidval);
-	Assert(resarr->nitems < resarr->maxitems);
-
-	if (RESARRAY_IS_ARRAY(resarr))
+	/* Note: reverse order */
+	if (ra->kind->release_phase == rb->kind->release_phase)
 	{
-		/* Append to linear array. */
-		idx = resarr->nitems;
+		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
-	{
-		/* 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++;
+		return 1;
 }
 
 /*
- * Remove a resource from ResourceArray
+ * Sort resources in reverse release priority.
  *
- * Returns true on success, false if resource was not found.
- *
- * Note: if same resource ID appears more than once, one instance is removed.
+ * 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 bool
-ResourceArrayRemove(ResourceArray *resarr, Datum value)
+static void
+ResourceOwnerSort(ResourceOwner owner)
 {
-	uint32		i,
-				idx,
-				lastidx = resarr->lastidx;
+	ResourceElem *items;
+	uint32		nitems;
 
-	Assert(value != resarr->invalidval);
-
-	/* Search through all items, but try lastidx first. */
-	if (RESARRAY_IS_ARRAY(resarr))
+	if (!owner->hash)
 	{
-		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++)
-		{
-			if (resarr->itemsarr[i] == value)
-			{
-				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;
-			}
-		}
+		items = owner->arr;
+		nitems = owner->narr;
 	}
 	else
 	{
-		uint32		mask = resarr->capacity - 1;
+		/*
+		 * Compact the hash table, so that all the elements are in the
+		 * beginning of the 'hash' array, with no empty elements.
+		 */
+		uint32		dst = 0;
 
-		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++)
+		for (int idx = 0; idx < owner->capacity; idx++)
 		{
-			if (resarr->itemsarr[idx] == value)
+			if (owner->hash[idx].kind != NULL)
 			{
-				resarr->itemsarr[idx] = resarr->invalidval;
-				resarr->nitems--;
-				return true;
+				if (dst != idx)
+					owner->hash[dst] = owner->hash[idx];
+				dst++;
 			}
-			idx = (idx + 1) & mask;
 		}
+
+		/*
+		 * 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.
+		 */
+		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;
 	}
 
-	return false;
+	qsort(items, nitems, sizeof(ResourceElem), resource_priority_cmp);
 }
 
 /*
- * 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.
- *
- * Returns true if we found an element, or false if the array is empty.
+ * Call the ReleaseResource callback on entries with given 'phase'.
  */
-static bool
-ResourceArrayGetAny(ResourceArray *resarr, Datum *value)
+static void
+ResourceOwnerReleaseAll(ResourceOwner owner, ResourceReleasePhase phase,
+						bool printLeakWarnings)
 {
-	if (resarr->nitems == 0)
-		return false;
+	ResourceElem *items;
+	uint32		nitems;
 
-	if (RESARRAY_IS_ARRAY(resarr))
+	/* ResourceOwnerSort must've been called already */
+	Assert(owner->releasing);
+	Assert(owner->sorted);
+	if (!owner->hash)
 	{
-		/* Linear array: just return the first element. */
-		resarr->lastidx = 0;
+		items = owner->arr;
+		nitems = owner->narr;
 	}
 	else
 	{
-		/* Hash: search forward from wherever we were last. */
-		uint32		mask = resarr->capacity - 1;
+		Assert(owner->narr == 0);
+		items = owner->hash;
+		nitems = owner->nhash;
+	}
+
+	/*
+	 * 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.
+	 */
+	while (nitems > 0)
+	{
+		uint32		idx = nitems - 1;
+		Datum		value = items[idx].item;
+		const ResourceOwnerDesc *kind = items[idx].kind;
+
+		if (kind->release_phase > phase)
+			break;
+		Assert(kind->release_phase == phase);
 
-		for (;;)
+		if (printLeakWarnings)
 		{
-			resarr->lastidx &= mask;
-			if (resarr->itemsarr[resarr->lastidx] != resarr->invalidval)
-				break;
-			resarr->lastidx++;
+			char	   *res_str;
+
+			res_str = kind->DebugPrint ?
+				kind->DebugPrint(value)
+				: psprintf("%s %p", kind->name, DatumGetPointer(value));
+			elog(WARNING, "resource was not closed: %s", res_str);
+			pfree(res_str);
 		}
+		kind->ReleaseResource(value);
+		nitems--;
 	}
-
-	*value = resarr->itemsarr[resarr->lastidx];
-	return true;
-}
-
-/*
- * Trash a ResourceArray (we don't care about its state after this)
- */
-static void
-ResourceArrayFree(ResourceArray *resarr)
-{
-	if (resarr->itemsarr)
-		pfree(resarr->itemsarr);
+	if (!owner->hash)
+		owner->narr = nitems;
+	else
+		owner->nhash = nitems;
 }
 
 
@@ -441,23 +415,205 @@ ResourceOwnerCreate(ResourceOwner parent, const char *name)
 		parent->firstchild = owner;
 	}
 
-	ResourceArrayInit(&(owner->bufferarr), BufferGetDatum(InvalidBuffer));
-	ResourceArrayInit(&(owner->bufferioarr), 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));
+#ifdef RESOWNER_TRACE
+	elog(LOG, "CREATE %d: %p %s",
+		 resowner_trace_counter++, owner, name);
+#endif
 
 	return owner;
 }
 
+/*
+ * 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.
+ *
+ * NB: Make sure there are no unrelated ResourceOwnerRemember() calls between
+ * your ResourceOwnerEnlarge() call and the ResourceOwnerRemember() call that
+ * you reserved the space for!
+ */
+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.
+	 */
+	if (owner->narr + owner->nhash >= owner->grow_at)
+	{
+		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)
+		{
+			/*
+			 * 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++)
+			{
+				if (oldhash[i].kind != NULL)
+					ResourceOwnerAddToHash(owner, oldhash[i].item, oldhash[i].kind);
+			}
+
+			/* And release old hash table. */
+			pfree(oldhash);
+		}
+	}
+
+	/* Move items from the array to the hash */
+	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);
+}
+
+/*
+ * Remember that an object is owner by a ReourceOwner
+ *
+ * Caller must have previously done ResourceOwnerEnlarge()
+ */
+void
+ResourceOwnerRemember(ResourceOwner owner, Datum value, const ResourceOwnerDesc *kind)
+{
+	uint32		idx;
+
+	/* sanity check the ResourceOwnerDesc */
+	Assert(kind->release_phase != 0);
+	Assert(kind->release_priority != 0);
+
+#ifdef RESOWNER_TRACE
+	elog(LOG, "REMEMBER %d: owner %p value " UINT64_FORMAT ", kind: %s",
+		 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);
+	Assert(!owner->sorted);
+
+	if (owner->narr >= RESOWNER_ARRAY_SIZE)
+	{
+		/* forgot to call ResourceOwnerEnlarge? */
+		elog(ERROR, "ResourceOwnerRemember called but array was full");
+	}
+
+	/* Append to the array. */
+	idx = owner->narr;
+	owner->arr[idx].item = value;
+	owner->arr[idx].kind = kind;
+	owner->narr++;
+}
+
+/*
+ * 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.
+ */
+void
+ResourceOwnerForget(ResourceOwner owner, Datum value, const ResourceOwnerDesc *kind)
+{
+#ifdef RESOWNER_TRACE
+	elog(LOG, "FORGET %d: owner %p value " UINT64_FORMAT ", kind: %s",
+		 resowner_trace_counter++, owner, DatumGetUInt64(value), kind->name);
+#endif
+
+	/*
+	 * 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);
+	Assert(!owner->sorted);
+
+	/* Search through all items in the array first. */
+	for (int i = owner->narr - 1; i >= 0; i--)
+	{
+		if (owner->arr[i].item == value &&
+			owner->arr[i].kind == kind)
+		{
+			owner->arr[i] = owner->arr[owner->narr - 1];
+			owner->narr--;
+
+#ifdef RESOWNER_STATS
+			narray_lookups++;
+#endif
+
+			return;
+		}
+	}
+
+	/* Search hash */
+	if (owner->nhash > 0)
+	{
+		uint32		mask = owner->capacity - 1;
+		uint32		idx;
+
+		idx = hash_resource_elem(value, kind) & mask;
+		for (uint32 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;
+		}
+	}
+
+	/*
+	 * 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);
+}
+
 /*
  * ResourceOwnerRelease
  *		Release all resources owned by a ResourceOwner and its descendants,
@@ -483,6 +639,12 @@ ResourceOwnerCreate(ResourceOwner parent, const char *name)
  * 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,
@@ -492,6 +654,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
@@ -504,105 +675,57 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 	ResourceOwner save;
 	ResourceReleaseCallbackItem *item;
 	ResourceReleaseCallbackItem *next;
-	Datum		foundres;
 
 	/* Recurse to handle descendants */
 	for (child = owner->firstchild; child != NULL; child = child->nextchild)
 		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.
 	 */
-	save = CurrentResourceOwner;
-	CurrentResourceOwner = owner;
-
-	if (phase == RESOURCE_RELEASE_BEFORE_LOCKS)
+	if (!owner->releasing)
+	{
+		Assert(phase == RESOURCE_RELEASE_BEFORE_LOCKS);
+		Assert(!owner->sorted);
+		owner->releasing = true;
+	}
+	else
 	{
 		/*
-		 * Abort failed buffer IO. AbortBufferIO()->TerminateBufferIO() calls
-		 * ResourceOwnerForgetBufferIO(), so we just have to iterate till
-		 * there are none.
-		 *
-		 * Needs to be before we release buffer pins.
-		 *
-		 * During a commit, there shouldn't be any in-progress IO.
+		 * 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.
 		 */
-		while (ResourceArrayGetAny(&(owner->bufferioarr), &foundres))
-		{
-			Buffer		res = DatumGetBuffer(foundres);
+	}
+	if (!owner->sorted)
+	{
+		ResourceOwnerSort(owner);
+		owner->sorted = true;
+	}
 
-			if (isCommit)
-				elog(PANIC, "lost track of buffer IO on buffer %d", res);
-			AbortBufferIO(res);
-		}
+	/*
+	 * Make CurrentResourceOwner point to me, so that the release callback
+	 * functions know which resource owner is been released.
+	 */
+	save = CurrentResourceOwner;
+	CurrentResourceOwner = 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)
 	{
@@ -655,101 +778,71 @@ 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);
-		}
+		ResourceOwnerReleaseAll(owner, phase, isCommit);
+	}
 
-		/* Ditto for catcache lists */
-		while (ResourceArrayGetAny(&(owner->catlistrefarr), &foundres))
-		{
-			CatCList   *res = (CatCList *) DatumGetPointer(foundres);
+	/* Let add-on modules get a chance too */
+	for (item = ResourceRelease_callbacks; item; item = next)
+	{
+		/* allow callbacks to unregister themselves when called */
+		next = item->next;
+		item->callback(phase, isCommit, isTopLevel, item->arg);
+	}
 
-			if (isCommit)
-				PrintCatCacheListLeakWarning(res);
-			ReleaseCatCacheList(res);
-		}
+	CurrentResourceOwner = save;
+}
 
-		/* 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);
+/*
+ * ResourceOwnerReleaseAllOfKind
+ *		Release all resources of a certain type held by this owner.
+ */
+void
+ResourceOwnerReleaseAllOfKind(ResourceOwner owner, const ResourceOwnerDesc *kind)
+{
+	/* 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);
+	Assert(!owner->sorted);
 
-			if (isCommit)
-				PrintTupleDescLeakWarning(res);
-			DecrTupleDescRefCount(res);
-		}
+	/*
+	 * 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;
 
-		/* Ditto for snapshot references */
-		while (ResourceArrayGetAny(&(owner->snapshotarr), &foundres))
+	/* Array first */
+	for (int i = 0; i < owner->narr; i++)
+	{
+		if (owner->arr[i].kind == kind)
 		{
-			Snapshot	res = (Snapshot) DatumGetPointer(foundres);
-
-			if (isCommit)
-				PrintSnapshotLeakWarning(res);
-			UnregisterSnapshot(res);
-		}
+			Datum		value = owner->arr[i].item;
 
-		/* Ditto for temporary files */
-		while (ResourceArrayGetAny(&(owner->filearr), &foundres))
-		{
-			File		res = DatumGetFile(foundres);
+			owner->arr[i] = owner->arr[owner->narr - 1];
+			owner->narr--;
+			i--;
 
-			if (isCommit)
-				PrintFileLeakWarning(res);
-			FileClose(res);
+			kind->ReleaseResource(value);
 		}
 	}
 
-	/* Let add-on modules get a chance too */
-	for (item = ResourceRelease_callbacks; item; item = next)
+	/* Then hash */
+	for (int i = 0; i < owner->capacity; i++)
 	{
-		/* allow callbacks to unregister themselves when called */
-		next = item->next;
-		item->callback(phase, isCommit, isTopLevel, item->arg);
-	}
-
-	CurrentResourceOwner = save;
-}
+		if (owner->hash[i].kind == kind)
+		{
+			Datum		value = owner->hash[i].item;
 
-/*
- * 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.
- */
-void
-ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner)
-{
-	Datum		foundres;
+			owner->hash[i].item = (Datum) 0;
+			owner->hash[i].kind = NULL;
+			owner->nhash--;
 
-	while (ResourceArrayGetAny(&(owner->planrefarr), &foundres))
-	{
-		CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres);
-
-		ReleaseCachedPlan(res, owner);
+			kind->ReleaseResource(value);
+		}
 	}
+	owner->releasing = false;
 }
 
 /*
@@ -765,21 +858,15 @@ ResourceOwnerDelete(ResourceOwner owner)
 	Assert(owner != CurrentResourceOwner);
 
 	/* And it better not own any resources, either */
-	Assert(owner->bufferarr.nitems == 0);
-	Assert(owner->bufferioarr.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.
@@ -795,20 +882,8 @@ ResourceOwnerDelete(ResourceOwner owner)
 	ResourceOwnerNewParent(owner, NULL);
 
 	/* And free the object. */
-	ResourceArrayFree(&(owner->bufferarr));
-	ResourceArrayFree(&(owner->bufferioarr));
-	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);
 }
 
@@ -866,11 +941,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
+ * ResourceOwnerDesc with custom callbacks.
  */
 void
 RegisterResourceReleaseCallback(ResourceReleaseCallback callback, void *arg)
@@ -946,6 +1020,9 @@ ReleaseAuxProcessResources(bool isCommit)
 	ResourceOwnerRelease(AuxProcessResourceOwner,
 						 RESOURCE_RELEASE_AFTER_LOCKS,
 						 isCommit, true);
+	/* allow it to be reused */
+	AuxProcessResourceOwner->releasing = false;
+	AuxProcessResourceOwner->sorted = false;
 }
 
 /*
@@ -960,88 +1037,12 @@ ReleaseAuxProcessResourcesCallback(int code, Datum arg)
 	ReleaseAuxProcessResources(isCommit);
 }
 
-
-/*
- * Make sure there is room for at least one more entry in a ResourceOwner's
- * buffer 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
-ResourceOwnerEnlargeBuffers(ResourceOwner owner)
-{
-	/* 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);
-}
-
-
-/*
- * Make sure there is room for at least one more entry in a ResourceOwner's
- * buffer 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
-ResourceOwnerEnlargeBufferIOs(ResourceOwner owner)
-{
-	/* We used to allow pinning buffers without a resowner, but no more */
-	Assert(owner != NULL);
-	ResourceArrayEnlarge(&(owner->bufferioarr));
-}
-
-/*
- * Remember that a buffer IO is owned by a ResourceOwner
- *
- * Caller must have previously done ResourceOwnerEnlargeBufferIOs()
- */
-void
-ResourceOwnerRememberBufferIO(ResourceOwner owner, Buffer buffer)
-{
-	ResourceArrayAdd(&(owner->bufferioarr), BufferGetDatum(buffer));
-}
-
-/*
- * Forget that a buffer IO is owned by a ResourceOwner
- */
-void
-ResourceOwnerForgetBufferIO(ResourceOwner owner, Buffer buffer)
-{
-	if (!ResourceArrayRemove(&(owner->bufferioarr), BufferGetDatum(buffer)))
-		elog(PANIC, "buffer IO %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
+ * 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,
  * ResourceOwnerForgetLock doesn't need to scan through a large array to find
  * the entry.
@@ -1087,469 +1088,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 4a3613d15fc..9198850ad25 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"
@@ -66,7 +67,7 @@
 #include "utils/builtins.h"
 #include "utils/memutils.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"
@@ -162,9 +163,34 @@ static List *exportedSnapshots = NIL;
 
 /* Prototypes for local functions */
 static Snapshot CopySnapshot(Snapshot snapshot);
+static void UnregisterSnapshotNoOwner(Snapshot snapshot);
 static void FreeSnapshot(Snapshot snapshot);
 static void SnapshotResetXmin(void);
 
+/* ResourceOwner callbacks to track snapshot references */
+static void ResOwnerReleaseSnapshot(Datum res);
+
+static const ResourceOwnerDesc snapshot_resowner_desc =
+{
+	.name = "snapshot reference",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_SNAPSHOT_REFS,
+	.ReleaseResource = ResOwnerReleaseSnapshot,
+	.DebugPrint = NULL			/* the default message is fine */
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberSnapshot(ResourceOwner owner, Snapshot snap)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(snap), &snapshot_resowner_desc);
+}
+static inline void
+ResourceOwnerForgetSnapshot(ResourceOwner owner, Snapshot snap)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(snap), &snapshot_resowner_desc);
+}
+
 /*
  * Snapshot fields to be serialized.
  *
@@ -796,7 +822,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);
 
@@ -832,11 +858,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);
@@ -1923,3 +1954,11 @@ XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot)
 
 	return false;
 }
+
+/* ResourceOwner callbacks */
+
+static void
+ResOwnerReleaseSnapshot(Datum res)
+{
+	UnregisterSnapshotNoOwner((Snapshot) DatumGetPointer(res));
+}
diff --git a/src/common/cryptohash_openssl.c b/src/common/cryptohash_openssl.c
index a654cd4ad40..366ce688b69 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,32 @@ struct pg_cryptohash_ctx
 #endif
 };
 
+/* ResourceOwner callbacks to hold cryptohash contexts */
+#ifndef FRONTEND
+static void ResOwnerReleaseCryptoHash(Datum res);
+
+static const ResourceOwnerDesc cryptohash_resowner_desc =
+{
+	.name = "OpenSSL cryptohash context",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_CRYPTOHASH_CONTEXTS,
+	.ReleaseResource = ResOwnerReleaseCryptoHash,
+	.DebugPrint = NULL			/* the default message is fine */
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberCryptoHash(ResourceOwner owner, pg_cryptohash_ctx *ctx)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(ctx), &cryptohash_resowner_desc);
+}
+static inline void
+ResourceOwnerForgetCryptoHash(ResourceOwner owner, pg_cryptohash_ctx *ctx)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(ctx), &cryptohash_resowner_desc);
+}
+#endif
+
 static const char *
 SSLerrmessage(unsigned long ecode)
 {
@@ -104,7 +129,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 +163,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 +331,8 @@ pg_cryptohash_free(pg_cryptohash_ctx *ctx)
 	EVP_MD_CTX_destroy(ctx->evpctx);
 
 #ifndef FRONTEND
-	ResourceOwnerForgetCryptoHash(ctx->resowner,
-								  PointerGetDatum(ctx));
+	if (ctx->resowner)
+		ResourceOwnerForgetCryptoHash(ctx->resowner, ctx);
 #endif
 
 	explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
@@ -351,3 +375,16 @@ 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_ctx *ctx = (pg_cryptohash_ctx *) DatumGetPointer(res);
+
+	ctx->resowner = NULL;
+	pg_cryptohash_free(ctx);
+}
+#endif
diff --git a/src/common/hmac_openssl.c b/src/common/hmac_openssl.c
index 12be542fa27..264170d3da8 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,32 @@ struct pg_hmac_ctx
 #endif
 };
 
+/* ResourceOwner callbacks to hold HMAC contexts */
+#ifndef FRONTEND
+static void ResOwnerReleaseHMAC(Datum res);
+
+static const ResourceOwnerDesc hmac_resowner_desc =
+{
+	.name = "OpenSSL HMAC context",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_HMAC_CONTEXTS,
+	.ReleaseResource = ResOwnerReleaseHMAC,
+	.DebugPrint = NULL			/* the default message is fine */
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberHMAC(ResourceOwner owner, pg_hmac_ctx *ctx)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(ctx), &hmac_resowner_desc);
+}
+static inline void
+ResourceOwnerForgetHMAC(ResourceOwner owner, pg_hmac_ctx *ctx)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(ctx), &hmac_resowner_desc);
+}
+#endif
+
 static const char *
 SSLerrmessage(unsigned long ecode)
 {
@@ -115,7 +140,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 +162,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 +328,8 @@ pg_hmac_free(pg_hmac_ctx *ctx)
 #ifdef HAVE_HMAC_CTX_FREE
 	HMAC_CTX_free(ctx->hmacctx);
 #ifndef FRONTEND
-	ResourceOwnerForgetHMAC(ctx->resowner, PointerGetDatum(ctx));
+	if (ctx->resowner)
+		ResourceOwnerForgetHMAC(ctx->resowner, ctx);
 #endif
 #else
 	explicit_bzero(ctx->hmacctx, sizeof(HMAC_CTX));
@@ -346,3 +372,16 @@ 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_ctx *ctx = (pg_hmac_ctx *) DatumGetPointer(res);
+
+	ctx->resowner = NULL;
+	pg_hmac_free(ctx);
+}
+#endif
diff --git a/src/include/storage/buf_internals.h b/src/include/storage/buf_internals.h
index bc79a329a1a..e15a86eff99 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -26,6 +26,7 @@
 #include "storage/smgr.h"
 #include "storage/spin.h"
 #include "utils/relcache.h"
+#include "utils/resowner.h"
 
 /*
  * Buffer state is a single 32-bit variable where following data is combined.
@@ -383,6 +384,32 @@ typedef struct CkptSortItem
 
 extern PGDLLIMPORT CkptSortItem *CkptBufferIds;
 
+/* ResourceOwner callbacks to hold buffer I/Os and pins */
+extern const ResourceOwnerDesc buffer_io_resowner_desc;
+extern const ResourceOwnerDesc buffer_pin_resowner_desc;
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer)
+{
+	ResourceOwnerRemember(owner, Int32GetDatum(buffer), &buffer_pin_resowner_desc);
+}
+static inline void
+ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer)
+{
+	ResourceOwnerForget(owner, Int32GetDatum(buffer), &buffer_pin_resowner_desc);
+}
+static inline void
+ResourceOwnerRememberBufferIO(ResourceOwner owner, Buffer buffer)
+{
+	ResourceOwnerRemember(owner, Int32GetDatum(buffer), &buffer_io_resowner_desc);
+}
+static inline void
+ResourceOwnerForgetBufferIO(ResourceOwner owner, Buffer buffer)
+{
+	ResourceOwnerForget(owner, Int32GetDatum(buffer), &buffer_io_resowner_desc);
+}
+
 /*
  * Internal buffer management routines
  */
@@ -418,6 +445,7 @@ extern void BufTableDelete(BufferTag *tagPtr, uint32 hashcode);
 /* localbuf.c */
 extern bool PinLocalBuffer(BufferDesc *buf_hdr, bool adjust_usagecount);
 extern void UnpinLocalBuffer(Buffer buffer);
+extern void UnpinLocalBufferNoOwner(Buffer buffer);
 extern PrefetchBufferResult PrefetchLocalBuffer(SMgrRelation smgr,
 												ForkNumber forkNum,
 												BlockNumber blockNum);
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 9241f868611..9a031d3b995 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -207,7 +207,7 @@ extern Buffer ExtendBufferedRelTo(BufferManagerRelation bmr,
 
 extern void InitBufferPoolAccess(void);
 extern void AtEOXact_Buffers(bool isCommit);
-extern void PrintBufferLeakWarning(Buffer buffer);
+extern char *DebugPrintBufferRefcount(Buffer buffer);
 extern void CheckPointBuffers(int flags);
 extern BlockNumber BufferGetBlockNumber(Buffer buffer);
 extern BlockNumber RelationGetNumberOfBlocksInFork(Relation relation,
@@ -248,8 +248,6 @@ extern bool ConditionalLockBufferForCleanup(Buffer buffer);
 extern bool IsBufferCleanupOK(Buffer buffer);
 extern bool HoldingBufferPinThatDelaysRecovery(void);
 
-extern void AbortBufferIO(Buffer buffer);
-
 extern bool BgBufferSync(struct WritebackContext *wb_context);
 
 /* in buf_init.c */
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 916e59d9fef..e432564a128 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);
diff --git a/src/include/utils/resowner.h b/src/include/utils/resowner.h
index cd070b6080e..8a3d080ba01 100644
--- a/src/include/utils/resowner.h
+++ b/src/include/utils/resowner.h
@@ -37,19 +37,87 @@ extern PGDLLIMPORT ResourceOwner AuxProcessResourceOwner;
 
 /*
  * Resource releasing is done in three phases: pre-locks, locks, and
- * post-locks.  The pre-lock phase must release any resources that are
- * visible to other backends (such as pinned buffers); this ensures that
- * 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.
+ * post-locks.  The pre-lock phase must release any resources that are visible
+ * to other backends (such as pinned buffers); this ensures that 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 ResourceOwnerDesc.  The priorities of built-in
+ * resource types are given below, extensions may use any priority relative to
+ * those or RELEASE_PRIO_FIRST/LAST.  RELEASE_PRIO_FIRST is a fine choice if
+ * your resource doesn't depend on any other resources.
  */
 typedef enum
 {
-	RESOURCE_RELEASE_BEFORE_LOCKS,
+	RESOURCE_RELEASE_BEFORE_LOCKS = 1,
 	RESOURCE_RELEASE_LOCKS,
 	RESOURCE_RELEASE_AFTER_LOCKS
 } ResourceReleasePhase;
 
+typedef uint32 ResourceReleasePriority;
+
+/* priorities of built-in BEFORE_LOCKS resources */
+#define RELEASE_PRIO_BUFFER_IOS			    100
+#define RELEASE_PRIO_BUFFER_PINS		    200
+#define RELEASE_PRIO_RELCACHE_REFS			300
+#define RELEASE_PRIO_DSMS					400
+#define RELEASE_PRIO_JIT_CONTEXTS			500
+#define RELEASE_PRIO_CRYPTOHASH_CONTEXTS	600
+#define RELEASE_PRIO_HMAC_CONTEXTS			700
+
+/* 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
+
+/* 0 is considered invalid */
+#define RELEASE_PRIO_FIRST					1
+#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
+ * ResourceOwnerDesc.
+ *
+ * Note that the callback occurs post-commit or post-abort, so these callback
+ * functions can only do noncritical cleanup.
+ */
+typedef struct ResourceOwnerDesc
+{
+	const char *name;			/* name for the object kind, for debugging */
+
+	/* when are these objects released? */
+	ResourceReleasePhase release_phase;
+	ResourceReleasePriority release_priority;
+
+	/*
+	 * Release resource.
+	 *
+	 * 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);
+
+	/*
+	 * Format a string describing the resource, for debugging purposes.  If a
+	 * resource has not been properly released before commit, this is used to
+	 * print a WARNING.
+	 *
+	 * This can be left to NULL, in which case a generic "[resource name]: %p"
+	 * format is used.
+	 */
+	char	   *(*DebugPrint) (Datum res);
+
+} ResourceOwnerDesc;
+
 /*
  *	Dynamically loaded modules can get control during ResourceOwnerRelease
  *	by providing a callback of this form.
@@ -71,16 +139,28 @@ 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, const ResourceOwnerDesc *kind);
+extern void ResourceOwnerForget(ResourceOwner owner, Datum res, const ResourceOwnerDesc *kind);
+
+extern void ResourceOwnerReleaseAllOfKind(ResourceOwner owner, const ResourceOwnerDesc *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);
+
 #endif							/* RESOWNER_H */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 4b76f7699a6..f8c7f487470 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -8470,7 +8470,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 e81873cb5ae..738b715e792 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -28,6 +28,7 @@ SUBDIRS = \
 		  test_predtest \
 		  test_rbtree \
 		  test_regex \
+		  test_resowner \
 		  test_rls_hooks \
 		  test_shm_mq \
 		  test_slru \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index fcd643f6f10..d4828dc44d5 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -25,6 +25,7 @@ subdir('test_pg_dump')
 subdir('test_predtest')
 subdir('test_rbtree')
 subdir('test_regex')
+subdir('test_resowner')
 subdir('test_rls_hooks')
 subdir('test_shm_mq')
 subdir('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..527b678fc12
--- /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 1
+NOTICE:  releasing string: child before locks priority 1
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: parent before locks priority 1
+NOTICE:  releasing string: parent before locks priority 1
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing locks
+NOTICE:  releasing resources after locks
+NOTICE:  releasing string: child after locks priority 1
+NOTICE:  releasing string: child after locks priority 1
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: parent after locks priority 1
+NOTICE:  releasing string: parent after locks priority 1
+NOTICE:  releasing string: parent after locks priority 2
+ 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 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: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+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 string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing locks
+NOTICE:  releasing resources after locks
+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: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+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
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+ 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:  resource was not closed: test string "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..5f669505a65
--- /dev/null
+++ b/src/test/modules/test_resowner/meson.build
@@ -0,0 +1,34 @@
+# Copyright (c) 2022-2023, PostgreSQL Global Development Group
+
+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_test_mod_args,
+)
+test_install_libs += test_resowner
+
+test_install_data += files(
+  'test_resowner.control',
+  'test_resowner--1.0.sql',
+)
+
+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/sql/test_resowner.sql b/src/test/modules/test_resowner/sql/test_resowner.sql
new file mode 100644
index 00000000000..23284b7c8b9
--- /dev/null
+++ b/src/test/modules/test_resowner/sql/test_resowner.sql
@@ -0,0 +1,25 @@
+CREATE EXTENSION test_resowner;
+
+-- This is small enough that everything fits in the small array
+SELECT test_resowner_priorities(2, 3);
+
+-- Same test with more resources, to exercise the hash table
+SELECT test_resowner_priorities(2, 32);
+
+-- 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
+);
+
+-- Test resource leak warning
+SELECT test_resowner_leak();
+
+-- 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();
+SELECT test_resowner_forget_between_phases();
+reset client_min_messages;
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..54d930828d7
--- /dev/null
+++ b/src/test/modules/test_resowner/test_resowner_basic.c
@@ -0,0 +1,212 @@
+/*--------------------------------------------------------------------------
+ *
+ * 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 char *PrintString(Datum res);
+
+/*
+ * A resource that tracks strings and prints the string when it's released.
+ * This makes the order that the resources are released visible.
+ */
+static const ResourceOwnerDesc string_desc = {
+	.name = "test resource",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_FIRST,
+	.ReleaseResource = ReleaseString,
+	.DebugPrint = PrintString
+};
+
+static void
+ReleaseString(Datum res)
+{
+	elog(NOTICE, "releasing string: %s", DatumGetPointer(res));
+}
+
+static char *
+PrintString(Datum res)
+{
+	return psprintf("test string \"%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;
+	ResourceOwnerDesc *before_desc;
+	ResourceOwnerDesc *after_desc;
+
+	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");
+
+	before_desc = palloc(nkinds * sizeof(ResourceOwnerDesc));
+	for (int i = 0; i < nkinds; i++)
+	{
+		before_desc[i].name = psprintf("test resource before locks %d", i);
+		before_desc[i].release_phase = RESOURCE_RELEASE_BEFORE_LOCKS;
+		before_desc[i].release_priority = RELEASE_PRIO_FIRST + i;
+		before_desc[i].ReleaseResource = ReleaseString;
+		before_desc[i].DebugPrint = PrintString;
+	}
+	after_desc = palloc(nkinds * sizeof(ResourceOwnerDesc));
+	for (int i = 0; i < nkinds; i++)
+	{
+		after_desc[i].name = psprintf("test resource after locks %d", i);
+		after_desc[i].release_phase = RESOURCE_RELEASE_AFTER_LOCKS;
+		after_desc[i].release_priority = RELEASE_PRIO_FIRST + i;
+		after_desc[i].ReleaseResource = ReleaseString;
+		after_desc[i].DebugPrint = PrintString;
+	}
+
+	/* Add a bunch of resources to child, with different priorities */
+	for (int i = 0; i < nresources; i++)
+	{
+		ResourceOwnerDesc *kind = &before_desc[i % nkinds];
+
+		ResourceOwnerEnlarge(child);
+		ResourceOwnerRemember(child,
+							  CStringGetDatum(psprintf("child before locks priority %d", kind->release_priority)),
+							  kind);
+	}
+	for (int i = 0; i < nresources; i++)
+	{
+		ResourceOwnerDesc *kind = &after_desc[i % nkinds];
+
+		ResourceOwnerEnlarge(child);
+		ResourceOwnerRemember(child,
+							  CStringGetDatum(psprintf("child after locks priority %d", kind->release_priority)),
+							  kind);
+	}
+
+	/* And also to the parent */
+	for (int i = 0; i < nresources; i++)
+	{
+		ResourceOwnerDesc *kind = &after_desc[i % nkinds];
+
+		ResourceOwnerEnlarge(parent);
+		ResourceOwnerRemember(parent,
+							  CStringGetDatum(psprintf("parent after locks priority %d", kind->release_priority)),
+							  kind);
+	}
+	for (int i = 0; i < nresources; i++)
+	{
+		ResourceOwnerDesc *kind = &before_desc[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_desc);
+
+	/* 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_desc);
+
+	/* 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_desc);
+
+	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_desc);
+
+	/* 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..3a50984b68c
--- /dev/null
+++ b/src/test/modules/test_resowner/test_resowner_many.c
@@ -0,0 +1,296 @@
+/*--------------------------------------------------------------------------
+ *
+ * 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
+{
+	ResourceOwnerDesc desc;
+	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 char *PrintManyTest(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->desc.name);
+	Assert(last_release_priority <= mres->kind->desc.release_priority);
+
+	dlist_delete(&mres->node);
+	mres->kind->nreleased++;
+	last_release_priority = mres->kind->desc.release_priority;
+	pfree(mres);
+}
+
+/* ResourceOwner callback */
+static char *
+PrintManyTest(Datum res)
+{
+	ManyTestResource *mres = (ManyTestResource *) DatumGetPointer(res);
+
+	/*
+	 * XXX: we assume that the DebugPrint function is called once for each
+	 * leaked resource, and that there are no other callers.
+	 */
+	mres->kind->nleaked++;
+	return psprintf("many-test resource from %s", mres->kind->desc.name);
+}
+
+static void
+InitManyTestResourceKind(ManyTestResourceKind *kind, char *name,
+						 ResourceReleasePhase phase, uint32 priority)
+{
+	kind->desc.name = name;
+	kind->desc.release_phase = phase;
+	kind->desc.release_priority = priority;
+	kind->desc.ReleaseResource = ReleaseManyTestResource;
+	kind->desc.DebugPrint = PrintManyTest;
+	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].desc);
+		kinds[kind_idx].nremembered++;
+		dlist_push_tail(&kinds[kind_idx].current_resources, &mres->node);
+
+		elog(DEBUG1, "remembered resource %p from %s", mres, mres->kind->desc.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;
+	int			ntotal;
+
+	ntotal = GetTotalResourceCount(kinds, nkinds);
+	if (ntotal < nresources)
+		elog(PANIC, "cannot free %d resources, only %d remembered", nresources, ntotal);
+
+	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].desc);
+				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,
+								 RELEASE_PRIO_FIRST + 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,
+								 RELEASE_PRIO_FIRST + 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();
+}
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 085d0d7e548..e7a8c089c22 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1525,6 +1525,8 @@ MVDependencies
 MVDependency
 MVNDistinct
 MVNDistinctItem
+ManyTestResource
+ManyTestResourceKind
 Material
 MaterialPath
 MaterialState
@@ -2357,8 +2359,10 @@ ReplicationStateOnDisk
 ResTarget
 ReservoirState
 ReservoirStateData
-ResourceArray
+ResourceElem
 ResourceOwner
+ResourceOwnerData
+ResourceOwnerDesc
 ResourceReleaseCallback
 ResourceReleaseCallbackItem
 ResourceReleasePhase
-- 
2.39.2

v16-0003-Use-a-faster-hash-function-in-resource-owners.patchtext/x-patch; charset=UTF-8; name=v16-0003-Use-a-faster-hash-function-in-resource-owners.patchDownload
From 72d1018b09746252d23ea852c7f6bf80db135335 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Mon, 31 Oct 2022 10:49:06 +0100
Subject: [PATCH v16 3/4] Use a faster hash function in resource owners.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

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

Reviewed-by: Aleksander Alekseev, Michael Paquier, Julien Rouhaud
Reviewed-by: Kyotaro Horiguchi, Hayato Kuroda, Álvaro Herrera, Zhihong Yu
Reviewed-by: Peter Eisentraut, Andres Freund
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 081342145b6..a0029831cc1 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -205,15 +205,27 @@ static void ReleaseAuxProcessResourcesCallback(int code, Datum arg);
  *	  INTERNAL ROUTINES														 *
  *****************************************************************************/
 
+/*
+ * Hash function for value+kind combination.
+ */
 static inline uint32
 hash_resource_elem(Datum value, const ResourceOwnerDesc *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.39.2

v16-0004-Change-pgcrypto-to-use-the-new-ResourceOwner-mec.patchtext/x-patch; charset=UTF-8; name=v16-0004-Change-pgcrypto-to-use-the-new-ResourceOwner-mec.patchDownload
From f9b1c8e56847abf7deab1e8b0eb81d2dc14d2bcc Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 25 Oct 2023 14:46:01 +0300
Subject: [PATCH v16 4/4] Change pgcrypto to use the new ResourceOwner
 mechanism.

Reviewed-by: Peter Eisentraut, Andres Freund
Discussion: https://www.postgresql.org/message-id/d746cead-a1ef-7efe-fb47-933311e876a3%40iki.fi
---
 contrib/pgcrypto/openssl.c | 181 ++++++++++++++++---------------------
 1 file changed, 78 insertions(+), 103 deletions(-)

diff --git a/contrib/pgcrypto/openssl.c b/contrib/pgcrypto/openssl.c
index cf315517e0c..4a913bd04f7 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,54 +59,39 @@ 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
-free_openssl_digest(OSSLDigest *digest)
+static const ResourceOwnerDesc ossldigest_resowner_desc =
 {
-	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;
-	pfree(digest);
+	.name = "pgcrypto OpenSSL digest handle",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_FIRST,
+	.ReleaseResource = ResOwnerReleaseOSSLDigest,
+	.DebugPrint = NULL,			/* default message is fine */
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberOSSLDigest(ResourceOwner owner, OSSLDigest *digest)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(digest), &ossldigest_resowner_desc);
+}
+static inline void
+ResourceOwnerForgetOSSLDigest(ResourceOwner owner, OSSLDigest *digest)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(digest), &ossldigest_resowner_desc);
 }
 
-/*
- * Close any open OpenSSL handles on abort.
- */
 static void
-digest_free_callback(ResourceReleasePhase phase,
-					 bool isCommit,
-					 bool isTopLevel,
-					 void *arg)
+free_openssl_digest(OSSLDigest *digest)
 {
-	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);
-		}
-	}
+	EVP_MD_CTX_destroy(digest->ctx);
+	if (digest->owner != NULL)
+		ResourceOwnerForgetOSSLDigest(digest->owner, digest);
+	pfree(digest);
 }
 
 static unsigned
@@ -188,16 +172,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 +201,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 +217,17 @@ 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);
+}
+
 /*
  * Ciphers
  *
@@ -266,9 +255,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,54 +269,39 @@ 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
-free_openssl_cipher(OSSLCipher *od)
+static const ResourceOwnerDesc osslcipher_resowner_desc =
 {
-	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;
-	pfree(od);
+	.name = "pgcrypto OpenSSL cipher handle",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_FIRST,
+	.ReleaseResource = ResOwnerReleaseOSSLCipher,
+	.DebugPrint = NULL,			/* default message is fine */
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberOSSLCipher(ResourceOwner owner, OSSLCipher *od)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(od), &osslcipher_resowner_desc);
+}
+static inline void
+ResourceOwnerForgetOSSLCipher(ResourceOwner owner, OSSLCipher *od)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(od), &osslcipher_resowner_desc);
 }
 
-/*
- * Close any open OpenSSL cipher handles on abort.
- */
 static void
-cipher_free_callback(ResourceReleasePhase phase,
-					 bool isCommit,
-					 bool isTopLevel,
-					 void *arg)
+free_openssl_cipher(OSSLCipher *od)
 {
-	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);
-		}
-	}
+	EVP_CIPHER_CTX_free(od->evp_ctx);
+	if (od->owner != NULL)
+		ResourceOwnerForgetOSSLCipher(od->owner, od);
+	pfree(od);
 }
 
 /* Common routines for all algorithms */
@@ -782,11 +755,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 +775,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 +794,11 @@ 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));
+}
-- 
2.39.2

#64Andres Freund
andres@anarazel.de
In reply to: Heikki Linnakangas (#63)
Re: ResourceOwner refactoring

Hi,

On 2023-10-25 15:43:36 +0300, Heikki Linnakangas wrote:

On 10/07/2023 22:14, Andres Freund wrote:

/*
- * Initially allocated size of a ResourceArray.  Must be power of two since
- * we'll use (arraysize - 1) as mask for hashing.
+ * Size of the fixed-size array to hold most-recently remembered resources.
*/
-#define RESARRAY_INIT_SIZE 16
+#define RESOWNER_ARRAY_SIZE 32

That's 512 bytes, pretty large to be searched sequentially. It's somewhat sad
that the array needs to include 8 byte of ResourceOwnerFuncs...

Yeah. Early on I considered having a RegisterResourceKind() function that
you need to call first, and then each resource kind could be assigned a
smaller ID. If we wanted to keep the old mechanism of separate arrays for
each different resource kind, that'd be the way to go. But overall this
seems simpler, and the performance is decent despite that linear scan.

Realistically, on a 64bit platform, the compiler / we want 64bit alignment for
ResourceElem->item, so making "kind" smaller isn't going to do much...

Due to 'narr' being before the large 'arr', 'nhash' is always on a separate
cacheline. I.e. the number of cachelines accessed in happy paths is higher
than necessary, also relevant because there's more dependencies on narr, nhash
than on the contents of those.

I'd probably order it so that both of the large elements (arr, locks) are at
the end.

It's hard to know with changes this small, but it does seem to yield a small
and repeatable performance benefit (pipelined pgbench -S).

Swapped the 'hash' and 'arr' parts of the struct, so that 'arr' and 'locks'
are at the end.

I don't remember what the layout was then, but now there are 10 bytes of holes
on x86-64 (and I think most other 64bit architectures).

struct ResourceOwnerData {
ResourceOwner parent; /* 0 8 */
ResourceOwner firstchild; /* 8 8 */
ResourceOwner nextchild; /* 16 8 */
const char * name; /* 24 8 */
_Bool releasing; /* 32 1 */
_Bool sorted; /* 33 1 */

/* XXX 6 bytes hole, try to pack */

ResourceElem * hash; /* 40 8 */
uint32 nhash; /* 48 4 */
uint32 capacity; /* 52 4 */
uint32 grow_at; /* 56 4 */
uint32 narr; /* 60 4 */
/* --- cacheline 1 boundary (64 bytes) --- */
ResourceElem arr[32]; /* 64 512 */
/* --- cacheline 9 boundary (576 bytes) --- */
uint32 nlocks; /* 576 4 */

/* XXX 4 bytes hole, try to pack */

LOCALLOCK * locks[15]; /* 584 120 */

/* size: 704, cachelines: 11, members: 14 */
/* sum members: 694, holes: 2, sum holes: 10 */
};

While somewhat annoying from a human pov, it seems like it would make sense to
rearrange a bit? At least moving nlocks into the first cacheline would be good
(not that we guarantee cacheline alignment rn, though it sometimes works out
to be aligned).

We also can make narr, nlocks uint8s.

With that narrowing and reordering, we end up with 688 bytes. Not worth for
most structs, but here perhaps worth doing?

Moving the lock length to the start of the struct would make sense to keep
things that are likely to be used in a "stalling" manner at the start of the
struct / in one cacheline.

It's not too awful to have it be in this order:

struct ResourceOwnerData {
ResourceOwner parent; /* 0 8 */
ResourceOwner firstchild; /* 8 8 */
ResourceOwner nextchild; /* 16 8 */
const char * name; /* 24 8 */
_Bool releasing; /* 32 1 */
_Bool sorted; /* 33 1 */
uint8 narr; /* 34 1 */
uint8 nlocks; /* 35 1 */
uint32 nhash; /* 36 4 */
uint32 capacity; /* 40 4 */
uint32 grow_at; /* 44 4 */
ResourceElem arr[32]; /* 48 512 */
/* --- cacheline 8 boundary (512 bytes) was 48 bytes ago --- */
ResourceElem * hash; /* 560 8 */
LOCALLOCK * locks[15]; /* 568 120 */

/* size: 688, cachelines: 11, members: 14 */
/* last cacheline: 48 bytes */
};

Requires rephrasing a few comments to document that the lenghts are separate
from the array / hashtable / locks, but otherwise...

This reliably shows a decent speed improvement in my stress test [1]pgbench running DO $$ BEGIN FOR i IN 1..5000 LOOP PERFORM abalance FROM pgbench_accounts WHERE aid = 17;END LOOP;END;$$;, on the
order of 5%.

At that point, the first array entry fits into the first cacheline. If we were
to move parent, firstchild, nextchild, name further down the struct, three
array entries would be on the first line. Just using the array presumably is a
very common case, so that might be worth it?

I got less clear performance results with this one, and it's also quite
possible it could hurt, if resowners aren't actually "used", just
released. Therefore it's probably not worth it for now.

Greetings,

Andres Freund

[1]: pgbench running DO $$ BEGIN FOR i IN 1..5000 LOOP PERFORM abalance FROM pgbench_accounts WHERE aid = 17;END LOOP;END;$$;
DO $$ BEGIN FOR i IN 1..5000 LOOP PERFORM abalance FROM pgbench_accounts WHERE aid = 17;END LOOP;END;$$;

#65Peter Eisentraut
peter@eisentraut.org
In reply to: Heikki Linnakangas (#63)
Re: ResourceOwner refactoring

It looks like this patch set needs a bit of surgery to adapt to the LLVM
changes in 9dce22033d. The cfbot is reporting compiler warnings about
this, and also some crashes, which might also be caused by this.

I do like the updated APIs. (Maybe the repeated ".DebugPrint = NULL,
/* default message is fine */" lines could be omitted?)

I like that one can now easily change the elog(WARNING) in
ResourceOwnerReleaseAll() to a PANIC or something to get automatic
verification during testing. I wonder if we should make this the
default if assertions are on? This would need some adjustments to
src/test/modules/test_resowner because it would then fail.

#66Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Andres Freund (#64)
Re: ResourceOwner refactoring

On 25/10/2023 21:07, Andres Freund wrote:

It's not too awful to have it be in this order:

struct ResourceOwnerData {
ResourceOwner parent; /* 0 8 */
ResourceOwner firstchild; /* 8 8 */
ResourceOwner nextchild; /* 16 8 */
const char * name; /* 24 8 */
_Bool releasing; /* 32 1 */
_Bool sorted; /* 33 1 */
uint8 narr; /* 34 1 */
uint8 nlocks; /* 35 1 */
uint32 nhash; /* 36 4 */
uint32 capacity; /* 40 4 */
uint32 grow_at; /* 44 4 */
ResourceElem arr[32]; /* 48 512 */
/* --- cacheline 8 boundary (512 bytes) was 48 bytes ago --- */
ResourceElem * hash; /* 560 8 */
LOCALLOCK * locks[15]; /* 568 120 */

/* size: 688, cachelines: 11, members: 14 */
/* last cacheline: 48 bytes */
};

Requires rephrasing a few comments to document that the lenghts are separate
from the array / hashtable / locks, but otherwise...

Hmm, let's move 'capacity' and 'grow_at' after 'arr', too. They are only
needed together with 'hash'.

This reliably shows a decent speed improvement in my stress test [1], on the
order of 5%.

[1] pgbench running
DO $$ BEGIN FOR i IN 1..5000 LOOP PERFORM abalance FROM pgbench_accounts WHERE aid = 17;END LOOP;END;$$;

I'm seeing similar results, although there's enough noise in the test
that I'm sure how real the would be across different tests.

At that point, the first array entry fits into the first cacheline. If we were
to move parent, firstchild, nextchild, name further down the struct, three
array entries would be on the first line. Just using the array presumably is a
very common case, so that might be worth it?

I got less clear performance results with this one, and it's also quite
possible it could hurt, if resowners aren't actually "used", just
released. Therefore it's probably not worth it for now.

You're assuming that the ResourceOwner struct begins at a cacheline
boundary. That's not usually true, we don't try to cacheline-align it.
So while it's helpful to avoid padding and to keep frequently-accessed
fields close to each other, there's no benefit in keeping them at the
beginning of the struct.

--
Heikki Linnakangas
Neon (https://neon.tech)

#67Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Peter Eisentraut (#65)
4 attachment(s)
Re: ResourceOwner refactoring

On 06/11/2023 12:43, Peter Eisentraut wrote:

It looks like this patch set needs a bit of surgery to adapt to the LLVM
changes in 9dce22033d. The cfbot is reporting compiler warnings about
this, and also some crashes, which might also be caused by this.

Updated patch set attached. I fixed those LLVM crashes, and reordered
the fields in the ResourceOwner struct per Andres' suggestion.

I do like the updated APIs. (Maybe the repeated ".DebugPrint = NULL,
/* default message is fine */" lines could be omitted?)

I like that one can now easily change the elog(WARNING) in
ResourceOwnerReleaseAll() to a PANIC or something to get automatic
verification during testing. I wonder if we should make this the
default if assertions are on? This would need some adjustments to
src/test/modules/test_resowner because it would then fail.

Yeah, perhaps, but I'll leave that to possible a follow-up patch.

I feel pretty good about this overall. Barring objections or new cfbot
failures, I will commit this in the next few days.

--
Heikki Linnakangas
Neon (https://neon.tech)

Attachments:

v17-0002-Make-resowners-more-easily-extensible.patchtext/x-patch; charset=UTF-8; name=v17-0002-Make-resowners-more-easily-extensible.patchDownload
From dc19723e126fb1f3d079c64b9e81d6a0de2c245b Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Tue, 7 Nov 2023 12:25:07 +0200
Subject: [PATCH v17 2/4] 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.

Each resource kind specifies a release priority, and
ResourceOwnerReleaseAll releases the resources in priority 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.

There were two exceptions to how the resource leak WARNINGs on commit
were printed previously: llvmjit silently released the context without
printing the warning, and a leaked buffer io triggered a PANIC. Now
everything prints a WARNING, including those cases.

Add tests in src/test/modules/test_resowner.

Reviewed-by: Aleksander Alekseev, Michael Paquier, Julien Rouhaud
Reviewed-by: Kyotaro Horiguchi, Hayato Kuroda, Álvaro Herrera, Zhihong Yu
Reviewed-by: Peter Eisentraut, Andres Freund
Discussion: https://www.postgresql.org/message-id/cbfabeb0-cd3c-e951-a572-19b365ed314d%40iki.fi
---
 src/backend/access/common/tupdesc.c           |   51 +-
 src/backend/access/transam/xact.c             |   14 +
 src/backend/jit/jit.c                         |    2 -
 src/backend/jit/llvm/llvmjit.c                |   45 +-
 src/backend/storage/buffer/bufmgr.c           |  164 +-
 src/backend/storage/buffer/localbuf.c         |   21 +-
 src/backend/storage/file/fd.c                 |   57 +-
 src/backend/storage/ipc/dsm.c                 |   53 +-
 src/backend/storage/lmgr/lock.c               |    2 +-
 src/backend/utils/cache/catcache.c            |  125 +-
 src/backend/utils/cache/plancache.c           |   50 +-
 src/backend/utils/cache/relcache.c            |   64 +-
 src/backend/utils/resowner/README             |  114 +-
 src/backend/utils/resowner/resowner.c         | 1568 ++++++-----------
 src/backend/utils/time/snapmgr.c              |   47 +-
 src/common/cryptohash_openssl.c               |   49 +-
 src/common/hmac_openssl.c                     |   47 +-
 src/include/storage/buf_internals.h           |   28 +
 src/include/storage/bufmgr.h                  |    4 +-
 src/include/utils/catcache.h                  |    3 -
 src/include/utils/plancache.h                 |    2 +
 src/include/utils/resowner.h                  |   94 +-
 src/pl/plpgsql/src/pl_exec.c                  |    2 +-
 src/pl/plpgsql/src/pl_handler.c               |    6 +-
 src/test/modules/Makefile                     |    1 +
 src/test/modules/meson.build                  |    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    |   34 +
 .../test_resowner/sql/test_resowner.sql       |   25 +
 .../test_resowner/test_resowner--1.0.sql      |   30 +
 .../test_resowner/test_resowner.control       |    4 +
 .../test_resowner/test_resowner_basic.c       |  212 +++
 .../test_resowner/test_resowner_many.c        |  296 ++++
 src/tools/pgindent/typedefs.list              |    6 +-
 36 files changed, 2303 insertions(+), 1143 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/sql/test_resowner.sql
 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 d119cfafb56..8826519e5e9 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -27,9 +27,34 @@
 #include "common/hashfn.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 char *ResOwnerPrintTupleDesc(Datum res);
+
+static const ResourceOwnerDesc tupdesc_resowner_desc =
+{
+	.name = "tupdesc reference",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_TUPDESC_REFS,
+	.ReleaseResource = ResOwnerReleaseTupleDesc,
+	.DebugPrint = ResOwnerPrintTupleDesc
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(tupdesc), &tupdesc_resowner_desc);
+}
+
+static inline void
+ResourceOwnerForgetTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(tupdesc), &tupdesc_resowner_desc);
+}
 
 /*
  * CreateTemplateTupleDesc
@@ -364,7 +389,7 @@ IncrTupleDescRefCount(TupleDesc tupdesc)
 {
 	Assert(tupdesc->tdrefcount >= 0);
 
-	ResourceOwnerEnlargeTupleDescs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	tupdesc->tdrefcount++;
 	ResourceOwnerRememberTupleDesc(CurrentResourceOwner, tupdesc);
 }
@@ -847,3 +872,25 @@ TupleDescGetDefault(TupleDesc tupdesc, AttrNumber attnum)
 
 	return result;
 }
+
+/* ResourceOwner callbacks */
+
+static void
+ResOwnerReleaseTupleDesc(Datum res)
+{
+	TupleDesc	tupdesc = (TupleDesc) DatumGetPointer(res);
+
+	/* Like DecrTupleDescRefCount, but don't call ResourceOwnerForget() */
+	Assert(tupdesc->tdrefcount > 0);
+	if (--tupdesc->tdrefcount == 0)
+		FreeTupleDesc(tupdesc);
+}
+
+static char *
+ResOwnerPrintTupleDesc(Datum res)
+{
+	TupleDesc	tupdesc = (TupleDesc) DatumGetPointer(res);
+
+	return psprintf("TupleDesc %p (%u,%d)",
+					tupdesc, tupdesc->tdtypeid, tupdesc->tdtypmod);
+}
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 5d89f828187..74ce5f9491c 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -5172,9 +5172,23 @@ 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/jit.c b/src/backend/jit/jit.c
index 4da8fee20b4..07461b99630 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 58f638859a4..2c8ac025501 100644
--- a/src/backend/jit/llvm/llvmjit.c
+++ b/src/backend/jit/llvm/llvmjit.c
@@ -45,7 +45,7 @@
 #include "portability/instr_time.h"
 #include "storage/ipc.h"
 #include "utils/memutils.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 
 #define LLVMJIT_LLVM_CONTEXT_REUSE_MAX 100
 
@@ -131,6 +131,30 @@ 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 const ResourceOwnerDesc jit_resowner_desc =
+{
+	.name = "LLVM JIT context",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_JIT_CONTEXTS,
+	.ReleaseResource = ResOwnerReleaseJitContext,
+	.DebugPrint = NULL			/* the default message is fine */
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberJIT(ResourceOwner owner, LLVMJitContext *handle)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(handle), &jit_resowner_desc);
+}
+static inline void
+ResourceOwnerForgetJIT(ResourceOwner owner, LLVMJitContext *handle)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(handle), &jit_resowner_desc);
+}
+
 PG_MODULE_MAGIC;
 
 
@@ -220,7 +244,7 @@ llvm_create_context(int jitFlags)
 
 	llvm_recreate_llvm_context();
 
-	ResourceOwnerEnlargeJIT(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	context = MemoryContextAllocZero(TopMemoryContext,
 									 sizeof(LLVMJitContext));
@@ -228,7 +252,7 @@ llvm_create_context(int jitFlags)
 
 	/* ensure cleanup */
 	context->base.resowner = CurrentResourceOwner;
-	ResourceOwnerRememberJIT(CurrentResourceOwner, PointerGetDatum(context));
+	ResourceOwnerRememberJIT(CurrentResourceOwner, context);
 
 	llvm_jit_context_in_use_count++;
 
@@ -300,6 +324,9 @@ llvm_release_context(JitContext *context)
 	llvm_jit_context->handles = NIL;
 
 	llvm_leave_fatal_on_oom();
+
+	if (context->resowner)
+		ResourceOwnerForgetJIT(context->resowner, llvm_jit_context);
 }
 
 /*
@@ -1394,3 +1421,15 @@ llvm_error_message(LLVMErrorRef error)
 }
 
 #endif							/* LLVM_VERSION_MAJOR > 11 */
+
+/*
+ * ResourceOwner callbacks
+ */
+static void
+ResOwnerReleaseJitContext(Datum res)
+{
+	JitContext *context = (JitContext *) DatumGetPointer(res);
+
+	context->resowner = NULL;
+	jit_release_context(context);
+}
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 506f71e6533..f7c67d504cd 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -47,6 +47,7 @@
 #include "postmaster/bgwriter.h"
 #include "storage/buf_internals.h"
 #include "storage/bufmgr.h"
+#include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/proc.h"
@@ -55,7 +56,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"
 
 
@@ -205,6 +206,30 @@ static PrivateRefCountEntry *GetPrivateRefCountEntry(Buffer buffer, bool do_move
 static inline int32 GetPrivateRefCount(Buffer buffer);
 static void ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref);
 
+/* ResourceOwner callbacks to hold in-progress I/Os and buffer pins */
+static void ResOwnerReleaseBufferIO(Datum res);
+static char *ResOwnerPrintBufferIO(Datum res);
+static void ResOwnerReleaseBufferPin(Datum res);
+static char *ResOwnerPrintBufferPin(Datum res);
+
+const ResourceOwnerDesc buffer_io_resowner_desc =
+{
+	.name = "buffer io",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_BUFFER_IOS,
+	.ReleaseResource = ResOwnerReleaseBufferIO,
+	.DebugPrint = ResOwnerPrintBufferIO
+};
+
+const ResourceOwnerDesc buffer_pin_resowner_desc =
+{
+	.name = "buffer pin",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_BUFFER_PINS,
+	.ReleaseResource = ResOwnerReleaseBufferPin,
+	.DebugPrint = ResOwnerPrintBufferPin
+};
+
 /*
  * Ensure that the PrivateRefCountArray has sufficient space to store one more
  * entry. This has to be called before using NewPrivateRefCountEntry() to fill
@@ -470,6 +495,7 @@ static BlockNumber ExtendBufferedRelShared(BufferManagerRelation bmr,
 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,
@@ -477,7 +503,8 @@ static int	SyncOneBuffer(int buf_id, bool skip_recently_used,
 static void WaitIO(BufferDesc *buf);
 static bool StartBufferIO(BufferDesc *buf, bool forInput);
 static void TerminateBufferIO(BufferDesc *buf, bool clear_dirty,
-							  uint32 set_flag_bits);
+							  uint32 set_flag_bits, bool forget_owner);
+static void AbortBufferIO(Buffer buffer);
 static void shared_buffer_write_error_callback(void *arg);
 static void local_buffer_write_error_callback(void *arg);
 static BufferDesc *BufferAlloc(SMgrRelation smgr,
@@ -639,7 +666,7 @@ ReadRecentBuffer(RelFileLocator rlocator, ForkNumber forkNum, BlockNumber blockN
 
 	Assert(BufferIsValid(recent_buffer));
 
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	ReservePrivateRefCountEntry();
 	InitBufferTag(&tag, &rlocator, forkNum, blockNum);
 
@@ -1173,7 +1200,7 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 	else
 	{
 		/* Set BM_VALID, terminate IO, and wake up any waiters */
-		TerminateBufferIO(bufHdr, false, BM_VALID);
+		TerminateBufferIO(bufHdr, false, BM_VALID, true);
 	}
 
 	VacuumPageMiss++;
@@ -1228,7 +1255,7 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 	uint32		victim_buf_state;
 
 	/* Make sure we will have room to remember the buffer pin */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	ReservePrivateRefCountEntry();
 
 	/* create a tag so we can lookup the buffer */
@@ -1315,9 +1342,8 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 		 * use.
 		 *
 		 * We could do this after releasing the partition lock, but then we'd
-		 * have to call ResourceOwnerEnlargeBuffers() &
-		 * ReservePrivateRefCountEntry() before acquiring the lock, for the
-		 * rare case of such a collision.
+		 * have to call ResourceOwnerEnlarge() & ReservePrivateRefCountEntry()
+		 * before acquiring the lock, for the rare case of such a collision.
 		 */
 		UnpinBuffer(victim_buf_hdr);
 
@@ -1595,7 +1621,7 @@ GetVictimBuffer(BufferAccessStrategy strategy, IOContext io_context)
 	 * entry, and a resource owner slot for the pin.
 	 */
 	ReservePrivateRefCountEntry();
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	/* we return here if a prospective victim buffer gets used concurrently */
 again:
@@ -1946,7 +1972,7 @@ ExtendBufferedRelShared(BufferManagerRelation bmr,
 		int			existing_id;
 
 		/* in case we need to pin an existing buffer below */
-		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ResourceOwnerEnlarge(CurrentResourceOwner);
 		ReservePrivateRefCountEntry();
 
 		InitBufferTag(&tag, &bmr.smgr->smgr_rlocator.locator, fork, first_block + i);
@@ -2090,7 +2116,7 @@ ExtendBufferedRelShared(BufferManagerRelation bmr,
 		if (lock)
 			LWLockAcquire(BufferDescriptorGetContentLock(buf_hdr), LW_EXCLUSIVE);
 
-		TerminateBufferIO(buf_hdr, false, BM_VALID);
+		TerminateBufferIO(buf_hdr, false, BM_VALID, true);
 	}
 
 	pgBufferUsage.shared_blks_written += extend_by;
@@ -2283,7 +2309,7 @@ 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 and ReservePrivateRefCountEntry()
+ * Note that ResourceOwnerEnlarge() and ReservePrivateRefCountEntry()
  * must have been done already.
  *
  * Returns true if buffer is BM_VALID, else false.  This provision allows
@@ -2379,7 +2405,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.
@@ -2440,6 +2466,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);
@@ -2449,9 +2484,6 @@ 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)
@@ -3122,7 +3154,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.
@@ -3252,6 +3284,7 @@ CheckForBufferLeaks(void)
 	int			RefCountErrors = 0;
 	PrivateRefCountEntry *res;
 	int			i;
+	char	   *s;
 
 	/* check the array */
 	for (i = 0; i < REFCOUNT_ARRAY_ENTRIES; i++)
@@ -3260,7 +3293,10 @@ CheckForBufferLeaks(void)
 
 		if (res->buffer != InvalidBuffer)
 		{
-			PrintBufferLeakWarning(res->buffer);
+			s = DebugPrintBufferRefcount(res->buffer);
+			elog(WARNING, "buffer refcount leak: %s", s);
+			pfree(s);
+
 			RefCountErrors++;
 		}
 	}
@@ -3273,7 +3309,9 @@ CheckForBufferLeaks(void)
 		hash_seq_init(&hstat, PrivateRefCountHash);
 		while ((res = (PrivateRefCountEntry *) hash_seq_search(&hstat)) != NULL)
 		{
-			PrintBufferLeakWarning(res->buffer);
+			s = DebugPrintBufferRefcount(res->buffer);
+			elog(WARNING, "buffer refcount leak: %s", s);
+			pfree(s);
 			RefCountErrors++;
 		}
 	}
@@ -3285,12 +3323,13 @@ CheckForBufferLeaks(void)
 /*
  * Helper routine to issue warnings when a buffer is unexpectedly pinned
  */
-void
-PrintBufferLeakWarning(Buffer buffer)
+char *
+DebugPrintBufferRefcount(Buffer buffer)
 {
 	BufferDesc *buf;
 	int32		loccount;
 	char	   *path;
+	char	   *result;
 	BackendId	backend;
 	uint32		buf_state;
 
@@ -3312,13 +3351,13 @@ PrintBufferLeakWarning(Buffer buffer)
 	path = relpathbackend(BufTagGetRelFileLocator(&buf->tag), backend,
 						  BufTagGetForkNum(&buf->tag));
 	buf_state = pg_atomic_read_u32(&buf->state);
-	elog(WARNING,
-		 "buffer refcount leak: [%03d] "
-		 "(rel=%s, blockNum=%u, flags=0x%x, refcount=%u %d)",
-		 buffer, path,
-		 buf->tag.blockNum, buf_state & BUF_FLAG_MASK,
-		 BUF_STATE_GET_REFCOUNT(buf_state), loccount);
+
+	result = psprintf("[%03d] (rel=%s, blockNum=%u, flags=0x%x, refcount=%u %d)",
+					  buffer, path,
+					  buf->tag.blockNum, buf_state & BUF_FLAG_MASK,
+					  BUF_STATE_GET_REFCOUNT(buf_state), loccount);
 	pfree(path);
+	return result;
 }
 
 /*
@@ -3522,7 +3561,7 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln, IOObject io_object,
 	 * Mark the buffer as clean (unless BM_JUST_DIRTIED has become set) and
 	 * end the BM_IO_IN_PROGRESS state.
 	 */
-	TerminateBufferIO(buf, true, 0);
+	TerminateBufferIO(buf, true, 0, true);
 
 	TRACE_POSTGRESQL_BUFFER_FLUSH_DONE(BufTagGetForkNum(&buf->tag),
 									   buf->tag.blockNum,
@@ -4182,7 +4221,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) &&
@@ -4279,7 +4318,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) &&
@@ -4489,7 +4528,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 &&
@@ -4566,7 +4605,7 @@ void
 IncrBufferRefCount(Buffer buffer)
 {
 	Assert(BufferIsPinned(buffer));
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	if (BufferIsLocal(buffer))
 		LocalRefCount[-buffer - 1]++;
 	else
@@ -5164,7 +5203,7 @@ StartBufferIO(BufferDesc *buf, bool forInput)
 {
 	uint32		buf_state;
 
-	ResourceOwnerEnlargeBufferIOs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	for (;;)
 	{
@@ -5209,9 +5248,14 @@ StartBufferIO(BufferDesc *buf, bool forInput)
  * set_flag_bits gets ORed into the buffer's flags.  It must include
  * BM_IO_ERROR in a failure case.  For successful completion it could
  * be 0, or BM_VALID if we just finished reading in the page.
+ *
+ * If forget_owner is true, we release the buffer I/O from the current
+ * resource owner. (forget_owner=false is used when the resource owner itself
+ * is being released)
  */
 static void
-TerminateBufferIO(BufferDesc *buf, bool clear_dirty, uint32 set_flag_bits)
+TerminateBufferIO(BufferDesc *buf, bool clear_dirty, uint32 set_flag_bits,
+				  bool forget_owner)
 {
 	uint32		buf_state;
 
@@ -5226,8 +5270,9 @@ TerminateBufferIO(BufferDesc *buf, bool clear_dirty, uint32 set_flag_bits)
 	buf_state |= set_flag_bits;
 	UnlockBufHdr(buf, buf_state);
 
-	ResourceOwnerForgetBufferIO(CurrentResourceOwner,
-								BufferDescriptorGetBuffer(buf));
+	if (forget_owner)
+		ResourceOwnerForgetBufferIO(CurrentResourceOwner,
+									BufferDescriptorGetBuffer(buf));
 
 	ConditionVariableBroadcast(BufferDescriptorGetIOCV(buf));
 }
@@ -5240,8 +5285,12 @@ TerminateBufferIO(BufferDesc *buf, bool clear_dirty, uint32 set_flag_bits)
  *
  *	If I/O was in progress, we always set BM_IO_ERROR, even though it's
  *	possible the error condition wasn't related to the I/O.
+ *
+ *  Note: this does not remove the buffer I/O from the resource owner.
+ *  That's correct when we're releasing the whole resource owner, but
+ *  beware if you use this in other contexts.
  */
-void
+static void
 AbortBufferIO(Buffer buffer)
 {
 	BufferDesc *buf_hdr = GetBufferDescriptor(buffer - 1);
@@ -5277,7 +5326,7 @@ AbortBufferIO(Buffer buffer)
 		}
 	}
 
-	TerminateBufferIO(buf_hdr, false, BM_IO_ERROR);
+	TerminateBufferIO(buf_hdr, false, BM_IO_ERROR, false);
 }
 
 /*
@@ -5629,3 +5678,42 @@ IssuePendingWritebacks(WritebackContext *wb_context, IOContext io_context)
 
 	wb_context->nr_pending = 0;
 }
+
+/* ResourceOwner callbacks */
+
+static void
+ResOwnerReleaseBufferIO(Datum res)
+{
+	Buffer		buffer = DatumGetInt32(res);
+
+	AbortBufferIO(buffer);
+}
+
+static char *
+ResOwnerPrintBufferIO(Datum res)
+{
+	Buffer		buffer = DatumGetInt32(res);
+
+	return psprintf("lost track of buffer IO on buffer %d", buffer);
+}
+
+static void
+ResOwnerReleaseBufferPin(Datum 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))
+		UnpinLocalBufferNoOwner(buffer);
+	else
+		UnpinBufferNoOwner(GetBufferDescriptor(buffer - 1));
+}
+
+static char *
+ResOwnerPrintBufferPin(Datum res)
+{
+	return DebugPrintBufferRefcount(DatumGetInt32(res));
+}
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index b8715b54651..4efb34b75a1 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -21,9 +21,10 @@
 #include "pgstat.h"
 #include "storage/buf_internals.h"
 #include "storage/bufmgr.h"
+#include "storage/fd.h"
 #include "utils/guc_hooks.h"
 #include "utils/memutils.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 
 
 /*#define LBDEBUG*/
@@ -130,7 +131,7 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
 	if (LocalBufHash == NULL)
 		InitLocalBuffers();
 
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	/* See if the desired buffer already exists */
 	hresult = (LocalBufferLookupEnt *)
@@ -182,7 +183,7 @@ GetLocalVictimBuffer(void)
 	uint32		buf_state;
 	BufferDesc *bufHdr;
 
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 
 	/*
 	 * Need to get a new buffer.  We use a clock sweep algorithm (essentially
@@ -674,6 +675,13 @@ PinLocalBuffer(BufferDesc *buf_hdr, bool adjust_usagecount)
 
 void
 UnpinLocalBuffer(Buffer buffer)
+{
+	UnpinLocalBufferNoOwner(buffer);
+	ResourceOwnerForgetBuffer(CurrentResourceOwner, buffer);
+}
+
+void
+UnpinLocalBufferNoOwner(Buffer buffer)
 {
 	int			buffid = -buffer - 1;
 
@@ -681,7 +689,6 @@ UnpinLocalBuffer(Buffer buffer)
 	Assert(LocalRefCount[buffid] > 0);
 	Assert(NLocalPinnedBuffers > 0);
 
-	ResourceOwnerForgetBuffer(CurrentResourceOwner, buffer);
 	if (--LocalRefCount[buffid] == 0)
 		NLocalPinnedBuffers--;
 }
@@ -785,8 +792,12 @@ CheckForLocalBufferLeaks(void)
 			if (LocalRefCount[i] != 0)
 			{
 				Buffer		b = -i - 1;
+				char	   *s;
+
+				s = DebugPrintBufferRefcount(b);
+				elog(WARNING, "local buffer refcount leak: %s", s);
+				pfree(s);
 
-				PrintBufferLeakWarning(b);
 				RefCountErrors++;
 			}
 		}
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index b884df71bf6..f691ba09321 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -99,7 +99,7 @@
 #include "storage/ipc.h"
 #include "utils/guc.h"
 #include "utils/guc_hooks.h"
-#include "utils/resowner_private.h"
+#include "utils/resowner.h"
 #include "utils/varlena.h"
 
 /* Define PG_FLUSH_DATA_WORKS if we have an implementation for pg_flush_data */
@@ -354,6 +354,31 @@ 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 char *ResOwnerPrintFile(Datum res);
+
+static const ResourceOwnerDesc file_resowner_desc =
+{
+	.name = "File",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_FILES,
+	.ReleaseResource = ResOwnerReleaseFile,
+	.DebugPrint = ResOwnerPrintFile
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberFile(ResourceOwner owner, File file)
+{
+	ResourceOwnerRemember(owner, Int32GetDatum(file), &file_resowner_desc);
+}
+static inline void
+ResourceOwnerForgetFile(ResourceOwner owner, File file)
+{
+	ResourceOwnerForget(owner, Int32GetDatum(file), &file_resowner_desc);
+}
+
 /*
  * pg_fsync --- do fsync with or without writethrough
  */
@@ -1492,7 +1517,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
@@ -1684,7 +1709,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
@@ -1816,7 +1841,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
@@ -1856,7 +1881,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);
 
@@ -3972,3 +3997,25 @@ assign_debug_io_direct(const char *newval, void *extra)
 
 	io_direct_flags = *flags;
 }
+
+/* ResourceOwner callbacks */
+
+static void
+ResOwnerReleaseFile(Datum res)
+{
+	File		file = (File) DatumGetInt32(res);
+	Vfd		   *vfdP;
+
+	Assert(FileIsValid(file));
+
+	vfdP = &VfdCache[file];
+	vfdP->resowner = NULL;
+
+	FileClose(file);
+}
+
+static char *
+ResOwnerPrintFile(Datum res)
+{
+	return psprintf("File %d", DatumGetInt32(res));
+}
diff --git a/src/backend/storage/ipc/dsm.c b/src/backend/storage/ipc/dsm.c
index 7e4e27810e8..628f3ecd3fb 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,32 @@ 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 char *ResOwnerPrintDSM(Datum res);
+
+static const ResourceOwnerDesc dsm_resowner_desc =
+{
+	.name = "dynamic shared memory segment",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_DSMS,
+	.ReleaseResource = ResOwnerReleaseDSM,
+	.DebugPrint = ResOwnerPrintDSM
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberDSM(ResourceOwner owner, dsm_segment *seg)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(seg), &dsm_resowner_desc);
+}
+static inline void
+ResourceOwnerForgetDSM(ResourceOwner owner, dsm_segment *seg)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(seg), &dsm_resowner_desc);
+}
+
 /*
  * Start up the dynamic shared memory system.
  *
@@ -907,7 +935,7 @@ void
 dsm_unpin_mapping(dsm_segment *seg)
 {
 	Assert(seg->resowner == NULL);
-	ResourceOwnerEnlargeDSMs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	seg->resowner = CurrentResourceOwner;
 	ResourceOwnerRememberDSM(seg->resowner, seg);
 }
@@ -1176,7 +1204,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);
@@ -1255,3 +1283,22 @@ is_main_region_dsm_handle(dsm_handle handle)
 {
 	return handle & 1;
 }
+
+/* ResourceOwner callbacks */
+
+static void
+ResOwnerReleaseDSM(Datum res)
+{
+	dsm_segment *seg = (dsm_segment *) DatumGetPointer(res);
+
+	seg->resowner = NULL;
+	dsm_detach(seg);
+}
+static char *
+ResOwnerPrintDSM(Datum res)
+{
+	dsm_segment *seg = (dsm_segment *) DatumGetPointer(res);
+
+	return psprintf("dynamic shared memory segment %u",
+					dsm_segment_handle(seg));
+}
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index ec6240fbaee..b8c57b3e16e 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 18db7e78e21..2e2e4d9f1f7 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"
 
 
@@ -94,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,
@@ -104,6 +107,56 @@ static void CatCacheCopyKeys(TupleDesc tupdesc, int nkeys, int *attnos,
  *					internal support functions
  */
 
+/* ResourceOwner callbacks to hold catcache references */
+
+static void ResOwnerReleaseCatCache(Datum res);
+static char *ResOwnerPrintCatCache(Datum res);
+static void ResOwnerReleaseCatCacheList(Datum res);
+static char *ResOwnerPrintCatCacheList(Datum res);
+
+static const ResourceOwnerDesc catcache_resowner_desc =
+{
+	/* catcache references */
+	.name = "catcache reference",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_CATCACHE_REFS,
+	.ReleaseResource = ResOwnerReleaseCatCache,
+	.DebugPrint = ResOwnerPrintCatCache
+};
+
+static const ResourceOwnerDesc catlistref_resowner_desc =
+{
+	/* catcache-list pins */
+	.name = "catcache list reference",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_CATCACHE_LIST_REFS,
+	.ReleaseResource = ResOwnerReleaseCatCacheList,
+	.DebugPrint = ResOwnerPrintCatCacheList
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberCatCacheRef(ResourceOwner owner, HeapTuple tuple)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(tuple), &catcache_resowner_desc);
+}
+static inline void
+ResourceOwnerForgetCatCacheRef(ResourceOwner owner, HeapTuple tuple)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(tuple), &catcache_resowner_desc);
+}
+static inline void
+ResourceOwnerRememberCatCacheListRef(ResourceOwner owner, CatCList *list)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(list), &catlistref_resowner_desc);
+}
+static inline void
+ResourceOwnerForgetCatCacheListRef(ResourceOwner owner, CatCList *list)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(list), &catlistref_resowner_desc);
+}
+
+
 /*
  * 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
@@ -1268,7 +1321,7 @@ SearchCatCacheInternal(CatCache *cache,
 		 */
 		if (!ct->negative)
 		{
-			ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner);
+			ResourceOwnerEnlarge(CurrentResourceOwner);
 			ct->refcount++;
 			ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple);
 
@@ -1369,7 +1422,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 */
@@ -1436,6 +1489,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));
@@ -1445,7 +1504,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
@@ -1581,7 +1641,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);
 
@@ -1693,7 +1753,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);
@@ -1778,12 +1838,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
@@ -2059,31 +2126,43 @@ PrepareToInvalidateCacheTuple(Relation relation,
 	}
 }
 
+/* ResourceOwner callbacks */
 
-/*
- * Subroutines for warning about reference leaks.  These are exported so
- * that resowner.c can call them.
- */
-void
-PrintCatCacheLeakWarning(HeapTuple tuple)
+static void
+ResOwnerReleaseCatCache(Datum res)
 {
+	ReleaseCatCacheWithOwner((HeapTuple) DatumGetPointer(res), NULL);
+}
+
+static char *
+ResOwnerPrintCatCache(Datum res)
+{
+	HeapTuple	tuple = (HeapTuple) DatumGetPointer(res);
 	CatCTup    *ct = (CatCTup *) (((char *) tuple) -
 								  offsetof(CatCTup, tuple));
 
 	/* Safety check to ensure we were handed a cache entry */
 	Assert(ct->ct_magic == CT_MAGIC);
 
-	elog(WARNING, "cache reference leak: cache %s (%d), tuple %u/%u has count %d",
-		 ct->my_cache->cc_relname, ct->my_cache->id,
-		 ItemPointerGetBlockNumber(&(tuple->t_self)),
-		 ItemPointerGetOffsetNumber(&(tuple->t_self)),
-		 ct->refcount);
+	return psprintf("cache %s (%d), tuple %u/%u has count %d",
+					ct->my_cache->cc_relname, ct->my_cache->id,
+					ItemPointerGetBlockNumber(&(tuple->t_self)),
+					ItemPointerGetOffsetNumber(&(tuple->t_self)),
+					ct->refcount);
 }
 
-void
-PrintCatCacheListLeakWarning(CatCList *list)
+static void
+ResOwnerReleaseCatCacheList(Datum 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);
+	ReleaseCatCacheListWithOwner((CatCList *) DatumGetPointer(res), NULL);
+}
+
+static char *
+ResOwnerPrintCatCacheList(Datum res)
+{
+	CatCList   *list = (CatCList *) DatumGetPointer(res);
+
+	return psprintf("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 7d4168f82f5..8f95520f2fb 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"
@@ -119,6 +119,31 @@ 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 const ResourceOwnerDesc planref_resowner_desc =
+{
+	.name = "plancache reference",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_PLANCACHE_REFS,
+	.ReleaseResource = ResOwnerReleaseCachedPlan,
+	.DebugPrint = NULL			/* the default message is fine */
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(plan), &planref_resowner_desc);
+}
+static inline void
+ResourceOwnerForgetPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(plan), &planref_resowner_desc);
+}
+
+
 /* GUC parameter */
 int			plan_cache_mode = PLAN_CACHE_MODE_AUTO;
 
@@ -1233,7 +1258,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);
@@ -1396,7 +1421,7 @@ CachedPlanAllowsSimpleValidityCheck(CachedPlanSource *plansource,
 	/* Bump refcount if requested. */
 	if (owner)
 	{
-		ResourceOwnerEnlargePlanCacheRefs(owner);
+		ResourceOwnerEnlarge(owner);
 		plan->refcount++;
 		ResourceOwnerRememberPlanCacheRef(owner, plan);
 	}
@@ -1457,7 +1482,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);
 	}
@@ -2203,3 +2228,20 @@ ResetPlanCache(void)
 		cexpr->is_valid = false;
 	}
 }
+
+/*
+ * Release all CachedPlans remembered by 'owner'
+ */
+void
+ReleaseAllPlanCacheRefsInOwner(ResourceOwner owner)
+{
+	ResourceOwnerReleaseAllOfKind(owner, &planref_resowner_desc);
+}
+
+/* ResourceOwner callbacks */
+
+static void
+ResOwnerReleaseCachedPlan(Datum res)
+{
+	ReleaseCachedPlan((CachedPlan *) DatumGetPointer(res), NULL);
+}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index a49ab465b36..6da02052804 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"
 
@@ -273,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);
 
@@ -2115,6 +2117,31 @@ RelationIdGetRelation(Oid relationId)
  * ----------------------------------------------------------------
  */
 
+/* ResourceOwner callbacks to track relcache references */
+static void ResOwnerReleaseRelation(Datum res);
+static char *ResOwnerPrintRelCache(Datum res);
+
+static const ResourceOwnerDesc relref_resowner_desc =
+{
+	.name = "relcache reference",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_RELCACHE_REFS,
+	.ReleaseResource = ResOwnerReleaseRelation,
+	.DebugPrint = ResOwnerPrintRelCache
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberRelationRef(ResourceOwner owner, Relation rel)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(rel), &relref_resowner_desc);
+}
+static inline void
+ResourceOwnerForgetRelationRef(ResourceOwner owner, Relation rel)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(rel), &relref_resowner_desc);
+}
+
 /*
  * RelationIncrementReferenceCount
  *		Increments relation reference count.
@@ -2126,7 +2153,7 @@ RelationIdGetRelation(Oid relationId)
 void
 RelationIncrementReferenceCount(Relation rel)
 {
-	ResourceOwnerEnlargeRelationRefs(CurrentResourceOwner);
+	ResourceOwnerEnlarge(CurrentResourceOwner);
 	rel->rd_refcnt += 1;
 	if (!IsBootstrapProcessingMode())
 		ResourceOwnerRememberRelationRef(CurrentResourceOwner, rel);
@@ -2162,6 +2189,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
@@ -6813,3 +6846,30 @@ unlink_initfile(const char *initfilename, int elevel)
 							initfilename)));
 	}
 }
+
+/*
+ * ResourceOwner callbacks
+ */
+static char *
+ResOwnerPrintRelCache(Datum res)
+{
+	Relation	rel = (Relation) DatumGetPointer(res);
+
+	return psprintf("relation \"%s\"", RelationGetRelationName(rel));
+}
+
+static void
+ResOwnerReleaseRelation(Datum 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 f94c9700df4..d67df3faedb 100644
--- a/src/backend/utils/resowner/README
+++ b/src/backend/utils/resowner/README
@@ -39,8 +39,8 @@ because transactions may initiate operations that require resources (such
 as query parsing) when no associated Portal exists yet.
 
 
-API Overview
-------------
+Usage
+-----
 
 The basic operations on a ResourceOwner are:
 
@@ -54,13 +54,6 @@ 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.
-
 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
 subtransaction or portal.  Therefore, the "release" operation on a child
@@ -79,3 +72,106 @@ 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.
+
+Adding a new resource type
+--------------------------
+
+ResourceOwner can track 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.
+
+To add a new kind of resource, define a ResourceOwnerDesc to describe it.
+For example:
+
+static const ResourceOwnerDesc myresource_desc = {
+	.name = "My fancy resource",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_FIRST,
+	.ReleaseResource = ReleaseMyResource,
+	.DebugPrint = PrintMyResource
+};
+
+ResourceOwnerRemember() and ResourceOwnerForget() functions take a pointer
+to that struct, along with a Datum to represent the resource.  The meaning
+of the Datum depends on the resource type.  Most resource types use it to
+store a pointer to some struct, but it can also be a file descriptor or
+library handle, for example.
+
+The ReleaseResource callback is called when a resource owner is released or
+deleted.  It should release any resources (e.g. close files, free memory)
+associated with the resource.  Because the callback is called during
+transaction abort, it must perform only low-level cleanup with no user
+visible effects.  The callback should not perform operations that could
+fail, like allocate memory.
+
+The optional DebugPrint callback is used in the warning at transaction
+commit, if any resources are leaked.  If not specified, a generic
+implementation that prints the resource name and the resource as a pointer
+is used.
+
+There is another 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 define a custom
+ResourceOwnerDesc struct.
+
+
+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 print a
+WARNING 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 f926f1faad3..ce1645b7273 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 the 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
@@ -20,85 +44,54 @@
  */
 #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/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;
+	const ResourceOwnerDesc *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 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 table.  Must be power of
+ * two because we 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.
- * 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 RESARRAY_MAX_ITEMS(capacity) \
-	((capacity) <= RESARRAY_MAX_ARRAY ? (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,
+				 "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
@@ -119,25 +112,48 @@ 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 bufferioarr;	/* in-progress buffer IO */
-	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 */
-
-	/* We can remember up to MAX_RESOWNER_LOCKS references to local locks. */
-	int			nlocks;			/* number of owned locks */
+	/*
+	 * When ResourceOwnerRelease is called, we sort the 'hash' and 'arr' by
+	 * the release priority.  After that, no new resources can be remembered
+	 * or forgotten in retail.  We have separate flags because
+	 * ResourceOwnerReleaseAllOfKind() temporarily sets 'releasing' without
+	 * sorting the arrays.
+	 */
+	bool		releasing;
+	bool		sorted;			/* are 'hash' and 'arr' sorted by priority? */
+
+	/*
+	 * Number of items in the locks cache, array, and hash table respectively.
+	 * (These are packed together to avoid padding in the struct.)
+	 */
+	uint8		nlocks;			/* number of owned locks */
+	uint8		narr;			/* how many items are stored in the array */
+	uint32		nhash;			/* how many items are stored in the hash */
+
+	/*
+	 * The fixed-size array for recent resources.
+	 *
+	 * If 'sorted' is set, the contents are sorted by release priority.
+	 */
+	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 'sorted' 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		capacity;		/* allocated length of hash[] */
+	uint32		grow_at;		/* grow hash when reach this */
+
+	/* The local locks cache. */
 	LOCALLOCK  *locks[MAX_RESOWNER_LOCKS];	/* list of owned locks */
-}			ResourceOwnerData;
+} ResourceOwnerData;
 
 
 /*****************************************************************************
@@ -149,6 +165,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
  */
@@ -163,253 +191,204 @@ 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, const ResourceOwnerDesc *kind);
+static void ResourceOwnerAddToHash(ResourceOwner owner, Datum value,
+								   const ResourceOwnerDesc *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);
 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														 *
  *****************************************************************************/
 
-
-/*
- * Initialize a ResourceArray
- */
-static void
-ResourceArrayInit(ResourceArray *resarr, Datum invalidval)
+static inline uint32
+hash_resource_elem(Datum value, const ResourceOwnerDesc *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 */
+	Datum		data[2];
+
+	data[0] = value;
+	data[1] = PointerGetDatum(kind);
+
+	return hash_bytes((unsigned char *) &data, 2 * SIZEOF_DATUM);
 }
 
 /*
- * 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.
+ * Adds 'value' of given 'kind' to the ResourceOwner's hash table
  */
 static void
-ResourceArrayEnlarge(ResourceArray *resarr)
+ResourceOwnerAddToHash(ResourceOwner owner, Datum value, const ResourceOwnerDesc *kind)
 {
-	uint32		i,
-				oldcap,
-				newcap;
-	Datum	   *olditemsarr;
-	Datum	   *newitemsarr;
-
-	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;
+	uint32		mask = owner->capacity - 1;
+	uint32		idx;
 
-	/* 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;
+	Assert(kind != NULL);
 
-	if (olditemsarr != NULL)
+	/* Insert into first free slot at or after hash location. */
+	idx = hash_resource_elem(value, kind) & mask;
+	for (;;)
 	{
-		/*
-		 * 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.
-		 */
-		for (i = 0; i < oldcap; i++)
-		{
-			if (olditemsarr[i] != resarr->invalidval)
-				ResourceArrayAdd(resarr, olditemsarr[i]);
-		}
-
-		/* And release old array. */
-		pfree(olditemsarr);
+		if (owner->hash[idx].kind == NULL)
+			break;				/* found a free slot */
+		idx = (idx + 1) & mask;
 	}
-
-	Assert(resarr->nitems < resarr->maxitems);
+	owner->hash[idx].item = value;
+	owner->hash[idx].kind = kind;
+	owner->nhash++;
 }
 
 /*
- * Add a resource to ResourceArray
- *
- * Caller must have previously done ResourceArrayEnlarge()
+ * Comparison function to sort by release phase and priority
  */
-static void
-ResourceArrayAdd(ResourceArray *resarr, Datum value)
+static int
+resource_priority_cmp(const void *a, const void *b)
 {
-	uint32		idx;
-
-	Assert(value != resarr->invalidval);
-	Assert(resarr->nitems < resarr->maxitems);
+	const ResourceElem *ra = (const ResourceElem *) a;
+	const ResourceElem *rb = (const ResourceElem *) b;
 
-	if (RESARRAY_IS_ARRAY(resarr))
+	/* Note: reverse order */
+	if (ra->kind->release_phase == rb->kind->release_phase)
 	{
-		/* Append to linear array. */
-		idx = resarr->nitems;
+		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
-	{
-		/* 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++;
+		return 1;
 }
 
 /*
- * Remove a resource from ResourceArray
- *
- * Returns true on success, false if resource was not found.
+ * Sort resources in reverse release priority.
  *
- * Note: if same resource ID appears more than once, one instance is removed.
+ * 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 bool
-ResourceArrayRemove(ResourceArray *resarr, Datum value)
+static void
+ResourceOwnerSort(ResourceOwner owner)
 {
-	uint32		i,
-				idx,
-				lastidx = resarr->lastidx;
-
-	Assert(value != resarr->invalidval);
+	ResourceElem *items;
+	uint32		nitems;
 
-	/* Search through all items, but try lastidx first. */
-	if (RESARRAY_IS_ARRAY(resarr))
+	if (owner->nhash == 0)
 	{
-		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++)
-		{
-			if (resarr->itemsarr[i] == value)
-			{
-				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;
-			}
-		}
+		items = owner->arr;
+		nitems = owner->narr;
 	}
 	else
 	{
-		uint32		mask = resarr->capacity - 1;
+		/*
+		 * Compact the hash table, so that all the elements are in the
+		 * beginning of the 'hash' array, with no empty elements.
+		 */
+		uint32		dst = 0;
 
-		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++)
+		for (int idx = 0; idx < owner->capacity; idx++)
 		{
-			if (resarr->itemsarr[idx] == value)
+			if (owner->hash[idx].kind != NULL)
 			{
-				resarr->itemsarr[idx] = resarr->invalidval;
-				resarr->nitems--;
-				return true;
+				if (dst != idx)
+					owner->hash[dst] = owner->hash[idx];
+				dst++;
 			}
-			idx = (idx + 1) & mask;
 		}
+
+		/*
+		 * 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.
+		 */
+		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;
 	}
 
-	return false;
+	qsort(items, nitems, sizeof(ResourceElem), resource_priority_cmp);
 }
 
 /*
- * 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.
- *
- * Returns true if we found an element, or false if the array is empty.
+ * Call the ReleaseResource callback on entries with given 'phase'.
  */
-static bool
-ResourceArrayGetAny(ResourceArray *resarr, Datum *value)
+static void
+ResourceOwnerReleaseAll(ResourceOwner owner, ResourceReleasePhase phase,
+						bool printLeakWarnings)
 {
-	if (resarr->nitems == 0)
-		return false;
+	ResourceElem *items;
+	uint32		nitems;
 
-	if (RESARRAY_IS_ARRAY(resarr))
+	/* ResourceOwnerSort must've been called already */
+	Assert(owner->releasing);
+	Assert(owner->sorted);
+	if (!owner->hash)
 	{
-		/* Linear array: just return the first element. */
-		resarr->lastidx = 0;
+		items = owner->arr;
+		nitems = owner->narr;
 	}
 	else
 	{
-		/* Hash: search forward from wherever we were last. */
-		uint32		mask = resarr->capacity - 1;
+		Assert(owner->narr == 0);
+		items = owner->hash;
+		nitems = owner->nhash;
+	}
+
+	/*
+	 * 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.
+	 */
+	while (nitems > 0)
+	{
+		uint32		idx = nitems - 1;
+		Datum		value = items[idx].item;
+		const ResourceOwnerDesc *kind = items[idx].kind;
 
-		for (;;)
+		if (kind->release_phase > phase)
+			break;
+		Assert(kind->release_phase == phase);
+
+		if (printLeakWarnings)
 		{
-			resarr->lastidx &= mask;
-			if (resarr->itemsarr[resarr->lastidx] != resarr->invalidval)
-				break;
-			resarr->lastidx++;
+			char	   *res_str;
+
+			res_str = kind->DebugPrint ?
+				kind->DebugPrint(value)
+				: psprintf("%s %p", kind->name, DatumGetPointer(value));
+			elog(WARNING, "resource was not closed: %s", res_str);
+			pfree(res_str);
 		}
+		kind->ReleaseResource(value);
+		nitems--;
 	}
-
-	*value = resarr->itemsarr[resarr->lastidx];
-	return true;
-}
-
-/*
- * Trash a ResourceArray (we don't care about its state after this)
- */
-static void
-ResourceArrayFree(ResourceArray *resarr)
-{
-	if (resarr->itemsarr)
-		pfree(resarr->itemsarr);
+	if (!owner->hash)
+		owner->narr = nitems;
+	else
+		owner->nhash = nitems;
 }
 
 
@@ -441,23 +420,205 @@ ResourceOwnerCreate(ResourceOwner parent, const char *name)
 		parent->firstchild = owner;
 	}
 
-	ResourceArrayInit(&(owner->bufferarr), BufferGetDatum(InvalidBuffer));
-	ResourceArrayInit(&(owner->bufferioarr), 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));
+#ifdef RESOWNER_TRACE
+	elog(LOG, "CREATE %d: %p %s",
+		 resowner_trace_counter++, owner, name);
+#endif
 
 	return owner;
 }
 
+/*
+ * 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.
+ *
+ * NB: Make sure there are no unrelated ResourceOwnerRemember() calls between
+ * your ResourceOwnerEnlarge() call and the ResourceOwnerRemember() call that
+ * you reserved the space for!
+ */
+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.
+	 */
+	if (owner->narr + owner->nhash >= owner->grow_at)
+	{
+		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)
+		{
+			/*
+			 * 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++)
+			{
+				if (oldhash[i].kind != NULL)
+					ResourceOwnerAddToHash(owner, oldhash[i].item, oldhash[i].kind);
+			}
+
+			/* And release old hash table. */
+			pfree(oldhash);
+		}
+	}
+
+	/* Move items from the array to the hash */
+	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);
+}
+
+/*
+ * Remember that an object is owner by a ReourceOwner
+ *
+ * Caller must have previously done ResourceOwnerEnlarge()
+ */
+void
+ResourceOwnerRemember(ResourceOwner owner, Datum value, const ResourceOwnerDesc *kind)
+{
+	uint32		idx;
+
+	/* sanity check the ResourceOwnerDesc */
+	Assert(kind->release_phase != 0);
+	Assert(kind->release_priority != 0);
+
+#ifdef RESOWNER_TRACE
+	elog(LOG, "REMEMBER %d: owner %p value " UINT64_FORMAT ", kind: %s",
+		 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);
+	Assert(!owner->sorted);
+
+	if (owner->narr >= RESOWNER_ARRAY_SIZE)
+	{
+		/* forgot to call ResourceOwnerEnlarge? */
+		elog(ERROR, "ResourceOwnerRemember called but array was full");
+	}
+
+	/* Append to the array. */
+	idx = owner->narr;
+	owner->arr[idx].item = value;
+	owner->arr[idx].kind = kind;
+	owner->narr++;
+}
+
+/*
+ * 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.
+ */
+void
+ResourceOwnerForget(ResourceOwner owner, Datum value, const ResourceOwnerDesc *kind)
+{
+#ifdef RESOWNER_TRACE
+	elog(LOG, "FORGET %d: owner %p value " UINT64_FORMAT ", kind: %s",
+		 resowner_trace_counter++, owner, DatumGetUInt64(value), kind->name);
+#endif
+
+	/*
+	 * 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);
+	Assert(!owner->sorted);
+
+	/* Search through all items in the array first. */
+	for (int i = owner->narr - 1; i >= 0; i--)
+	{
+		if (owner->arr[i].item == value &&
+			owner->arr[i].kind == kind)
+		{
+			owner->arr[i] = owner->arr[owner->narr - 1];
+			owner->narr--;
+
+#ifdef RESOWNER_STATS
+			narray_lookups++;
+#endif
+
+			return;
+		}
+	}
+
+	/* Search hash */
+	if (owner->nhash > 0)
+	{
+		uint32		mask = owner->capacity - 1;
+		uint32		idx;
+
+		idx = hash_resource_elem(value, kind) & mask;
+		for (uint32 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;
+		}
+	}
+
+	/*
+	 * 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);
+}
+
 /*
  * ResourceOwnerRelease
  *		Release all resources owned by a ResourceOwner and its descendants,
@@ -483,6 +644,12 @@ ResourceOwnerCreate(ResourceOwner parent, const char *name)
  * 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,
@@ -492,6 +659,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
@@ -504,105 +680,57 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 	ResourceOwner save;
 	ResourceReleaseCallbackItem *item;
 	ResourceReleaseCallbackItem *next;
-	Datum		foundres;
 
 	/* Recurse to handle descendants */
 	for (child = owner->firstchild; child != NULL; child = child->nextchild)
 		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.
 	 */
-	save = CurrentResourceOwner;
-	CurrentResourceOwner = owner;
-
-	if (phase == RESOURCE_RELEASE_BEFORE_LOCKS)
+	if (!owner->releasing)
+	{
+		Assert(phase == RESOURCE_RELEASE_BEFORE_LOCKS);
+		Assert(!owner->sorted);
+		owner->releasing = true;
+	}
+	else
 	{
 		/*
-		 * Abort failed buffer IO. AbortBufferIO()->TerminateBufferIO() calls
-		 * ResourceOwnerForgetBufferIO(), so we just have to iterate till
-		 * there are none.
-		 *
-		 * Needs to be before we release buffer pins.
-		 *
-		 * During a commit, there shouldn't be any in-progress IO.
+		 * 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.
 		 */
-		while (ResourceArrayGetAny(&(owner->bufferioarr), &foundres))
-		{
-			Buffer		res = DatumGetBuffer(foundres);
+	}
+	if (!owner->sorted)
+	{
+		ResourceOwnerSort(owner);
+		owner->sorted = true;
+	}
 
-			if (isCommit)
-				elog(PANIC, "lost track of buffer IO on buffer %d", res);
-			AbortBufferIO(res);
-		}
+	/*
+	 * Make CurrentResourceOwner point to me, so that the release callback
+	 * functions know which resource owner is been released.
+	 */
+	save = CurrentResourceOwner;
+	CurrentResourceOwner = 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)
 	{
@@ -655,101 +783,71 @@ 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);
+		ResourceOwnerReleaseAll(owner, phase, isCommit);
+	}
 
-			if (isCommit)
-				PrintCatCacheLeakWarning(res);
-			ReleaseCatCache(res);
-		}
+	/* Let add-on modules get a chance too */
+	for (item = ResourceRelease_callbacks; item; item = next)
+	{
+		/* allow callbacks to unregister themselves when called */
+		next = item->next;
+		item->callback(phase, isCommit, isTopLevel, item->arg);
+	}
 
-		/* Ditto for catcache lists */
-		while (ResourceArrayGetAny(&(owner->catlistrefarr), &foundres))
-		{
-			CatCList   *res = (CatCList *) DatumGetPointer(foundres);
+	CurrentResourceOwner = save;
+}
 
-			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);
+/*
+ * ResourceOwnerReleaseAllOfKind
+ *		Release all resources of a certain type held by this owner.
+ */
+void
+ResourceOwnerReleaseAllOfKind(ResourceOwner owner, const ResourceOwnerDesc *kind)
+{
+	/* 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);
+	Assert(!owner->sorted);
 
-			if (isCommit)
-				PrintTupleDescLeakWarning(res);
-			DecrTupleDescRefCount(res);
-		}
+	/*
+	 * 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;
 
-		/* Ditto for snapshot references */
-		while (ResourceArrayGetAny(&(owner->snapshotarr), &foundres))
+	/* Array first */
+	for (int i = 0; i < owner->narr; i++)
+	{
+		if (owner->arr[i].kind == kind)
 		{
-			Snapshot	res = (Snapshot) DatumGetPointer(foundres);
+			Datum		value = owner->arr[i].item;
 
-			if (isCommit)
-				PrintSnapshotLeakWarning(res);
-			UnregisterSnapshot(res);
-		}
-
-		/* Ditto for temporary files */
-		while (ResourceArrayGetAny(&(owner->filearr), &foundres))
-		{
-			File		res = DatumGetFile(foundres);
+			owner->arr[i] = owner->arr[owner->narr - 1];
+			owner->narr--;
+			i--;
 
-			if (isCommit)
-				PrintFileLeakWarning(res);
-			FileClose(res);
+			kind->ReleaseResource(value);
 		}
 	}
 
-	/* Let add-on modules get a chance too */
-	for (item = ResourceRelease_callbacks; item; item = next)
+	/* Then hash */
+	for (int i = 0; i < owner->capacity; i++)
 	{
-		/* allow callbacks to unregister themselves when called */
-		next = item->next;
-		item->callback(phase, isCommit, isTopLevel, item->arg);
-	}
-
-	CurrentResourceOwner = save;
-}
-
-/*
- * 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.
- */
-void
-ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner)
-{
-	Datum		foundres;
+		if (owner->hash[i].kind == kind)
+		{
+			Datum		value = owner->hash[i].item;
 
-	while (ResourceArrayGetAny(&(owner->planrefarr), &foundres))
-	{
-		CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres);
+			owner->hash[i].item = (Datum) 0;
+			owner->hash[i].kind = NULL;
+			owner->nhash--;
 
-		ReleaseCachedPlan(res, owner);
+			kind->ReleaseResource(value);
+		}
 	}
+	owner->releasing = false;
 }
 
 /*
@@ -765,21 +863,15 @@ ResourceOwnerDelete(ResourceOwner owner)
 	Assert(owner != CurrentResourceOwner);
 
 	/* And it better not own any resources, either */
-	Assert(owner->bufferarr.nitems == 0);
-	Assert(owner->bufferioarr.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.
@@ -795,20 +887,8 @@ ResourceOwnerDelete(ResourceOwner owner)
 	ResourceOwnerNewParent(owner, NULL);
 
 	/* And free the object. */
-	ResourceArrayFree(&(owner->bufferarr));
-	ResourceArrayFree(&(owner->bufferioarr));
-	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);
 }
 
@@ -866,11 +946,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
+ * ResourceOwnerDesc with custom callbacks.
  */
 void
 RegisterResourceReleaseCallback(ResourceReleaseCallback callback, void *arg)
@@ -946,6 +1025,9 @@ ReleaseAuxProcessResources(bool isCommit)
 	ResourceOwnerRelease(AuxProcessResourceOwner,
 						 RESOURCE_RELEASE_AFTER_LOCKS,
 						 isCommit, true);
+	/* allow it to be reused */
+	AuxProcessResourceOwner->releasing = false;
+	AuxProcessResourceOwner->sorted = false;
 }
 
 /*
@@ -960,88 +1042,12 @@ ReleaseAuxProcessResourcesCallback(int code, Datum arg)
 	ReleaseAuxProcessResources(isCommit);
 }
 
-
-/*
- * Make sure there is room for at least one more entry in a ResourceOwner's
- * buffer 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
-ResourceOwnerEnlargeBuffers(ResourceOwner owner)
-{
-	/* 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);
-}
-
-
-/*
- * Make sure there is room for at least one more entry in a ResourceOwner's
- * buffer 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
-ResourceOwnerEnlargeBufferIOs(ResourceOwner owner)
-{
-	/* We used to allow pinning buffers without a resowner, but no more */
-	Assert(owner != NULL);
-	ResourceArrayEnlarge(&(owner->bufferioarr));
-}
-
-/*
- * Remember that a buffer IO is owned by a ResourceOwner
- *
- * Caller must have previously done ResourceOwnerEnlargeBufferIOs()
- */
-void
-ResourceOwnerRememberBufferIO(ResourceOwner owner, Buffer buffer)
-{
-	ResourceArrayAdd(&(owner->bufferioarr), BufferGetDatum(buffer));
-}
-
-/*
- * Forget that a buffer IO is owned by a ResourceOwner
- */
-void
-ResourceOwnerForgetBufferIO(ResourceOwner owner, Buffer buffer)
-{
-	if (!ResourceArrayRemove(&(owner->bufferioarr), BufferGetDatum(buffer)))
-		elog(PANIC, "buffer IO %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
+ * 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,
  * ResourceOwnerForgetLock doesn't need to scan through a large array to find
  * the entry.
@@ -1087,469 +1093,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 4a3613d15fc..9198850ad25 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"
@@ -66,7 +67,7 @@
 #include "utils/builtins.h"
 #include "utils/memutils.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"
@@ -162,9 +163,34 @@ static List *exportedSnapshots = NIL;
 
 /* Prototypes for local functions */
 static Snapshot CopySnapshot(Snapshot snapshot);
+static void UnregisterSnapshotNoOwner(Snapshot snapshot);
 static void FreeSnapshot(Snapshot snapshot);
 static void SnapshotResetXmin(void);
 
+/* ResourceOwner callbacks to track snapshot references */
+static void ResOwnerReleaseSnapshot(Datum res);
+
+static const ResourceOwnerDesc snapshot_resowner_desc =
+{
+	.name = "snapshot reference",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_SNAPSHOT_REFS,
+	.ReleaseResource = ResOwnerReleaseSnapshot,
+	.DebugPrint = NULL			/* the default message is fine */
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberSnapshot(ResourceOwner owner, Snapshot snap)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(snap), &snapshot_resowner_desc);
+}
+static inline void
+ResourceOwnerForgetSnapshot(ResourceOwner owner, Snapshot snap)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(snap), &snapshot_resowner_desc);
+}
+
 /*
  * Snapshot fields to be serialized.
  *
@@ -796,7 +822,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);
 
@@ -832,11 +858,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);
@@ -1923,3 +1954,11 @@ XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot)
 
 	return false;
 }
+
+/* ResourceOwner callbacks */
+
+static void
+ResOwnerReleaseSnapshot(Datum res)
+{
+	UnregisterSnapshotNoOwner((Snapshot) DatumGetPointer(res));
+}
diff --git a/src/common/cryptohash_openssl.c b/src/common/cryptohash_openssl.c
index d9ca5a14090..241582c48d0 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,32 @@ struct pg_cryptohash_ctx
 #endif
 };
 
+/* ResourceOwner callbacks to hold cryptohash contexts */
+#ifndef FRONTEND
+static void ResOwnerReleaseCryptoHash(Datum res);
+
+static const ResourceOwnerDesc cryptohash_resowner_desc =
+{
+	.name = "OpenSSL cryptohash context",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_CRYPTOHASH_CONTEXTS,
+	.ReleaseResource = ResOwnerReleaseCryptoHash,
+	.DebugPrint = NULL			/* the default message is fine */
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberCryptoHash(ResourceOwner owner, pg_cryptohash_ctx *ctx)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(ctx), &cryptohash_resowner_desc);
+}
+static inline void
+ResourceOwnerForgetCryptoHash(ResourceOwner owner, pg_cryptohash_ctx *ctx)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(ctx), &cryptohash_resowner_desc);
+}
+#endif
+
 static const char *
 SSLerrmessage(unsigned long ecode)
 {
@@ -104,7 +129,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 +163,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 +331,8 @@ pg_cryptohash_free(pg_cryptohash_ctx *ctx)
 	EVP_MD_CTX_destroy(ctx->evpctx);
 
 #ifndef FRONTEND
-	ResourceOwnerForgetCryptoHash(ctx->resowner,
-								  PointerGetDatum(ctx));
+	if (ctx->resowner)
+		ResourceOwnerForgetCryptoHash(ctx->resowner, ctx);
 #endif
 
 	explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
@@ -351,3 +375,16 @@ 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_ctx *ctx = (pg_cryptohash_ctx *) DatumGetPointer(res);
+
+	ctx->resowner = NULL;
+	pg_cryptohash_free(ctx);
+}
+#endif
diff --git a/src/common/hmac_openssl.c b/src/common/hmac_openssl.c
index 9164f4fdfee..b12e49ba66c 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,32 @@ struct pg_hmac_ctx
 #endif
 };
 
+/* ResourceOwner callbacks to hold HMAC contexts */
+#ifndef FRONTEND
+static void ResOwnerReleaseHMAC(Datum res);
+
+static const ResourceOwnerDesc hmac_resowner_desc =
+{
+	.name = "OpenSSL HMAC context",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_HMAC_CONTEXTS,
+	.ReleaseResource = ResOwnerReleaseHMAC,
+	.DebugPrint = NULL			/* the default message is fine */
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberHMAC(ResourceOwner owner, pg_hmac_ctx *ctx)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(ctx), &hmac_resowner_desc);
+}
+static inline void
+ResourceOwnerForgetHMAC(ResourceOwner owner, pg_hmac_ctx *ctx)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(ctx), &hmac_resowner_desc);
+}
+#endif
+
 static const char *
 SSLerrmessage(unsigned long ecode)
 {
@@ -115,7 +140,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 +162,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 +328,8 @@ pg_hmac_free(pg_hmac_ctx *ctx)
 #ifdef HAVE_HMAC_CTX_FREE
 	HMAC_CTX_free(ctx->hmacctx);
 #ifndef FRONTEND
-	ResourceOwnerForgetHMAC(ctx->resowner, PointerGetDatum(ctx));
+	if (ctx->resowner)
+		ResourceOwnerForgetHMAC(ctx->resowner, ctx);
 #endif
 #else
 	explicit_bzero(ctx->hmacctx, sizeof(HMAC_CTX));
@@ -346,3 +372,16 @@ 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_ctx *ctx = (pg_hmac_ctx *) DatumGetPointer(res);
+
+	ctx->resowner = NULL;
+	pg_hmac_free(ctx);
+}
+#endif
diff --git a/src/include/storage/buf_internals.h b/src/include/storage/buf_internals.h
index bc79a329a1a..e15a86eff99 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -26,6 +26,7 @@
 #include "storage/smgr.h"
 #include "storage/spin.h"
 #include "utils/relcache.h"
+#include "utils/resowner.h"
 
 /*
  * Buffer state is a single 32-bit variable where following data is combined.
@@ -383,6 +384,32 @@ typedef struct CkptSortItem
 
 extern PGDLLIMPORT CkptSortItem *CkptBufferIds;
 
+/* ResourceOwner callbacks to hold buffer I/Os and pins */
+extern const ResourceOwnerDesc buffer_io_resowner_desc;
+extern const ResourceOwnerDesc buffer_pin_resowner_desc;
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer)
+{
+	ResourceOwnerRemember(owner, Int32GetDatum(buffer), &buffer_pin_resowner_desc);
+}
+static inline void
+ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer)
+{
+	ResourceOwnerForget(owner, Int32GetDatum(buffer), &buffer_pin_resowner_desc);
+}
+static inline void
+ResourceOwnerRememberBufferIO(ResourceOwner owner, Buffer buffer)
+{
+	ResourceOwnerRemember(owner, Int32GetDatum(buffer), &buffer_io_resowner_desc);
+}
+static inline void
+ResourceOwnerForgetBufferIO(ResourceOwner owner, Buffer buffer)
+{
+	ResourceOwnerForget(owner, Int32GetDatum(buffer), &buffer_io_resowner_desc);
+}
+
 /*
  * Internal buffer management routines
  */
@@ -418,6 +445,7 @@ extern void BufTableDelete(BufferTag *tagPtr, uint32 hashcode);
 /* localbuf.c */
 extern bool PinLocalBuffer(BufferDesc *buf_hdr, bool adjust_usagecount);
 extern void UnpinLocalBuffer(Buffer buffer);
+extern void UnpinLocalBufferNoOwner(Buffer buffer);
 extern PrefetchBufferResult PrefetchLocalBuffer(SMgrRelation smgr,
 												ForkNumber forkNum,
 												BlockNumber blockNum);
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 2eda466d44e..41e26d3e205 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -207,7 +207,7 @@ extern Buffer ExtendBufferedRelTo(BufferManagerRelation bmr,
 
 extern void InitBufferPoolAccess(void);
 extern void AtEOXact_Buffers(bool isCommit);
-extern void PrintBufferLeakWarning(Buffer buffer);
+extern char *DebugPrintBufferRefcount(Buffer buffer);
 extern void CheckPointBuffers(int flags);
 extern BlockNumber BufferGetBlockNumber(Buffer buffer);
 extern BlockNumber RelationGetNumberOfBlocksInFork(Relation relation,
@@ -248,8 +248,6 @@ extern bool ConditionalLockBufferForCleanup(Buffer buffer);
 extern bool IsBufferCleanupOK(Buffer buffer);
 extern bool HoldingBufferPinThatDelaysRecovery(void);
 
-extern void AbortBufferIO(Buffer buffer);
-
 extern bool BgBufferSync(struct WritebackContext *wb_context);
 
 /* in buf_init.c */
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 97e58157b7c..110e649fce8 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);
diff --git a/src/include/utils/resowner.h b/src/include/utils/resowner.h
index cb35e9e0904..9ce236763c3 100644
--- a/src/include/utils/resowner.h
+++ b/src/include/utils/resowner.h
@@ -37,19 +37,87 @@ extern PGDLLIMPORT ResourceOwner AuxProcessResourceOwner;
 
 /*
  * Resource releasing is done in three phases: pre-locks, locks, and
- * post-locks.  The pre-lock phase must release any resources that are
- * visible to other backends (such as pinned buffers); this ensures that
- * 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.
+ * post-locks.  The pre-lock phase must release any resources that are visible
+ * to other backends (such as pinned buffers); this ensures that 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 ResourceOwnerDesc.  The priorities of built-in
+ * resource types are given below, extensions may use any priority relative to
+ * those or RELEASE_PRIO_FIRST/LAST.  RELEASE_PRIO_FIRST is a fine choice if
+ * your resource doesn't depend on any other resources.
  */
 typedef enum
 {
-	RESOURCE_RELEASE_BEFORE_LOCKS,
+	RESOURCE_RELEASE_BEFORE_LOCKS = 1,
 	RESOURCE_RELEASE_LOCKS,
 	RESOURCE_RELEASE_AFTER_LOCKS,
 } ResourceReleasePhase;
 
+typedef uint32 ResourceReleasePriority;
+
+/* priorities of built-in BEFORE_LOCKS resources */
+#define RELEASE_PRIO_BUFFER_IOS			    100
+#define RELEASE_PRIO_BUFFER_PINS		    200
+#define RELEASE_PRIO_RELCACHE_REFS			300
+#define RELEASE_PRIO_DSMS					400
+#define RELEASE_PRIO_JIT_CONTEXTS			500
+#define RELEASE_PRIO_CRYPTOHASH_CONTEXTS	600
+#define RELEASE_PRIO_HMAC_CONTEXTS			700
+
+/* 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
+
+/* 0 is considered invalid */
+#define RELEASE_PRIO_FIRST					1
+#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
+ * ResourceOwnerDesc.
+ *
+ * Note that the callback occurs post-commit or post-abort, so these callback
+ * functions can only do noncritical cleanup.
+ */
+typedef struct ResourceOwnerDesc
+{
+	const char *name;			/* name for the object kind, for debugging */
+
+	/* when are these objects released? */
+	ResourceReleasePhase release_phase;
+	ResourceReleasePriority release_priority;
+
+	/*
+	 * Release resource.
+	 *
+	 * 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);
+
+	/*
+	 * Format a string describing the resource, for debugging purposes.  If a
+	 * resource has not been properly released before commit, this is used to
+	 * print a WARNING.
+	 *
+	 * This can be left to NULL, in which case a generic "[resource name]: %p"
+	 * format is used.
+	 */
+	char	   *(*DebugPrint) (Datum res);
+
+} ResourceOwnerDesc;
+
 /*
  *	Dynamically loaded modules can get control during ResourceOwnerRelease
  *	by providing a callback of this form.
@@ -71,16 +139,28 @@ 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, const ResourceOwnerDesc *kind);
+extern void ResourceOwnerForget(ResourceOwner owner, Datum res, const ResourceOwnerDesc *kind);
+
+extern void ResourceOwnerReleaseAllOfKind(ResourceOwner owner, const ResourceOwnerDesc *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);
+
 #endif							/* RESOWNER_H */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 4b76f7699a6..f8c7f487470 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -8470,7 +8470,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 e81873cb5ae..738b715e792 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -28,6 +28,7 @@ SUBDIRS = \
 		  test_predtest \
 		  test_rbtree \
 		  test_regex \
+		  test_resowner \
 		  test_rls_hooks \
 		  test_shm_mq \
 		  test_slru \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index fcd643f6f10..d4828dc44d5 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -25,6 +25,7 @@ subdir('test_pg_dump')
 subdir('test_predtest')
 subdir('test_rbtree')
 subdir('test_regex')
+subdir('test_resowner')
 subdir('test_rls_hooks')
 subdir('test_shm_mq')
 subdir('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..527b678fc12
--- /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 1
+NOTICE:  releasing string: child before locks priority 1
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: parent before locks priority 1
+NOTICE:  releasing string: parent before locks priority 1
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing locks
+NOTICE:  releasing resources after locks
+NOTICE:  releasing string: child after locks priority 1
+NOTICE:  releasing string: child after locks priority 1
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: parent after locks priority 1
+NOTICE:  releasing string: parent after locks priority 1
+NOTICE:  releasing string: parent after locks priority 2
+ 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 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: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+NOTICE:  releasing string: child before locks priority 2
+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 string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing string: parent before locks priority 2
+NOTICE:  releasing locks
+NOTICE:  releasing resources after locks
+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: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+NOTICE:  releasing string: child after locks priority 2
+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
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+NOTICE:  releasing string: parent after locks priority 2
+ 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:  resource was not closed: test string "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..5f669505a65
--- /dev/null
+++ b/src/test/modules/test_resowner/meson.build
@@ -0,0 +1,34 @@
+# Copyright (c) 2022-2023, PostgreSQL Global Development Group
+
+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_test_mod_args,
+)
+test_install_libs += test_resowner
+
+test_install_data += files(
+  'test_resowner.control',
+  'test_resowner--1.0.sql',
+)
+
+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/sql/test_resowner.sql b/src/test/modules/test_resowner/sql/test_resowner.sql
new file mode 100644
index 00000000000..23284b7c8b9
--- /dev/null
+++ b/src/test/modules/test_resowner/sql/test_resowner.sql
@@ -0,0 +1,25 @@
+CREATE EXTENSION test_resowner;
+
+-- This is small enough that everything fits in the small array
+SELECT test_resowner_priorities(2, 3);
+
+-- Same test with more resources, to exercise the hash table
+SELECT test_resowner_priorities(2, 32);
+
+-- 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
+);
+
+-- Test resource leak warning
+SELECT test_resowner_leak();
+
+-- 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();
+SELECT test_resowner_forget_between_phases();
+reset client_min_messages;
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..54d930828d7
--- /dev/null
+++ b/src/test/modules/test_resowner/test_resowner_basic.c
@@ -0,0 +1,212 @@
+/*--------------------------------------------------------------------------
+ *
+ * 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 char *PrintString(Datum res);
+
+/*
+ * A resource that tracks strings and prints the string when it's released.
+ * This makes the order that the resources are released visible.
+ */
+static const ResourceOwnerDesc string_desc = {
+	.name = "test resource",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_FIRST,
+	.ReleaseResource = ReleaseString,
+	.DebugPrint = PrintString
+};
+
+static void
+ReleaseString(Datum res)
+{
+	elog(NOTICE, "releasing string: %s", DatumGetPointer(res));
+}
+
+static char *
+PrintString(Datum res)
+{
+	return psprintf("test string \"%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;
+	ResourceOwnerDesc *before_desc;
+	ResourceOwnerDesc *after_desc;
+
+	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");
+
+	before_desc = palloc(nkinds * sizeof(ResourceOwnerDesc));
+	for (int i = 0; i < nkinds; i++)
+	{
+		before_desc[i].name = psprintf("test resource before locks %d", i);
+		before_desc[i].release_phase = RESOURCE_RELEASE_BEFORE_LOCKS;
+		before_desc[i].release_priority = RELEASE_PRIO_FIRST + i;
+		before_desc[i].ReleaseResource = ReleaseString;
+		before_desc[i].DebugPrint = PrintString;
+	}
+	after_desc = palloc(nkinds * sizeof(ResourceOwnerDesc));
+	for (int i = 0; i < nkinds; i++)
+	{
+		after_desc[i].name = psprintf("test resource after locks %d", i);
+		after_desc[i].release_phase = RESOURCE_RELEASE_AFTER_LOCKS;
+		after_desc[i].release_priority = RELEASE_PRIO_FIRST + i;
+		after_desc[i].ReleaseResource = ReleaseString;
+		after_desc[i].DebugPrint = PrintString;
+	}
+
+	/* Add a bunch of resources to child, with different priorities */
+	for (int i = 0; i < nresources; i++)
+	{
+		ResourceOwnerDesc *kind = &before_desc[i % nkinds];
+
+		ResourceOwnerEnlarge(child);
+		ResourceOwnerRemember(child,
+							  CStringGetDatum(psprintf("child before locks priority %d", kind->release_priority)),
+							  kind);
+	}
+	for (int i = 0; i < nresources; i++)
+	{
+		ResourceOwnerDesc *kind = &after_desc[i % nkinds];
+
+		ResourceOwnerEnlarge(child);
+		ResourceOwnerRemember(child,
+							  CStringGetDatum(psprintf("child after locks priority %d", kind->release_priority)),
+							  kind);
+	}
+
+	/* And also to the parent */
+	for (int i = 0; i < nresources; i++)
+	{
+		ResourceOwnerDesc *kind = &after_desc[i % nkinds];
+
+		ResourceOwnerEnlarge(parent);
+		ResourceOwnerRemember(parent,
+							  CStringGetDatum(psprintf("parent after locks priority %d", kind->release_priority)),
+							  kind);
+	}
+	for (int i = 0; i < nresources; i++)
+	{
+		ResourceOwnerDesc *kind = &before_desc[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_desc);
+
+	/* 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_desc);
+
+	/* 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_desc);
+
+	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_desc);
+
+	/* 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..3a50984b68c
--- /dev/null
+++ b/src/test/modules/test_resowner/test_resowner_many.c
@@ -0,0 +1,296 @@
+/*--------------------------------------------------------------------------
+ *
+ * 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
+{
+	ResourceOwnerDesc desc;
+	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 char *PrintManyTest(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->desc.name);
+	Assert(last_release_priority <= mres->kind->desc.release_priority);
+
+	dlist_delete(&mres->node);
+	mres->kind->nreleased++;
+	last_release_priority = mres->kind->desc.release_priority;
+	pfree(mres);
+}
+
+/* ResourceOwner callback */
+static char *
+PrintManyTest(Datum res)
+{
+	ManyTestResource *mres = (ManyTestResource *) DatumGetPointer(res);
+
+	/*
+	 * XXX: we assume that the DebugPrint function is called once for each
+	 * leaked resource, and that there are no other callers.
+	 */
+	mres->kind->nleaked++;
+	return psprintf("many-test resource from %s", mres->kind->desc.name);
+}
+
+static void
+InitManyTestResourceKind(ManyTestResourceKind *kind, char *name,
+						 ResourceReleasePhase phase, uint32 priority)
+{
+	kind->desc.name = name;
+	kind->desc.release_phase = phase;
+	kind->desc.release_priority = priority;
+	kind->desc.ReleaseResource = ReleaseManyTestResource;
+	kind->desc.DebugPrint = PrintManyTest;
+	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].desc);
+		kinds[kind_idx].nremembered++;
+		dlist_push_tail(&kinds[kind_idx].current_resources, &mres->node);
+
+		elog(DEBUG1, "remembered resource %p from %s", mres, mres->kind->desc.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;
+	int			ntotal;
+
+	ntotal = GetTotalResourceCount(kinds, nkinds);
+	if (ntotal < nresources)
+		elog(PANIC, "cannot free %d resources, only %d remembered", nresources, ntotal);
+
+	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].desc);
+				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,
+								 RELEASE_PRIO_FIRST + 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,
+								 RELEASE_PRIO_FIRST + 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();
+}
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 87c1aee379f..bf50a321198 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1527,6 +1527,8 @@ MVDependencies
 MVDependency
 MVNDistinct
 MVNDistinctItem
+ManyTestResource
+ManyTestResourceKind
 Material
 MaterialPath
 MaterialState
@@ -2359,8 +2361,10 @@ ReplicationStateOnDisk
 ResTarget
 ReservoirState
 ReservoirStateData
-ResourceArray
+ResourceElem
 ResourceOwner
+ResourceOwnerData
+ResourceOwnerDesc
 ResourceReleaseCallback
 ResourceReleaseCallbackItem
 ResourceReleasePhase
-- 
2.39.2

v17-0003-Use-a-faster-hash-function-in-resource-owners.patchtext/x-patch; charset=UTF-8; name=v17-0003-Use-a-faster-hash-function-in-resource-owners.patchDownload
From 91445a0436c02db4d478b801dc6d60403b33c66b Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Tue, 7 Nov 2023 12:25:11 +0200
Subject: [PATCH v17 3/4] Use a faster hash function in resource owners.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

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

Reviewed-by: Aleksander Alekseev, Michael Paquier, Julien Rouhaud
Reviewed-by: Kyotaro Horiguchi, Hayato Kuroda, Álvaro Herrera, Zhihong Yu
Reviewed-by: Peter Eisentraut, Andres Freund
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 ce1645b7273..a9a61c1c7b0 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -210,15 +210,27 @@ static void ReleaseAuxProcessResourcesCallback(int code, Datum arg);
  *	  INTERNAL ROUTINES														 *
  *****************************************************************************/
 
+/*
+ * Hash function for value+kind combination.
+ */
 static inline uint32
 hash_resource_elem(Datum value, const ResourceOwnerDesc *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.39.2

v17-0001-Move-a-few-ResourceOwnerEnlarge-calls-for-safety.patchtext/x-patch; charset=UTF-8; name=v17-0001-Move-a-few-ResourceOwnerEnlarge-calls-for-safety.patchDownload
From aecaa76279f01f8d79a89ed5ff639436059d7305 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Tue, 7 Nov 2023 12:25:03 +0200
Subject: [PATCH v17 1/4] 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
Reviewed-by: Kyotaro Horiguchi, Hayato Kuroda, Álvaro Herrera, Zhihong Yu
Reviewed-by: Peter Eisentraut, Andres Freund
Discussion: https://www.postgresql.org/message-id/cbfabeb0-cd3c-e951-a572-19b365ed314d%40iki.fi
---
 src/backend/storage/buffer/bufmgr.c   | 49 ++++++++++++---------------
 src/backend/storage/buffer/localbuf.c |  2 ++
 src/backend/utils/cache/catcache.c    |  5 +--
 3 files changed, 27 insertions(+), 29 deletions(-)

diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index dc504a1ae00..506f71e6533 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -1023,9 +1023,6 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 								 forkNum, strategy, flags);
 	}
 
-	/* Make sure we will have room to remember the buffer pin */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
-
 	TRACE_POSTGRESQL_BUFFER_READ_START(forkNum, blockNum,
 									   smgr->smgr_rlocator.locator.spcOid,
 									   smgr->smgr_rlocator.locator.dbOid,
@@ -1230,6 +1227,10 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 	BufferDesc *victim_buf_hdr;
 	uint32		victim_buf_state;
 
+	/* Make sure we will have room to remember the buffer pin */
+	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+	ReservePrivateRefCountEntry();
+
 	/* create a tag so we can lookup the buffer */
 	InitBufferTag(&newTag, &smgr->smgr_rlocator.locator, forkNum, blockNum);
 
@@ -1591,7 +1592,7 @@ GetVictimBuffer(BufferAccessStrategy strategy, IOContext io_context)
 
 	/*
 	 * Ensure, while the spinlock's not yet held, that there's a free refcount
-	 * entry.
+	 * entry, and a resource owner slot for the pin.
 	 */
 	ReservePrivateRefCountEntry();
 	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
@@ -1859,9 +1860,6 @@ ExtendBufferedRelShared(BufferManagerRelation bmr,
 		MemSet((char *) buf_block, 0, BLCKSZ);
 	}
 
-	/* in case we need to pin an existing buffer below */
-	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
-
 	/*
 	 * Lock relation against concurrent extensions, unless requested not to.
 	 *
@@ -1947,6 +1945,10 @@ ExtendBufferedRelShared(BufferManagerRelation bmr,
 		LWLock	   *partition_lock;
 		int			existing_id;
 
+		/* in case we need to pin an existing buffer below */
+		ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+		ReservePrivateRefCountEntry();
+
 		InitBufferTag(&tag, &bmr.smgr->smgr_rlocator.locator, fork, first_block + i);
 		hash = BufTableHashCode(&tag);
 		partition_lock = BufMappingPartitionLock(hash);
@@ -2281,7 +2283,8 @@ 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.
+ * Note that ResourceOwnerEnlargeBuffers and ReservePrivateRefCountEntry()
+ * 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.
@@ -2294,6 +2297,7 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
 	PrivateRefCountEntry *ref;
 
 	Assert(!BufferIsLocal(b));
+	Assert(ReservedRefCountEntry != NULL);
 
 	ref = GetPrivateRefCountEntry(b, true);
 
@@ -2302,7 +2306,6 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy)
 		uint32		buf_state;
 		uint32		old_buf_state;
 
-		ReservePrivateRefCountEntry();
 		ref = NewPrivateRefCountEntry(b);
 
 		old_buf_state = pg_atomic_read_u32(&buf->state);
@@ -2375,7 +2378,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.
@@ -2550,9 +2554,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
@@ -3029,9 +3030,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;
@@ -3113,8 +3111,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)
@@ -3124,7 +3120,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.
@@ -4169,9 +4167,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;
@@ -4185,7 +4180,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) &&
@@ -4242,9 +4239,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;
@@ -4283,7 +4277,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) &&
@@ -4478,9 +4474,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;
@@ -4494,7 +4487,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 9f20dca1212..b8715b54651 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -130,6 +130,8 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
 	if (LocalBufHash == NULL)
 		InitLocalBuffers();
 
+	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 1aacb736c22..18db7e78e21 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1605,8 +1605,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();
@@ -1694,6 +1692,9 @@ 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);
-- 
2.39.2

v17-0004-Change-pgcrypto-to-use-the-new-ResourceOwner-mec.patchtext/x-patch; charset=UTF-8; name=v17-0004-Change-pgcrypto-to-use-the-new-ResourceOwner-mec.patchDownload
From ce6bbca07257e6db8e0822ecc8f78a54152befb4 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Tue, 7 Nov 2023 12:25:15 +0200
Subject: [PATCH v17 4/4] Change pgcrypto to use the new ResourceOwner
 mechanism.

Reviewed-by: Peter Eisentraut, Andres Freund
Discussion: https://www.postgresql.org/message-id/d746cead-a1ef-7efe-fb47-933311e876a3%40iki.fi
---
 contrib/pgcrypto/openssl.c | 181 ++++++++++++++++---------------------
 1 file changed, 78 insertions(+), 103 deletions(-)

diff --git a/contrib/pgcrypto/openssl.c b/contrib/pgcrypto/openssl.c
index cf315517e0c..4a913bd04f7 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,54 +59,39 @@ 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
-free_openssl_digest(OSSLDigest *digest)
+static const ResourceOwnerDesc ossldigest_resowner_desc =
 {
-	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;
-	pfree(digest);
+	.name = "pgcrypto OpenSSL digest handle",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_FIRST,
+	.ReleaseResource = ResOwnerReleaseOSSLDigest,
+	.DebugPrint = NULL,			/* default message is fine */
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberOSSLDigest(ResourceOwner owner, OSSLDigest *digest)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(digest), &ossldigest_resowner_desc);
+}
+static inline void
+ResourceOwnerForgetOSSLDigest(ResourceOwner owner, OSSLDigest *digest)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(digest), &ossldigest_resowner_desc);
 }
 
-/*
- * Close any open OpenSSL handles on abort.
- */
 static void
-digest_free_callback(ResourceReleasePhase phase,
-					 bool isCommit,
-					 bool isTopLevel,
-					 void *arg)
+free_openssl_digest(OSSLDigest *digest)
 {
-	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);
-		}
-	}
+	EVP_MD_CTX_destroy(digest->ctx);
+	if (digest->owner != NULL)
+		ResourceOwnerForgetOSSLDigest(digest->owner, digest);
+	pfree(digest);
 }
 
 static unsigned
@@ -188,16 +172,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 +201,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 +217,17 @@ 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);
+}
+
 /*
  * Ciphers
  *
@@ -266,9 +255,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,54 +269,39 @@ 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
-free_openssl_cipher(OSSLCipher *od)
+static const ResourceOwnerDesc osslcipher_resowner_desc =
 {
-	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;
-	pfree(od);
+	.name = "pgcrypto OpenSSL cipher handle",
+	.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
+	.release_priority = RELEASE_PRIO_FIRST,
+	.ReleaseResource = ResOwnerReleaseOSSLCipher,
+	.DebugPrint = NULL,			/* default message is fine */
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberOSSLCipher(ResourceOwner owner, OSSLCipher *od)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(od), &osslcipher_resowner_desc);
+}
+static inline void
+ResourceOwnerForgetOSSLCipher(ResourceOwner owner, OSSLCipher *od)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(od), &osslcipher_resowner_desc);
 }
 
-/*
- * Close any open OpenSSL cipher handles on abort.
- */
 static void
-cipher_free_callback(ResourceReleasePhase phase,
-					 bool isCommit,
-					 bool isTopLevel,
-					 void *arg)
+free_openssl_cipher(OSSLCipher *od)
 {
-	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);
-		}
-	}
+	EVP_CIPHER_CTX_free(od->evp_ctx);
+	if (od->owner != NULL)
+		ResourceOwnerForgetOSSLCipher(od->owner, od);
+	pfree(od);
 }
 
 /* Common routines for all algorithms */
@@ -782,11 +755,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 +775,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 +794,11 @@ 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));
+}
-- 
2.39.2

#68Alexander Lakhin
exclusion@gmail.com
In reply to: Heikki Linnakangas (#67)
Re: ResourceOwner refactoring

Hello Heikki,

07.11.2023 14:28, Heikki Linnakangas wrote:

I feel pretty good about this overall. Barring objections or new cfbot failures, I will commit this in the next few days.

A script, that I published in [1]/messages/by-id/27a7998b-d67f-e32a-a28d-e659a2e390c6@gmail.com, detects several typos in the patches:
betwen
ther
ReourceOwner
ResouceOwnerSort
ReleaseOwnerRelease

Maybe it's worth to fix them now, or just accumulate and clean all such
defects once a year...

[1]: /messages/by-id/27a7998b-d67f-e32a-a28d-e659a2e390c6@gmail.com

Best regards,
Alexander

#69Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Alexander Lakhin (#68)
Re: ResourceOwner refactoring

On 07/11/2023 16:00, Alexander Lakhin wrote:

07.11.2023 14:28, Heikki Linnakangas wrote:

I feel pretty good about this overall. Barring objections or new cfbot failures, I will commit this in the next few days.

A script, that I published in [1], detects several typos in the patches:
betwen
ther
ReourceOwner
ResouceOwnerSort
ReleaseOwnerRelease

Maybe it's worth to fix them now, or just accumulate and clean all such
defects once a year...

Fixed these, and pushed. Thanks everyone for reviewing!

--
Heikki Linnakangas
Neon (https://neon.tech)

#70Alexander Lakhin
exclusion@gmail.com
In reply to: Heikki Linnakangas (#69)
Re: ResourceOwner refactoring

Hello Heikki,

08.11.2023 14:37, Heikki Linnakangas wrote:

Fixed these, and pushed. Thanks everyone for reviewing!

Please look at a new assertion failure, I've managed to trigger with this script:
CREATE TABLE t(
i01 int, i02 int, i03 int, i04 int, i05 int, i06 int, i07 int, i08 int, i09 int, i10 int,
i11 int, i12 int, i13 int, i14 int, i15 int, i16 int, i17 int, i18 int, i19 int, i20 int,
i21 int, i22 int, i23 int, i24 int, i25 int, i26 int
);
CREATE TABLE tp PARTITION OF t FOR VALUES IN (1);

(gdb) bt
...
#5  0x0000560dd4e42f77 in ExceptionalCondition (conditionName=0x560dd5059fbc "owner->narr == 0", fileName=0x560dd5059e31
"resowner.c", lineNumber=362) at assert.c:66
#6  0x0000560dd4e93cbd in ResourceOwnerReleaseAll (owner=0x560dd69cebd0, phase=RESOURCE_RELEASE_BEFORE_LOCKS,
printLeakWarnings=false) at resowner.c:362
#7  0x0000560dd4e92e22 in ResourceOwnerReleaseInternal (owner=0x560dd69cebd0, phase=RESOURCE_RELEASE_BEFORE_LOCKS,
isCommit=false, isTopLevel=true) at resowner.c:725
#8  0x0000560dd4e92d42 in ResourceOwnerReleaseInternal (owner=0x560dd69ce3f8, phase=RESOURCE_RELEASE_BEFORE_LOCKS,
isCommit=false, isTopLevel=true) at resowner.c:678
#9  0x0000560dd4e92cdb in ResourceOwnerRelease (owner=0x560dd69ce3f8, phase=RESOURCE_RELEASE_BEFORE_LOCKS,
isCommit=false, isTopLevel=true) at resowner.c:652
#10 0x0000560dd47316ef in AbortTransaction () at xact.c:2848
#11 0x0000560dd47329ac in AbortCurrentTransaction () at xact.c:3339
#12 0x0000560dd4c37284 in PostgresMain (dbname=0x560dd69779f0 "regression", username=0x560dd69779d8 "law") at
postgres.c:4370
...

Best regards,
Alexander

#71Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Alexander Lakhin (#70)
Re: ResourceOwner refactoring

On 08/11/2023 20:00, Alexander Lakhin wrote:

Please look at a new assertion failure, I've managed to trigger with this script:
CREATE TABLE t(
i01 int, i02 int, i03 int, i04 int, i05 int, i06 int, i07 int, i08 int, i09 int, i10 int,
i11 int, i12 int, i13 int, i14 int, i15 int, i16 int, i17 int, i18 int, i19 int, i20 int,
i21 int, i22 int, i23 int, i24 int, i25 int, i26 int
);
CREATE TABLE tp PARTITION OF t FOR VALUES IN (1);

(gdb) bt
...
#5  0x0000560dd4e42f77 in ExceptionalCondition (conditionName=0x560dd5059fbc "owner->narr == 0", fileName=0x560dd5059e31
"resowner.c", lineNumber=362) at assert.c:66
#6  0x0000560dd4e93cbd in ResourceOwnerReleaseAll (owner=0x560dd69cebd0, phase=RESOURCE_RELEASE_BEFORE_LOCKS,
printLeakWarnings=false) at resowner.c:362
#7  0x0000560dd4e92e22 in ResourceOwnerReleaseInternal (owner=0x560dd69cebd0, phase=RESOURCE_RELEASE_BEFORE_LOCKS,
isCommit=false, isTopLevel=true) at resowner.c:725
#8  0x0000560dd4e92d42 in ResourceOwnerReleaseInternal (owner=0x560dd69ce3f8, phase=RESOURCE_RELEASE_BEFORE_LOCKS,
isCommit=false, isTopLevel=true) at resowner.c:678
#9  0x0000560dd4e92cdb in ResourceOwnerRelease (owner=0x560dd69ce3f8, phase=RESOURCE_RELEASE_BEFORE_LOCKS,
isCommit=false, isTopLevel=true) at resowner.c:652
#10 0x0000560dd47316ef in AbortTransaction () at xact.c:2848
#11 0x0000560dd47329ac in AbortCurrentTransaction () at xact.c:3339
#12 0x0000560dd4c37284 in PostgresMain (dbname=0x560dd69779f0 "regression", username=0x560dd69779d8 "law") at
postgres.c:4370
...

Thanks for the testing! Fixed. I introduced that bug in one of the last
revisions of the patch: I changed ResourceOwnerSort() to not bother
moving all the elements from the array to the hash table if the hash
table is allocated but empty. However, ResourceOwnerReleas() didn't get
the memo, and still checked "owner->hash != NULL" rather than
"owner->nhash == 0".

--
Heikki Linnakangas
Neon (https://neon.tech)

#72Alexander Lakhin
exclusion@gmail.com
In reply to: Heikki Linnakangas (#71)
Re: ResourceOwner refactoring

Hello Heikki,

09.11.2023 02:48, Heikki Linnakangas wrote:

Thanks for the testing! Fixed. ...

Thank you for the fix!

Please look at one more failure caused be the new implementation of
ResourceOwners:
numdbs=80
for ((i=1;i<=10;i++)); do
echo "ITERATION $i"

for ((d=1;d<=$numdbs;d++)); do createdb db$d; done

for ((d=1;d<=$numdbs;d++)); do
echo "
create table t(t1 text);
drop table t;
" | psql -d db$d >psql-$d.log 2>&1 &
done
wait
grep 'PANIC' server.log  && break;

for ((d=1;d<=$numdbs;d++)); do dropdb db$d; done
grep 'PANIC' server.log  && break;
done

I could see two failure modes:
2023-11-10 08:42:28.870 UTC [1163274] ERROR:  ResourceOwnerEnlarge called after release started
2023-11-10 08:42:28.870 UTC [1163274] STATEMENT:  drop table t;
2023-11-10 08:42:28.870 UTC [1163274] WARNING:  AbortTransaction while in COMMIT state
2023-11-10 08:42:28.870 UTC [1163274] PANIC:  cannot abort transaction 906, it was already committed

2023-11-10 08:43:27.897 UTC [1164148] ERROR:  ResourceOwnerEnlarge called after release started
2023-11-10 08:43:27.897 UTC [1164148] STATEMENT:  DROP DATABASE db69;
2023-11-10 08:43:27.897 UTC [1164148] WARNING:  AbortTransaction while in COMMIT state
2023-11-10 08:43:27.897 UTC [1164148] PANIC:  cannot abort transaction 1043, it was already committed

The stack trace for the second ERROR (ResourceOwnerEnlarge called ...) is:
...
#6  0x0000558af5b2f35c in ResourceOwnerEnlarge (owner=0x558af716f3c8) at resowner.c:455
#7  0x0000558af5888f18 in dsm_create_descriptor () at dsm.c:1207
#8  0x0000558af5889205 in dsm_attach (h=3172038420) at dsm.c:697
#9  0x0000558af5b1ebed in get_segment_by_index (area=0x558af711da18, index=2) at dsa.c:1764
#10 0x0000558af5b1ea4b in dsa_get_address (area=0x558af711da18, dp=2199023329568) at dsa.c:970
#11 0x0000558af5669366 in dshash_seq_next (status=0x7ffdd5912fd0) at dshash.c:687
#12 0x0000558af5901998 in pgstat_drop_database_and_contents (dboid=16444) at pgstat_shmem.c:830
#13 0x0000558af59016f0 in pgstat_drop_entry (kind=PGSTAT_KIND_DATABASE, dboid=16444, objoid=0) at pgstat_shmem.c:888
#14 0x0000558af59044eb in AtEOXact_PgStat_DroppedStats (xact_state=0x558af7111ee0, isCommit=true) at pgstat_xact.c:88
#15 0x0000558af59043c7 in AtEOXact_PgStat (isCommit=true, parallel=false) at pgstat_xact.c:55
#16 0x0000558af53c782e in CommitTransaction () at xact.c:2371
#17 0x0000558af53c709e in CommitTransactionCommand () at xact.c:306
...

Best regards,
Alexander

#73Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Alexander Lakhin (#72)
3 attachment(s)
Re: ResourceOwner refactoring

Thanks for the testing again!

On 10/11/2023 11:00, Alexander Lakhin wrote:

I could see two failure modes:
2023-11-10 08:42:28.870 UTC [1163274] ERROR:  ResourceOwnerEnlarge called after release started
2023-11-10 08:42:28.870 UTC [1163274] STATEMENT:  drop table t;
2023-11-10 08:42:28.870 UTC [1163274] WARNING:  AbortTransaction while in COMMIT state
2023-11-10 08:42:28.870 UTC [1163274] PANIC:  cannot abort transaction 906, it was already committed

2023-11-10 08:43:27.897 UTC [1164148] ERROR:  ResourceOwnerEnlarge called after release started
2023-11-10 08:43:27.897 UTC [1164148] STATEMENT:  DROP DATABASE db69;
2023-11-10 08:43:27.897 UTC [1164148] WARNING:  AbortTransaction while in COMMIT state
2023-11-10 08:43:27.897 UTC [1164148] PANIC:  cannot abort transaction 1043, it was already committed

The stack trace for the second ERROR (ResourceOwnerEnlarge called ...) is:
...
#6  0x0000558af5b2f35c in ResourceOwnerEnlarge (owner=0x558af716f3c8) at resowner.c:455
#7  0x0000558af5888f18 in dsm_create_descriptor () at dsm.c:1207
#8  0x0000558af5889205 in dsm_attach (h=3172038420) at dsm.c:697
#9  0x0000558af5b1ebed in get_segment_by_index (area=0x558af711da18, index=2) at dsa.c:1764
#10 0x0000558af5b1ea4b in dsa_get_address (area=0x558af711da18, dp=2199023329568) at dsa.c:970
#11 0x0000558af5669366 in dshash_seq_next (status=0x7ffdd5912fd0) at dshash.c:687
#12 0x0000558af5901998 in pgstat_drop_database_and_contents (dboid=16444) at pgstat_shmem.c:830
#13 0x0000558af59016f0 in pgstat_drop_entry (kind=PGSTAT_KIND_DATABASE, dboid=16444, objoid=0) at pgstat_shmem.c:888
#14 0x0000558af59044eb in AtEOXact_PgStat_DroppedStats (xact_state=0x558af7111ee0, isCommit=true) at pgstat_xact.c:88
#15 0x0000558af59043c7 in AtEOXact_PgStat (isCommit=true, parallel=false) at pgstat_xact.c:55
#16 0x0000558af53c782e in CommitTransaction () at xact.c:2371
#17 0x0000558af53c709e in CommitTransactionCommand () at xact.c:306
...

The quick, straightforward fix is to move the "CurrentResourceOwner =
NULL" line earlier in CommitTransaction, per attached
0003-Clear-CurrentResourceOwner-earlier-in-CommitTransact.patch. You're
not allowed to use the resource owner after you start to release it; it
was a bit iffy even before the ResourceOwner rewrite but now it's
explicitly forbidden. By clearing CurrentResourceOwner as soon as we
start releasing it, we can prevent any accidental use.

When CurrentResourceOwner == NULL, dsm_attach() returns a handle that's
not associated with any ResourceOwner. That's appropriate for the pgstat
case. The DSA is "pinned", so the handle is forgotten from the
ResourceOwner right after calling dsm_attach() anyway.

Looking closer at dsa.c, I think this is a wider problem though. The
comments don't make it very clear how it's supposed to interact with
ResourceOwners. There's just this brief comment in dsa_pin_mapping():

* By default, areas are owned by the current resource owner, which means they
* are detached automatically when that scope ends.

The dsa_area struct isn't directly owned by any ResourceOwner though.
The DSM segments created by dsa_create() or dsa_attach() are.

But the functions dsa_allocate() and dsa_get_address() can create or
attach more DSM segments to the area, and they will be owned by the by
the current resource owner *at the time of the call*. So if you call
dsa_get_address() while in a different resource owner, things get very
confusing. The attached new test module demonstrates that
(0001-Add-test_dsa-module.patch), here's a shortened version:

a = dsa_create(tranche_id);

/* Switch to new resource owner */
oldowner = CurrentResourceOwner;
childowner = ResourceOwnerCreate(oldowner, "temp owner");
CurrentResourceOwner = childowner;

/* make a bunch of allocations */
for (int i = 0; i < 10000; i++)
p[i] = dsa_allocate(a, 1000);

/* Release the child resource owner */
CurrentResourceOwner = oldowner;
ResourceOwnerRelease(childowner,
RESOURCE_RELEASE_BEFORE_LOCKS,
true, false);
ResourceOwnerRelease(childowner,
RESOURCE_RELEASE_LOCKS,
true, false);
ResourceOwnerRelease(childowner,
RESOURCE_RELEASE_AFTER_LOCKS,
true, false);
ResourceOwnerDelete(childowner);

dsa_detach(a);

This first prints warnings on The ResourceOwnerRelease() calls:

2023-11-10 13:57:21.475 EET [745346] WARNING: resource was not closed:
dynamic shared memory segment 2395813396
2023-11-10 13:57:21.475 EET [745346] WARNING: resource was not closed:
dynamic shared memory segment 3922992700
2023-11-10 13:57:21.475 EET [745346] WARNING: resource was not closed:
dynamic shared memory segment 1155452762
2023-11-10 13:57:21.475 EET [745346] WARNING: resource was not closed:
dynamic shared memory segment 4045183168
2023-11-10 13:57:21.476 EET [745346] WARNING: resource was not closed:
dynamic shared memory segment 1529990480

And a segfault at the dsm_detach() call:

2023-11-10 13:57:21.480 EET [745246] LOG: server process (PID 745346)
was terminated by signal 11: Segmentation fault

#0 0x000055a5148f64ee in slist_pop_head_node (head=0x55a516d06bf8) at
../../../../src/include/lib/ilist.h:1034
#1 0x000055a5148f5eba in dsm_detach (seg=0x55a516d06bc0) at dsm.c:822
#2 0x000055a514b86db0 in dsa_detach (area=0x55a516d64dc8) at dsa.c:1939
#3 0x00007fd5dcaee5e0 in test_dsa_resowners (fcinfo=0x55a516d56a28) at
test_dsa.c:112

I think that is surprising behavior from the DSA facility. When you make
allocations with dsa_allocate() or just call dsa_get_address() on an
existing dsa_pointer, you wouldn't expect the current resource owner to
matter. I think dsa_create/attach() should store the current resource
owner in the dsa_area, for use in subsequent operations on the DSA, per
attached patch (0002-Fix-dsa.c-with-different-resource-owners.patch).

--
Heikki Linnakangas
Neon (https://neon.tech)

Attachments:

0002-Fix-dsa.c-with-different-resource-owners.patchtext/x-patch; charset=UTF-8; name=0002-Fix-dsa.c-with-different-resource-owners.patchDownload
From 39af16dbd035047572198ca6e8631a5306d4879b Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Fri, 10 Nov 2023 13:51:56 +0200
Subject: [PATCH 2/3] Fix dsa.c with different resource owners.

This fixes the bug with mixing different resource owners, demonstrated
by the test case from previous commit.

Discussion: https://www.postgresql.org/message-id/11b70743-c5f3-3910-8e5b-dd6c115ff829%40gmail.com
---
 src/backend/utils/mmgr/dsa.c | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/src/backend/utils/mmgr/dsa.c b/src/backend/utils/mmgr/dsa.c
index 8d1aace40ac..c32bb58618b 100644
--- a/src/backend/utils/mmgr/dsa.c
+++ b/src/backend/utils/mmgr/dsa.c
@@ -59,6 +59,7 @@
 #include "utils/dsa.h"
 #include "utils/freepage.h"
 #include "utils/memutils.h"
+#include "utils/resowner.h"
 
 /*
  * The size of the initial DSM segment that backs a dsa_area created by
@@ -371,6 +372,13 @@ struct dsa_area
 	/* Has the mapping been pinned? */
 	bool		mapping_pinned;
 
+	/*
+	 * All the mappings are owned by this. The dsa_area itself is not directly
+	 * tracked by the ResourceOwner, but the effect is the same.  Can be NULL
+	 * for an area that's held open for session lifetime.
+	 */
+	ResourceOwner	owner;
+
 	/*
 	 * This backend's array of segment maps, ordered by segment index
 	 * corresponding to control->segment_handles.  Some of the area's segments
@@ -1265,6 +1273,7 @@ create_internal(void *place, size_t size,
 	area = palloc(sizeof(dsa_area));
 	area->control = control;
 	area->mapping_pinned = false;
+	area->owner = CurrentResourceOwner;
 	memset(area->segment_maps, 0, sizeof(dsa_segment_map) * DSA_MAX_SEGMENTS);
 	area->high_segment_index = 0;
 	area->freed_segment_counter = 0;
@@ -1321,6 +1330,7 @@ attach_internal(void *place, dsm_segment *segment, dsa_handle handle)
 	area = palloc(sizeof(dsa_area));
 	area->control = control;
 	area->mapping_pinned = false;
+	area->owner = CurrentResourceOwner;
 	memset(&area->segment_maps[0], 0,
 		   sizeof(dsa_segment_map) * DSA_MAX_SEGMENTS);
 	area->high_segment_index = 0;
@@ -1743,6 +1753,7 @@ get_segment_by_index(dsa_area *area, dsa_segment_index index)
 		dsm_handle	handle;
 		dsm_segment *segment;
 		dsa_segment_map *segment_map;
+		ResourceOwner oldowner;
 
 		/*
 		 * If we are reached by dsa_free or dsa_get_address, there must be at
@@ -1761,7 +1772,10 @@ get_segment_by_index(dsa_area *area, dsa_segment_index index)
 			elog(ERROR,
 				 "dsa_area could not attach to a segment that has been freed");
 
+		oldowner = CurrentResourceOwner;
+		CurrentResourceOwner = area->owner;
 		segment = dsm_attach(handle);
+		CurrentResourceOwner = oldowner;
 		if (segment == NULL)
 			elog(ERROR, "dsa_area could not attach to segment");
 		if (area->mapping_pinned)
@@ -2067,6 +2081,7 @@ make_new_segment(dsa_area *area, size_t requested_pages)
 	size_t		usable_pages;
 	dsa_segment_map *segment_map;
 	dsm_segment *segment;
+	ResourceOwner oldowner;
 
 	Assert(LWLockHeldByMe(DSA_AREA_LOCK(area)));
 
@@ -2151,7 +2166,10 @@ make_new_segment(dsa_area *area, size_t requested_pages)
 	}
 
 	/* Create the segment. */
+	oldowner = CurrentResourceOwner;
+	CurrentResourceOwner = area->owner;
 	segment = dsm_create(total_size, 0);
+	CurrentResourceOwner = oldowner;
 	if (segment == NULL)
 		return NULL;
 	dsm_pin_segment(segment);
-- 
2.39.2

0001-Add-test_dsa-module.patchtext/x-patch; charset=UTF-8; name=0001-Add-test_dsa-module.patchDownload
From 09ae4ae0fef8156da0f0026aadcd67307e1dc6f6 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Fri, 10 Nov 2023 13:54:11 +0200
Subject: [PATCH 1/3] Add test_dsa module.

This covers basic calls within a single backend process, and a test
that uses a different resource owner. The resource owner test
demonstrates that calling dsa_allocate() or dsa_get_address() while in
a different resource owner than in the dsa_create/attach call causes a
segfault. I think that's a bug.

This resource owner issue was originally found by Alexander Lakhin,
exposed by my large ResourceOwner rewrite commit.

Reported-by: Alexander Lakhin
Discussion: https://www.postgresql.org/message-id/11b70743-c5f3-3910-8e5b-dd6c115ff829%40gmail.com
---
 src/test/modules/Makefile                     |   1 +
 src/test/modules/meson.build                  |   1 +
 src/test/modules/test_dsa/Makefile            |  23 ++++
 .../modules/test_dsa/expected/test_dsa.out    |  13 ++
 src/test/modules/test_dsa/meson.build         |  33 +++++
 src/test/modules/test_dsa/sql/test_dsa.sql    |   4 +
 src/test/modules/test_dsa/test_dsa--1.0.sql   |  12 ++
 src/test/modules/test_dsa/test_dsa.c          | 115 ++++++++++++++++++
 src/test/modules/test_dsa/test_dsa.control    |   4 +
 9 files changed, 206 insertions(+)
 create mode 100644 src/test/modules/test_dsa/Makefile
 create mode 100644 src/test/modules/test_dsa/expected/test_dsa.out
 create mode 100644 src/test/modules/test_dsa/meson.build
 create mode 100644 src/test/modules/test_dsa/sql/test_dsa.sql
 create mode 100644 src/test/modules/test_dsa/test_dsa--1.0.sql
 create mode 100644 src/test/modules/test_dsa/test_dsa.c
 create mode 100644 src/test/modules/test_dsa/test_dsa.control

diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 738b715e792..a18e4d28a04 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -17,6 +17,7 @@ SUBDIRS = \
 		  test_copy_callbacks \
 		  test_custom_rmgrs \
 		  test_ddl_deparse \
+		  test_dsa \
 		  test_extensions \
 		  test_ginpostinglist \
 		  test_integerset \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index d4828dc44d5..4e83c0f8d74 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -14,6 +14,7 @@ subdir('test_bloomfilter')
 subdir('test_copy_callbacks')
 subdir('test_custom_rmgrs')
 subdir('test_ddl_deparse')
+subdir('test_dsa')
 subdir('test_extensions')
 subdir('test_ginpostinglist')
 subdir('test_integerset')
diff --git a/src/test/modules/test_dsa/Makefile b/src/test/modules/test_dsa/Makefile
new file mode 100644
index 00000000000..77583464dca
--- /dev/null
+++ b/src/test/modules/test_dsa/Makefile
@@ -0,0 +1,23 @@
+# src/test/modules/test_dsa/Makefile
+
+MODULE_big = test_dsa
+OBJS = \
+	$(WIN32RES) \
+	test_dsa.o
+PGFILEDESC = "test_dsa - test code for dynamic shared memory areas"
+
+EXTENSION = test_dsa
+DATA = test_dsa--1.0.sql
+
+REGRESS = test_dsa
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_dsa
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_dsa/expected/test_dsa.out b/src/test/modules/test_dsa/expected/test_dsa.out
new file mode 100644
index 00000000000..266010e77fe
--- /dev/null
+++ b/src/test/modules/test_dsa/expected/test_dsa.out
@@ -0,0 +1,13 @@
+CREATE EXTENSION test_dsa;
+SELECT test_dsa_basic();
+ test_dsa_basic 
+----------------
+ 
+(1 row)
+
+SELECT test_dsa_resowners();
+ test_dsa_resowners 
+--------------------
+ 
+(1 row)
+
diff --git a/src/test/modules/test_dsa/meson.build b/src/test/modules/test_dsa/meson.build
new file mode 100644
index 00000000000..21738290ad5
--- /dev/null
+++ b/src/test/modules/test_dsa/meson.build
@@ -0,0 +1,33 @@
+# Copyright (c) 2022-2023, PostgreSQL Global Development Group
+
+test_dsa_sources = files(
+  'test_dsa.c',
+)
+
+if host_system == 'windows'
+  test_dsa_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_dsa',
+    '--FILEDESC', 'test_dsa - test code for dynamic shared memory areas',])
+endif
+
+test_dsa = shared_module('test_dsa',
+  test_dsa_sources,
+  kwargs: pg_test_mod_args,
+)
+test_install_libs += test_dsa
+
+test_install_data += files(
+  'test_dsa.control',
+  'test_dsa--1.0.sql',
+)
+
+tests += {
+  'name': 'test_dsa',
+  'sd': meson.current_source_dir(),
+  'bd': meson.current_build_dir(),
+  'regress': {
+    'sql': [
+      'test_dsa',
+    ],
+  },
+}
diff --git a/src/test/modules/test_dsa/sql/test_dsa.sql b/src/test/modules/test_dsa/sql/test_dsa.sql
new file mode 100644
index 00000000000..c3d8db94372
--- /dev/null
+++ b/src/test/modules/test_dsa/sql/test_dsa.sql
@@ -0,0 +1,4 @@
+CREATE EXTENSION test_dsa;
+
+SELECT test_dsa_basic();
+SELECT test_dsa_resowners();
diff --git a/src/test/modules/test_dsa/test_dsa--1.0.sql b/src/test/modules/test_dsa/test_dsa--1.0.sql
new file mode 100644
index 00000000000..2904cb23525
--- /dev/null
+++ b/src/test/modules/test_dsa/test_dsa--1.0.sql
@@ -0,0 +1,12 @@
+/* src/test/modules/test_dsa/test_dsa--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_dsa" to load this file. \quit
+
+CREATE FUNCTION test_dsa_basic()
+	RETURNS pg_catalog.void
+	AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_dsa_resowners()
+	RETURNS pg_catalog.void
+	AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_dsa/test_dsa.c b/src/test/modules/test_dsa/test_dsa.c
new file mode 100644
index 00000000000..b3753b58577
--- /dev/null
+++ b/src/test/modules/test_dsa/test_dsa.c
@@ -0,0 +1,115 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_dsa.c
+ *		Test dynamic shared memory areas (DSAs)
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		src/test/modules/test_dsa/test_dsa.c
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "utils/dsa.h"
+#include "storage/lwlock.h"
+#include "utils/resowner.h"
+
+PG_MODULE_MAGIC;
+
+/* Test basic DSA functionality */
+PG_FUNCTION_INFO_V1(test_dsa_basic);
+Datum
+test_dsa_basic(PG_FUNCTION_ARGS)
+{
+	int			tranche_id;
+	dsa_area   *a;
+	dsa_pointer p[100];
+
+	/* XXX: this tranche is leaked */
+	tranche_id = LWLockNewTrancheId();
+	LWLockRegisterTranche(tranche_id, "test_dsa");
+
+	a = dsa_create(tranche_id);
+	for (int i = 0; i < 100; i++)
+	{
+		p[i] = dsa_allocate(a, 1000);
+		snprintf(dsa_get_address(a, p[i]), 1000, "foobar%d", i);
+	}
+
+	for (int i = 0; i < 100; i++)
+	{
+		char buf[100];
+
+		snprintf(buf, 100, "foobar%d", i);
+		if (strcmp(dsa_get_address(a, p[i]), buf) != 0)
+			elog(ERROR, "no match");
+	}
+
+	for (int i = 0; i < 100; i++)
+	{
+		dsa_free(a, p[i]);
+	}
+
+	dsa_detach(a);
+
+	PG_RETURN_VOID();
+}
+
+/* Test using DSA across different resource owners */
+PG_FUNCTION_INFO_V1(test_dsa_resowners);
+Datum
+test_dsa_resowners(PG_FUNCTION_ARGS)
+{
+	int			tranche_id;
+	dsa_area   *a;
+	dsa_pointer p[10000];
+	ResourceOwner oldowner;
+	ResourceOwner childowner;
+
+	/* XXX: this tranche is leaked */
+	tranche_id = LWLockNewTrancheId();
+	LWLockRegisterTranche(tranche_id, "test_dsa");
+
+	/* Create DSA in parent resource owner */
+	a = dsa_create(tranche_id);
+
+	/* Switch to child resource owner, and do a bunch of allocations in the
+	 * DSA */
+	oldowner = CurrentResourceOwner;
+	childowner = ResourceOwnerCreate(oldowner, "test_dsa temp owner");
+	CurrentResourceOwner = childowner;
+
+	for (int i = 0; i < 10000; i++)
+	{
+		p[i] = dsa_allocate(a, 1000);
+		snprintf(dsa_get_address(a, p[i]), 1000, "foobar%d", i);
+	}
+
+	/*
+	 * Free them all. Not necessary, but demonstrates that you get a crash
+	 * even if you free all the allocations that you made while in the child
+	 * resource owner. The allocations are not "owned" by the resource owner.
+	 */
+	for (int i = 0; i < 100; i++)
+		dsa_free(a, p[i]);
+
+	/* Release the child resource owner */
+	CurrentResourceOwner = oldowner;
+	ResourceOwnerRelease(childowner,
+						 RESOURCE_RELEASE_BEFORE_LOCKS,
+						 true, false);
+	ResourceOwnerRelease(childowner,
+						 RESOURCE_RELEASE_LOCKS,
+						 true, false);
+	ResourceOwnerRelease(childowner,
+						 RESOURCE_RELEASE_AFTER_LOCKS,
+						 true, false);
+	ResourceOwnerDelete(childowner);
+
+	dsa_detach(a);
+
+	PG_RETURN_VOID();
+}
diff --git a/src/test/modules/test_dsa/test_dsa.control b/src/test/modules/test_dsa/test_dsa.control
new file mode 100644
index 00000000000..ac9674b2193
--- /dev/null
+++ b/src/test/modules/test_dsa/test_dsa.control
@@ -0,0 +1,4 @@
+comment = 'Test code for dynamic shared memory areas'
+default_version = '1.0'
+module_pathname = '$libdir/test_dsa'
+relocatable = true
-- 
2.39.2

0003-Clear-CurrentResourceOwner-earlier-in-CommitTransact.patchtext/x-patch; charset=UTF-8; name=0003-Clear-CurrentResourceOwner-earlier-in-CommitTransact.patchDownload
From f5fbb84504e336e99c1d9bc4fb38e7564d220799 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Fri, 10 Nov 2023 14:30:54 +0200
Subject: [PATCH 3/3] Clear CurrentResourceOwner earlier in CommitTransaction.

Alexander reported a crash with repeated create + drop database, after
the ResourceOwner rewrite (commit b8bff07daa). That was fixed by the
previous commit, but it nevertheless seems like a good idea clear
CurrentResourceOwner earlier, because you're not supposed to use it
for anything after we start releasing it.

Reported-by: Alexander Lakhin
Discussion: https://www.postgresql.org/message-id/11b70743-c5f3-3910-8e5b-dd6c115ff829%40gmail.com
---
 src/backend/access/transam/xact.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 74ce5f9491c..8fad8ffa1eb 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -2309,6 +2309,7 @@ CommitTransaction(void)
 	CallXactCallbacks(is_parallel_worker ? XACT_EVENT_PARALLEL_COMMIT
 					  : XACT_EVENT_COMMIT);
 
+	CurrentResourceOwner = NULL;
 	ResourceOwnerRelease(TopTransactionResourceOwner,
 						 RESOURCE_RELEASE_BEFORE_LOCKS,
 						 true, true);
@@ -2374,7 +2375,6 @@ CommitTransaction(void)
 	AtEOXact_LogicalRepWorkers(true);
 	pgstat_report_xact_timestamp(0);
 
-	CurrentResourceOwner = NULL;
 	ResourceOwnerDelete(TopTransactionResourceOwner);
 	s->curTransactionOwner = NULL;
 	CurTransactionResourceOwner = NULL;
-- 
2.39.2

#74Andres Freund
andres@anarazel.de
In reply to: Heikki Linnakangas (#73)
Re: ResourceOwner refactoring

Hi,

On 2023-11-10 16:26:51 +0200, Heikki Linnakangas wrote:

The quick, straightforward fix is to move the "CurrentResourceOwner = NULL"
line earlier in CommitTransaction, per attached
0003-Clear-CurrentResourceOwner-earlier-in-CommitTransact.patch. You're not
allowed to use the resource owner after you start to release it; it was a
bit iffy even before the ResourceOwner rewrite but now it's explicitly
forbidden. By clearing CurrentResourceOwner as soon as we start releasing
it, we can prevent any accidental use.

When CurrentResourceOwner == NULL, dsm_attach() returns a handle that's not
associated with any ResourceOwner. That's appropriate for the pgstat case.
The DSA is "pinned", so the handle is forgotten from the ResourceOwner right
after calling dsm_attach() anyway.

Yea - I wonder if we should have a version of dsm_attach() that pins at the
same time. It's pretty silly to first attach (remembering the dsm) and then
immediately pin (forgetting the dsm).

Looking closer at dsa.c, I think this is a wider problem though. The
comments don't make it very clear how it's supposed to interact with
ResourceOwners. There's just this brief comment in dsa_pin_mapping():

* By default, areas are owned by the current resource owner, which means they
* are detached automatically when that scope ends.

The dsa_area struct isn't directly owned by any ResourceOwner though. The
DSM segments created by dsa_create() or dsa_attach() are.

But there's a relationship from the dsa too, via on_dsm_detach().

But the functions dsa_allocate() and dsa_get_address() can create or attach
more DSM segments to the area, and they will be owned by the by the current
resource owner *at the time of the call*.

Yea, that doesn't seem great. It shouldn't affect the stats could though, due
the dsa being pinned.

I think that is surprising behavior from the DSA facility. When you make
allocations with dsa_allocate() or just call dsa_get_address() on an
existing dsa_pointer, you wouldn't expect the current resource owner to
matter. I think dsa_create/attach() should store the current resource owner
in the dsa_area, for use in subsequent operations on the DSA, per attached
patch (0002-Fix-dsa.c-with-different-resource-owners.patch).

Yea, that seems the right direction from here.

Greetings,

Andres Freund

#75Alexander Lakhin
exclusion@gmail.com
In reply to: Heikki Linnakangas (#73)
Re: ResourceOwner refactoring

Hello Heikki,

10.11.2023 17:26, Heikki Linnakangas wrote:

I think that is surprising behavior from the DSA facility. When you make allocations with dsa_allocate() or just call
dsa_get_address() on an existing dsa_pointer, you wouldn't expect the current resource owner to matter. I think
dsa_create/attach() should store the current resource owner in the dsa_area, for use in subsequent operations on the
DSA, per attached patch (0002-Fix-dsa.c-with-different-resource-owners.patch).

With the patch 0002 applied, I'm observing another anomaly:
CREATE TABLE t(t1 text, t2 text);
INSERT INTO t SELECT md5(g::text), '12345678901234567890' FROM generate_series(1, 100000) g;
CREATE INDEX tidx ON t(t1);

CREATE FUNCTION f() RETURNS TABLE (t1 text, t2 text) AS 'BEGIN END' LANGUAGE plpgsql;

SELECT * FROM f();

gives me under Valgrind:
2023-11-11 11:54:18.964 UTC|law|regression|654f5d2e.3c7a92|LOG: statement: SELECT * FROM f();
==00:00:00:49.589 3963538== Invalid read of size 1
==00:00:00:49.589 3963538==    at 0x9C8785: ResourceOwnerEnlarge (resowner.c:454)
==00:00:00:49.589 3963538==    by 0x7507C4: dsm_create_descriptor (dsm.c:1207)
==00:00:00:49.589 3963538==    by 0x74EF71: dsm_create (dsm.c:538)
==00:00:00:49.589 3963538==    by 0x9B7BEA: make_new_segment (dsa.c:2171)
==00:00:00:49.589 3963538==    by 0x9B6E28: ensure_active_superblock (dsa.c:1696)
==00:00:00:49.589 3963538==    by 0x9B67DD: alloc_object (dsa.c:1487)
==00:00:00:49.589 3963538==    by 0x9B5064: dsa_allocate_extended (dsa.c:816)
==00:00:00:49.589 3963538==    by 0x978A4C: share_tupledesc (typcache.c:2742)
==00:00:00:49.589 3963538==    by 0x978C07: find_or_make_matching_shared_tupledesc (typcache.c:2796)
==00:00:00:49.589 3963538==    by 0x977652: assign_record_type_typmod (typcache.c:1995)
==00:00:00:49.589 3963538==    by 0x9885A2: internal_get_result_type (funcapi.c:462)
==00:00:00:49.589 3963538==    by 0x98800D: get_expr_result_type (funcapi.c:299)
==00:00:00:49.589 3963538==  Address 0x72f9bf0 is 2,096 bytes inside a recently re-allocated block of size 16,384 alloc'd
==00:00:00:49.589 3963538==    at 0x4848899: malloc (vg_replace_malloc.c:381)
==00:00:00:49.589 3963538==    by 0x9B1F94: AllocSetAlloc (aset.c:928)
==00:00:00:49.589 3963538==    by 0x9C1162: MemoryContextAllocZero (mcxt.c:1076)
==00:00:00:49.589 3963538==    by 0x9C872C: ResourceOwnerCreate (resowner.c:423)
==00:00:00:49.589 3963538==    by 0x2CC522: AtStart_ResourceOwner (xact.c:1211)
==00:00:00:49.589 3963538==    by 0x2CD52A: StartTransaction (xact.c:2084)
==00:00:00:49.589 3963538==    by 0x2CE442: StartTransactionCommand (xact.c:2948)
==00:00:00:49.589 3963538==    by 0x992D69: InitPostgres (postinit.c:860)
==00:00:00:49.589 3963538==    by 0x791E6E: PostgresMain (postgres.c:4209)
==00:00:00:49.589 3963538==    by 0x6B13C4: BackendRun (postmaster.c:4423)
==00:00:00:49.589 3963538==    by 0x6B09EB: BackendStartup (postmaster.c:4108)
==00:00:00:49.589 3963538==    by 0x6AD0A6: ServerLoop (postmaster.c:1767)

without Valgrind I get:
2023-11-11 11:08:38.511 UTC|law|regression|654f60b6.3ca7eb|ERROR: ResourceOwnerEnlarge called after release started
2023-11-11 11:08:38.511 UTC|law|regression|654f60b6.3ca7eb|STATEMENT:  SELECT * FROM f();

Best regards,
Alexander

#76Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Alexander Lakhin (#75)
3 attachment(s)
Re: ResourceOwner refactoring

On 11/11/2023 14:00, Alexander Lakhin wrote:

10.11.2023 17:26, Heikki Linnakangas wrote:

I think that is surprising behavior from the DSA facility. When you make allocations with dsa_allocate() or just call
dsa_get_address() on an existing dsa_pointer, you wouldn't expect the current resource owner to matter. I think
dsa_create/attach() should store the current resource owner in the dsa_area, for use in subsequent operations on the
DSA, per attached patch (0002-Fix-dsa.c-with-different-resource-owners.patch).

With the patch 0002 applied, I'm observing another anomaly:

Ok, so the ownership of a dsa was still muddled :-(. Attached is a new
version that goes a little further. It replaces the whole
'mapping_pinned' flag in dsa_area with the 'resowner'. When a mapping is
pinned, it means that 'resowner == NULL'. This is now similar to how the
resowner field and pinning works in dsm.c.

--
Heikki Linnakangas
Neon (https://neon.tech)

Attachments:

0001-Add-test_dsa-module.patchtext/x-patch; charset=UTF-8; name=0001-Add-test_dsa-module.patchDownload
From 90a0f1c8204f01c423b60be032ea521e4afd7473 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Fri, 10 Nov 2023 13:54:11 +0200
Subject: [PATCH 1/3] Add test_dsa module.

This covers basic calls within a single backend process, and a test
that uses a different resource owner. The resource owner test
demonstrates that calling dsa_allocate() or dsa_get_address() while in
a different resource owner than in the dsa_create/attach call causes a
segfault. I think that's a bug.

This resource owner issue was originally found by Alexander Lakhin,
exposed by my large ResourceOwner rewrite commit.

Reported-by: Alexander Lakhin
Discussion: https://www.postgresql.org/message-id/11b70743-c5f3-3910-8e5b-dd6c115ff829%40gmail.com
---
 src/test/modules/Makefile                     |   1 +
 src/test/modules/meson.build                  |   1 +
 src/test/modules/test_dsa/Makefile            |  23 ++++
 .../modules/test_dsa/expected/test_dsa.out    |  13 ++
 src/test/modules/test_dsa/meson.build         |  33 +++++
 src/test/modules/test_dsa/sql/test_dsa.sql    |   4 +
 src/test/modules/test_dsa/test_dsa--1.0.sql   |  12 ++
 src/test/modules/test_dsa/test_dsa.c          | 115 ++++++++++++++++++
 src/test/modules/test_dsa/test_dsa.control    |   4 +
 9 files changed, 206 insertions(+)
 create mode 100644 src/test/modules/test_dsa/Makefile
 create mode 100644 src/test/modules/test_dsa/expected/test_dsa.out
 create mode 100644 src/test/modules/test_dsa/meson.build
 create mode 100644 src/test/modules/test_dsa/sql/test_dsa.sql
 create mode 100644 src/test/modules/test_dsa/test_dsa--1.0.sql
 create mode 100644 src/test/modules/test_dsa/test_dsa.c
 create mode 100644 src/test/modules/test_dsa/test_dsa.control

diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 738b715e792..a18e4d28a04 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -17,6 +17,7 @@ SUBDIRS = \
 		  test_copy_callbacks \
 		  test_custom_rmgrs \
 		  test_ddl_deparse \
+		  test_dsa \
 		  test_extensions \
 		  test_ginpostinglist \
 		  test_integerset \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index d4828dc44d5..4e83c0f8d74 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -14,6 +14,7 @@ subdir('test_bloomfilter')
 subdir('test_copy_callbacks')
 subdir('test_custom_rmgrs')
 subdir('test_ddl_deparse')
+subdir('test_dsa')
 subdir('test_extensions')
 subdir('test_ginpostinglist')
 subdir('test_integerset')
diff --git a/src/test/modules/test_dsa/Makefile b/src/test/modules/test_dsa/Makefile
new file mode 100644
index 00000000000..77583464dca
--- /dev/null
+++ b/src/test/modules/test_dsa/Makefile
@@ -0,0 +1,23 @@
+# src/test/modules/test_dsa/Makefile
+
+MODULE_big = test_dsa
+OBJS = \
+	$(WIN32RES) \
+	test_dsa.o
+PGFILEDESC = "test_dsa - test code for dynamic shared memory areas"
+
+EXTENSION = test_dsa
+DATA = test_dsa--1.0.sql
+
+REGRESS = test_dsa
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_dsa
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_dsa/expected/test_dsa.out b/src/test/modules/test_dsa/expected/test_dsa.out
new file mode 100644
index 00000000000..266010e77fe
--- /dev/null
+++ b/src/test/modules/test_dsa/expected/test_dsa.out
@@ -0,0 +1,13 @@
+CREATE EXTENSION test_dsa;
+SELECT test_dsa_basic();
+ test_dsa_basic 
+----------------
+ 
+(1 row)
+
+SELECT test_dsa_resowners();
+ test_dsa_resowners 
+--------------------
+ 
+(1 row)
+
diff --git a/src/test/modules/test_dsa/meson.build b/src/test/modules/test_dsa/meson.build
new file mode 100644
index 00000000000..21738290ad5
--- /dev/null
+++ b/src/test/modules/test_dsa/meson.build
@@ -0,0 +1,33 @@
+# Copyright (c) 2022-2023, PostgreSQL Global Development Group
+
+test_dsa_sources = files(
+  'test_dsa.c',
+)
+
+if host_system == 'windows'
+  test_dsa_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_dsa',
+    '--FILEDESC', 'test_dsa - test code for dynamic shared memory areas',])
+endif
+
+test_dsa = shared_module('test_dsa',
+  test_dsa_sources,
+  kwargs: pg_test_mod_args,
+)
+test_install_libs += test_dsa
+
+test_install_data += files(
+  'test_dsa.control',
+  'test_dsa--1.0.sql',
+)
+
+tests += {
+  'name': 'test_dsa',
+  'sd': meson.current_source_dir(),
+  'bd': meson.current_build_dir(),
+  'regress': {
+    'sql': [
+      'test_dsa',
+    ],
+  },
+}
diff --git a/src/test/modules/test_dsa/sql/test_dsa.sql b/src/test/modules/test_dsa/sql/test_dsa.sql
new file mode 100644
index 00000000000..c3d8db94372
--- /dev/null
+++ b/src/test/modules/test_dsa/sql/test_dsa.sql
@@ -0,0 +1,4 @@
+CREATE EXTENSION test_dsa;
+
+SELECT test_dsa_basic();
+SELECT test_dsa_resowners();
diff --git a/src/test/modules/test_dsa/test_dsa--1.0.sql b/src/test/modules/test_dsa/test_dsa--1.0.sql
new file mode 100644
index 00000000000..2904cb23525
--- /dev/null
+++ b/src/test/modules/test_dsa/test_dsa--1.0.sql
@@ -0,0 +1,12 @@
+/* src/test/modules/test_dsa/test_dsa--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_dsa" to load this file. \quit
+
+CREATE FUNCTION test_dsa_basic()
+	RETURNS pg_catalog.void
+	AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_dsa_resowners()
+	RETURNS pg_catalog.void
+	AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_dsa/test_dsa.c b/src/test/modules/test_dsa/test_dsa.c
new file mode 100644
index 00000000000..b3753b58577
--- /dev/null
+++ b/src/test/modules/test_dsa/test_dsa.c
@@ -0,0 +1,115 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_dsa.c
+ *		Test dynamic shared memory areas (DSAs)
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		src/test/modules/test_dsa/test_dsa.c
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "utils/dsa.h"
+#include "storage/lwlock.h"
+#include "utils/resowner.h"
+
+PG_MODULE_MAGIC;
+
+/* Test basic DSA functionality */
+PG_FUNCTION_INFO_V1(test_dsa_basic);
+Datum
+test_dsa_basic(PG_FUNCTION_ARGS)
+{
+	int			tranche_id;
+	dsa_area   *a;
+	dsa_pointer p[100];
+
+	/* XXX: this tranche is leaked */
+	tranche_id = LWLockNewTrancheId();
+	LWLockRegisterTranche(tranche_id, "test_dsa");
+
+	a = dsa_create(tranche_id);
+	for (int i = 0; i < 100; i++)
+	{
+		p[i] = dsa_allocate(a, 1000);
+		snprintf(dsa_get_address(a, p[i]), 1000, "foobar%d", i);
+	}
+
+	for (int i = 0; i < 100; i++)
+	{
+		char buf[100];
+
+		snprintf(buf, 100, "foobar%d", i);
+		if (strcmp(dsa_get_address(a, p[i]), buf) != 0)
+			elog(ERROR, "no match");
+	}
+
+	for (int i = 0; i < 100; i++)
+	{
+		dsa_free(a, p[i]);
+	}
+
+	dsa_detach(a);
+
+	PG_RETURN_VOID();
+}
+
+/* Test using DSA across different resource owners */
+PG_FUNCTION_INFO_V1(test_dsa_resowners);
+Datum
+test_dsa_resowners(PG_FUNCTION_ARGS)
+{
+	int			tranche_id;
+	dsa_area   *a;
+	dsa_pointer p[10000];
+	ResourceOwner oldowner;
+	ResourceOwner childowner;
+
+	/* XXX: this tranche is leaked */
+	tranche_id = LWLockNewTrancheId();
+	LWLockRegisterTranche(tranche_id, "test_dsa");
+
+	/* Create DSA in parent resource owner */
+	a = dsa_create(tranche_id);
+
+	/* Switch to child resource owner, and do a bunch of allocations in the
+	 * DSA */
+	oldowner = CurrentResourceOwner;
+	childowner = ResourceOwnerCreate(oldowner, "test_dsa temp owner");
+	CurrentResourceOwner = childowner;
+
+	for (int i = 0; i < 10000; i++)
+	{
+		p[i] = dsa_allocate(a, 1000);
+		snprintf(dsa_get_address(a, p[i]), 1000, "foobar%d", i);
+	}
+
+	/*
+	 * Free them all. Not necessary, but demonstrates that you get a crash
+	 * even if you free all the allocations that you made while in the child
+	 * resource owner. The allocations are not "owned" by the resource owner.
+	 */
+	for (int i = 0; i < 100; i++)
+		dsa_free(a, p[i]);
+
+	/* Release the child resource owner */
+	CurrentResourceOwner = oldowner;
+	ResourceOwnerRelease(childowner,
+						 RESOURCE_RELEASE_BEFORE_LOCKS,
+						 true, false);
+	ResourceOwnerRelease(childowner,
+						 RESOURCE_RELEASE_LOCKS,
+						 true, false);
+	ResourceOwnerRelease(childowner,
+						 RESOURCE_RELEASE_AFTER_LOCKS,
+						 true, false);
+	ResourceOwnerDelete(childowner);
+
+	dsa_detach(a);
+
+	PG_RETURN_VOID();
+}
diff --git a/src/test/modules/test_dsa/test_dsa.control b/src/test/modules/test_dsa/test_dsa.control
new file mode 100644
index 00000000000..ac9674b2193
--- /dev/null
+++ b/src/test/modules/test_dsa/test_dsa.control
@@ -0,0 +1,4 @@
+comment = 'Test code for dynamic shared memory areas'
+default_version = '1.0'
+module_pathname = '$libdir/test_dsa'
+relocatable = true
-- 
2.39.2

0002-Fix-dsa.c-with-different-resource-owners.patchtext/x-patch; charset=UTF-8; name=0002-Fix-dsa.c-with-different-resource-owners.patchDownload
From 8ef5c0f197f0a095a19b4642ad94c56422e40d40 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Sun, 12 Nov 2023 23:10:28 +0100
Subject: [PATCH 2/3] Fix dsa.c with different resource owners.

The comments in dsa.c suggested that areas were owned by resource
owners, but it was not in fact tracked explicitly. The DSM attachments
held by the dsa were owned by resource owners, but not the area
itself. That led to confusion if you used one resource owner to attach
or create the area, but then switched to a different resource owner
when allocating or even just accessing the allocations in the area
with dsa_get_address(). The additional DSM segments associated with
the area would get owned by a different resource owner than the
initial segment. To fix, add an explicit 'resowner' field to dsa_area.
It replaces the 'mapping_pinned' flag; resowner == NULL now indicates
that the mapping is pinned.

This fixes the bug demonstrated by the test case from previous commit.

Reviewed-by: Alexander Lakhin <exclusion@gmail.com>
Discussion: https://www.postgresql.org/message-id/11b70743-c5f3-3910-8e5b-dd6c115ff829%40gmail.com
---
 src/backend/utils/mmgr/dsa.c | 38 ++++++++++++++++++++++++------------
 1 file changed, 25 insertions(+), 13 deletions(-)

diff --git a/src/backend/utils/mmgr/dsa.c b/src/backend/utils/mmgr/dsa.c
index 8d1aace40ac..71cf11b1894 100644
--- a/src/backend/utils/mmgr/dsa.c
+++ b/src/backend/utils/mmgr/dsa.c
@@ -59,6 +59,7 @@
 #include "utils/dsa.h"
 #include "utils/freepage.h"
 #include "utils/memutils.h"
+#include "utils/resowner.h"
 
 /*
  * The size of the initial DSM segment that backs a dsa_area created by
@@ -368,8 +369,13 @@ struct dsa_area
 	/* Pointer to the control object in shared memory. */
 	dsa_area_control *control;
 
-	/* Has the mapping been pinned? */
-	bool		mapping_pinned;
+	/*
+	 * All the mappings are owned by this.  The dsa_area itself is not
+	 * directly tracked by the ResourceOwner, but the effect is the same.
+	 * NULL if the attachment has session lifespan, i.e if dsa_pin_mapping()
+	 * has been called.
+	 */
+	ResourceOwner	resowner;
 
 	/*
 	 * This backend's array of segment maps, ordered by segment index
@@ -645,12 +651,14 @@ dsa_pin_mapping(dsa_area *area)
 {
 	int			i;
 
-	Assert(!area->mapping_pinned);
-	area->mapping_pinned = true;
+	if (area->resowner != NULL)
+	{
+		area->resowner = NULL;
 
-	for (i = 0; i <= area->high_segment_index; ++i)
-		if (area->segment_maps[i].segment != NULL)
-			dsm_pin_mapping(area->segment_maps[i].segment);
+		for (i = 0; i <= area->high_segment_index; ++i)
+			if (area->segment_maps[i].segment != NULL)
+				dsm_pin_mapping(area->segment_maps[i].segment);
+	}
 }
 
 /*
@@ -1264,7 +1272,7 @@ create_internal(void *place, size_t size,
 	 */
 	area = palloc(sizeof(dsa_area));
 	area->control = control;
-	area->mapping_pinned = false;
+	area->resowner = CurrentResourceOwner;
 	memset(area->segment_maps, 0, sizeof(dsa_segment_map) * DSA_MAX_SEGMENTS);
 	area->high_segment_index = 0;
 	area->freed_segment_counter = 0;
@@ -1320,7 +1328,7 @@ attach_internal(void *place, dsm_segment *segment, dsa_handle handle)
 	/* Build the backend-local area object. */
 	area = palloc(sizeof(dsa_area));
 	area->control = control;
-	area->mapping_pinned = false;
+	area->resowner = CurrentResourceOwner;
 	memset(&area->segment_maps[0], 0,
 		   sizeof(dsa_segment_map) * DSA_MAX_SEGMENTS);
 	area->high_segment_index = 0;
@@ -1743,6 +1751,7 @@ get_segment_by_index(dsa_area *area, dsa_segment_index index)
 		dsm_handle	handle;
 		dsm_segment *segment;
 		dsa_segment_map *segment_map;
+		ResourceOwner oldowner;
 
 		/*
 		 * If we are reached by dsa_free or dsa_get_address, there must be at
@@ -1761,11 +1770,12 @@ get_segment_by_index(dsa_area *area, dsa_segment_index index)
 			elog(ERROR,
 				 "dsa_area could not attach to a segment that has been freed");
 
+		oldowner = CurrentResourceOwner;
+		CurrentResourceOwner = area->resowner;
 		segment = dsm_attach(handle);
+		CurrentResourceOwner = oldowner;
 		if (segment == NULL)
 			elog(ERROR, "dsa_area could not attach to segment");
-		if (area->mapping_pinned)
-			dsm_pin_mapping(segment);
 		segment_map = &area->segment_maps[index];
 		segment_map->segment = segment;
 		segment_map->mapped_address = dsm_segment_address(segment);
@@ -2067,6 +2077,7 @@ make_new_segment(dsa_area *area, size_t requested_pages)
 	size_t		usable_pages;
 	dsa_segment_map *segment_map;
 	dsm_segment *segment;
+	ResourceOwner oldowner;
 
 	Assert(LWLockHeldByMe(DSA_AREA_LOCK(area)));
 
@@ -2151,12 +2162,13 @@ make_new_segment(dsa_area *area, size_t requested_pages)
 	}
 
 	/* Create the segment. */
+	oldowner = CurrentResourceOwner;
+	CurrentResourceOwner = area->resowner;
 	segment = dsm_create(total_size, 0);
+	CurrentResourceOwner = oldowner;
 	if (segment == NULL)
 		return NULL;
 	dsm_pin_segment(segment);
-	if (area->mapping_pinned)
-		dsm_pin_mapping(segment);
 
 	/* Store the handle in shared memory to be found by index. */
 	area->control->segment_handles[new_index] =
-- 
2.39.2

0003-Clear-CurrentResourceOwner-earlier-in-CommitTransact.patchtext/x-patch; charset=UTF-8; name=0003-Clear-CurrentResourceOwner-earlier-in-CommitTransact.patchDownload
From 64536b950d003292658be77a16d09662ae440eab Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Sun, 12 Nov 2023 23:11:14 +0100
Subject: [PATCH 3/3] Clear CurrentResourceOwner earlier in CommitTransaction.

Alexander reported a crash with repeated create + drop database, after
the ResourceOwner rewrite (commit b8bff07daa). That was fixed by the
previous commit, but it nevertheless seems like a good idea clear
CurrentResourceOwner earlier, because you're not supposed to use it
for anything after we start releasing it.

Reported-by: Alexander Lakhin
Discussion: https://www.postgresql.org/message-id/11b70743-c5f3-3910-8e5b-dd6c115ff829%40gmail.com
---
 src/backend/access/transam/xact.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 74ce5f9491c..8fad8ffa1eb 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -2309,6 +2309,7 @@ CommitTransaction(void)
 	CallXactCallbacks(is_parallel_worker ? XACT_EVENT_PARALLEL_COMMIT
 					  : XACT_EVENT_COMMIT);
 
+	CurrentResourceOwner = NULL;
 	ResourceOwnerRelease(TopTransactionResourceOwner,
 						 RESOURCE_RELEASE_BEFORE_LOCKS,
 						 true, true);
@@ -2374,7 +2375,6 @@ CommitTransaction(void)
 	AtEOXact_LogicalRepWorkers(true);
 	pgstat_report_xact_timestamp(0);
 
-	CurrentResourceOwner = NULL;
 	ResourceOwnerDelete(TopTransactionResourceOwner);
 	s->curTransactionOwner = NULL;
 	CurTransactionResourceOwner = NULL;
-- 
2.39.2

#77Thomas Munro
thomas.munro@gmail.com
In reply to: Heikki Linnakangas (#76)
Re: ResourceOwner refactoring

On Mon, Nov 13, 2023 at 11:16 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

On 11/11/2023 14:00, Alexander Lakhin wrote:

10.11.2023 17:26, Heikki Linnakangas wrote:

I think that is surprising behavior from the DSA facility. When you make allocations with dsa_allocate() or just call
dsa_get_address() on an existing dsa_pointer, you wouldn't expect the current resource owner to matter. I think
dsa_create/attach() should store the current resource owner in the dsa_area, for use in subsequent operations on the
DSA, per attached patch (0002-Fix-dsa.c-with-different-resource-owners.patch).

Yeah, I agree that it is surprising and dangerous that the DSM
segments can have different owners if you're not careful, and it's not
clear that you have to be. Interesting that no one ever reported
breakage in parallel query due to this thinko, presumably because the
current resource owner always turns out to be either the same one or
something with longer life.

With the patch 0002 applied, I'm observing another anomaly:

Ok, so the ownership of a dsa was still muddled :-(. Attached is a new
version that goes a little further. It replaces the whole
'mapping_pinned' flag in dsa_area with the 'resowner'. When a mapping is
pinned, it means that 'resowner == NULL'. This is now similar to how the
resowner field and pinning works in dsm.c.

This patch makes sense to me.

It might be tempting to delete the dsa_pin_mapping() interface
completely and say that if CurrentResourceOwner == NULL, then it's
effectively (what we used to call) pinned, but I think it's useful for
exception-safe construction of multiple objects to be able to start
out with a resource owner and then later 'commit' by clearing it. As
seen in GetSessionDsmHandle().

I don't love the way this stuff is not very explicit, and if we're
going to keep doing this 'dynamic scope' stuff then I wish we could
find a scoping notation that looks more like the stuff one sees in
other languages that say something like
"with-resource-owner(area->resowner) { block of code }".

#78Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Thomas Munro (#77)
Re: ResourceOwner refactoring

On 13/11/2023 01:08, Thomas Munro wrote:

On Mon, Nov 13, 2023 at 11:16 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

On 11/11/2023 14:00, Alexander Lakhin wrote:

10.11.2023 17:26, Heikki Linnakangas wrote:

I think that is surprising behavior from the DSA facility. When you make allocations with dsa_allocate() or just call
dsa_get_address() on an existing dsa_pointer, you wouldn't expect the current resource owner to matter. I think
dsa_create/attach() should store the current resource owner in the dsa_area, for use in subsequent operations on the
DSA, per attached patch (0002-Fix-dsa.c-with-different-resource-owners.patch).

Yeah, I agree that it is surprising and dangerous that the DSM
segments can have different owners if you're not careful, and it's not
clear that you have to be. Interesting that no one ever reported
breakage in parallel query due to this thinko, presumably because the
current resource owner always turns out to be either the same one or
something with longer life.

Yep. I ran check-world with an extra assertion that
"CurrentResourceOwner == area->resowner || area->resowner == NULL" to
verify that. No failures.

With the patch 0002 applied, I'm observing another anomaly:

Ok, so the ownership of a dsa was still muddled :-(. Attached is a new
version that goes a little further. It replaces the whole
'mapping_pinned' flag in dsa_area with the 'resowner'. When a mapping is
pinned, it means that 'resowner == NULL'. This is now similar to how the
resowner field and pinning works in dsm.c.

This patch makes sense to me.

It might be tempting to delete the dsa_pin_mapping() interface
completely and say that if CurrentResourceOwner == NULL, then it's
effectively (what we used to call) pinned, but I think it's useful for
exception-safe construction of multiple objects to be able to start
out with a resource owner and then later 'commit' by clearing it. As
seen in GetSessionDsmHandle().

Yeah that's useful. I don't like the "pinned mapping" term here. It's
confusing because we also have dsa_pin(), which means something
different: the area will continue to exist even after all backends have
detached from it. I think we should rename 'dsa_pin_mapping()' to
'dsa_forget_resowner()' or something like that.

I pushed these fixes, without that renaming. Let's do that as a separate
patch if we like that.

I didn't backpatch this for now, because we don't seem to have a live
bug in back branches. You could argue for backpatching because this
could bite us in the future if we backpatch something else that uses
dsa's, or if there are extensions using it. We can do that later after
we've had this in 'master' for a while.

--
Heikki Linnakangas
Neon (https://neon.tech)

#79Andres Freund
andres@anarazel.de
In reply to: Heikki Linnakangas (#67)
Re: ResourceOwner refactoring

Hi,

On 2023-11-07 13:28:28 +0200, Heikki Linnakangas wrote:

I feel pretty good about this overall. Barring objections or new cfbot
failures, I will commit this in the next few days.

I am working on rebasing the AIO patch over this. I think I found a crash
that's unrelated to AIO.

#4 0x0000000000ea7631 in ExceptionalCondition (conditionName=0x4ba6f1 "owner->narr == 0",
fileName=0x4ba628 "../../../../../home/andres/src/postgresql/src/backend/utils/resowner/resowner.c", lineNumber=354)
at ../../../../../home/andres/src/postgresql/src/backend/utils/error/assert.c:66
#5 0x0000000000ef13b5 in ResourceOwnerReleaseAll (owner=0x3367d48, phase=RESOURCE_RELEASE_BEFORE_LOCKS, printLeakWarnings=false)
at ../../../../../home/andres/src/postgresql/src/backend/utils/resowner/resowner.c:354
#6 0x0000000000ef1d9c in ResourceOwnerReleaseInternal (owner=0x3367d48, phase=RESOURCE_RELEASE_BEFORE_LOCKS, isCommit=false, isTopLevel=true)
at ../../../../../home/andres/src/postgresql/src/backend/utils/resowner/resowner.c:717
#7 0x0000000000ef1c89 in ResourceOwnerRelease (owner=0x3367d48, phase=RESOURCE_RELEASE_BEFORE_LOCKS, isCommit=false, isTopLevel=true)
at ../../../../../home/andres/src/postgresql/src/backend/utils/resowner/resowner.c:644
#8 0x00000000008c1f87 in AbortTransaction () at ../../../../../home/andres/src/postgresql/src/backend/access/transam/xact.c:2851
#9 0x00000000008c4ae0 in AbortOutOfAnyTransaction () at ../../../../../home/andres/src/postgresql/src/backend/access/transam/xact.c:4761
#10 0x0000000000ec1502 in ShutdownPostgres (code=1, arg=0) at ../../../../../home/andres/src/postgresql/src/backend/utils/init/postinit.c:1357
#11 0x0000000000c942e5 in shmem_exit (code=1) at ../../../../../home/andres/src/postgresql/src/backend/storage/ipc/ipc.c:243

I think the problem is that ResourceOwnerSort() looks at owner->nhash == 0,
whereas ResourceOwnerReleaseAll() looks at !owner->hash. Therefore it's
possible to reach ResourceOwnerReleaseAll() with owner->hash != NULL while
also having owner->narr > 0.

I think both checks for !owner->hash in ResourceOwnerReleaseAll() need to
instead check owner->nhash == 0.

I'm somewhat surprised that this is only hit with the AIO branch. I guess one
needs to be somewhat unlucky to end up with a hashtable but 0 elements in it
at the time of resowner release. But still somewhat surprising.

Seperately, I see that resowner_private.h still exists in the repo, I think
that should be deleted, as many of the functions don't exist anymore?

Lastly, I think it'd be good to assert that we're not releasing the resowner
in ResourceOwnerRememberLock(). It's currently not strictly required, but that
seems like it's just leaking an implementation detail out?

Greetings,

Andres Freund

#80Andres Freund
andres@anarazel.de
In reply to: Andres Freund (#79)
Re: ResourceOwner refactoring

Hi,

On 2023-11-17 12:44:41 -0800, Andres Freund wrote:

On 2023-11-07 13:28:28 +0200, Heikki Linnakangas wrote:

I feel pretty good about this overall. Barring objections or new cfbot
failures, I will commit this in the next few days.

I am working on rebasing the AIO patch over this. I think I found a crash
that's unrelated to AIO.

#4 0x0000000000ea7631 in ExceptionalCondition (conditionName=0x4ba6f1 "owner->narr == 0",
fileName=0x4ba628 "../../../../../home/andres/src/postgresql/src/backend/utils/resowner/resowner.c", lineNumber=354)
at ../../../../../home/andres/src/postgresql/src/backend/utils/error/assert.c:66
#5 0x0000000000ef13b5 in ResourceOwnerReleaseAll (owner=0x3367d48, phase=RESOURCE_RELEASE_BEFORE_LOCKS, printLeakWarnings=false)
at ../../../../../home/andres/src/postgresql/src/backend/utils/resowner/resowner.c:354
#6 0x0000000000ef1d9c in ResourceOwnerReleaseInternal (owner=0x3367d48, phase=RESOURCE_RELEASE_BEFORE_LOCKS, isCommit=false, isTopLevel=true)
at ../../../../../home/andres/src/postgresql/src/backend/utils/resowner/resowner.c:717
#7 0x0000000000ef1c89 in ResourceOwnerRelease (owner=0x3367d48, phase=RESOURCE_RELEASE_BEFORE_LOCKS, isCommit=false, isTopLevel=true)
at ../../../../../home/andres/src/postgresql/src/backend/utils/resowner/resowner.c:644
#8 0x00000000008c1f87 in AbortTransaction () at ../../../../../home/andres/src/postgresql/src/backend/access/transam/xact.c:2851
#9 0x00000000008c4ae0 in AbortOutOfAnyTransaction () at ../../../../../home/andres/src/postgresql/src/backend/access/transam/xact.c:4761
#10 0x0000000000ec1502 in ShutdownPostgres (code=1, arg=0) at ../../../../../home/andres/src/postgresql/src/backend/utils/init/postinit.c:1357
#11 0x0000000000c942e5 in shmem_exit (code=1) at ../../../../../home/andres/src/postgresql/src/backend/storage/ipc/ipc.c:243

I think the problem is that ResourceOwnerSort() looks at owner->nhash == 0,
whereas ResourceOwnerReleaseAll() looks at !owner->hash. Therefore it's
possible to reach ResourceOwnerReleaseAll() with owner->hash != NULL while
also having owner->narr > 0.

I think both checks for !owner->hash in ResourceOwnerReleaseAll() need to
instead check owner->nhash == 0.

I'm somewhat surprised that this is only hit with the AIO branch. I guess one
needs to be somewhat unlucky to end up with a hashtable but 0 elements in it
at the time of resowner release. But still somewhat surprising.

Oops - I hadn't rebased far enough at this point... That was already fixed in
8f4a1ab471e.

Seems like it'd be good to cover that path in one of the tests?

- Andres

#81Anton A. Melnikov
a.melnikov@postgrespro.ru
In reply to: Andres Freund (#80)
Re: ResourceOwner refactoring

Hello!

Found possibly missing PGDLLIMPORT in the definition of the

extern const ResourceOwnerDesc buffer_io_resowner_desc;
and
extern const ResourceOwnerDesc buffer_pin_resowner_desc;

callbacks in the src/include/storage/buf_internals.h

Please take a look on it.

With the best regards,

--
Anton A. Melnikov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#82Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Anton A. Melnikov (#81)
Re: ResourceOwner refactoring

On 16/01/2024 13:23, Anton A. Melnikov wrote:

Hello!

Found possibly missing PGDLLIMPORT in the definition of the

extern const ResourceOwnerDesc buffer_io_resowner_desc;
and
extern const ResourceOwnerDesc buffer_pin_resowner_desc;

callbacks in the src/include/storage/buf_internals.h

Please take a look on it.

Fixed, thanks. mark_pgdllimport.pl also highlighted two new variables in
walsummarizer.h, fixed those too.

--
Heikki Linnakangas
Neon (https://neon.tech)

#83Anton A. Melnikov
a.melnikov@postgrespro.ru
In reply to: Heikki Linnakangas (#82)
Re: ResourceOwner refactoring

On 16.01.2024 14:54, Heikki Linnakangas wrote:

Fixed, thanks. mark_pgdllimport.pl also highlighted two new variables in walsummarizer.h, fixed those too.

Thanks!

Have a nice day!

--
Anton A. Melnikov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#84Alexander Lakhin
exclusion@gmail.com
In reply to: Heikki Linnakangas (#69)
Re: ResourceOwner refactoring

Hello Heikki,

08.11.2023 14:37, Heikki Linnakangas wrote:

Fixed these, and pushed. Thanks everyone for reviewing!

Please try the following script:
mkdir /tmp/50m
sudo mount -t tmpfs -o size=50M tmpfs /tmp/50m
export PGDATA=/tmp/50m/tmpdb

initdb
pg_ctl -l server.log start

cat << 'EOF' | psql
CREATE TEMP TABLE t (a name, b name, c name, d name);
INSERT INTO t SELECT 'a', 'b', 'c', 'd' FROM generate_series(1, 1000) g;

COPY t TO '/tmp/t.data';
SELECT 'COPY t FROM ''/tmp/t.data''' FROM generate_series(1, 100)
\gexec
EOF

which produces an unexpected error, a warning, and an assertion failure,
starting from b8bff07da:
..
COPY 1000
ERROR:  could not extend file "base/5/t2_16386" with FileFallocate(): No space left on device
HINT:  Check free disk space.
CONTEXT:  COPY t, line 1000
ERROR:  ResourceOwnerRemember called but array was full
CONTEXT:  COPY t, line 1000
WARNING:  local buffer refcount leak: [-499] (rel=base/5/t2_16386, blockNum=1483, flags=0x2000000, refcount=0 1)
server closed the connection unexpectedly
        This probably means the server terminated abnormally
        before or while processing the request.
connection to server was lost
2024-02-02 08:29:24.434 UTC [2052831] LOG:  server process (PID 2052839) was terminated by signal 6: Aborted

server.log contains:
TRAP: failed Assert("RefCountErrors == 0"), File: "localbuf.c", Line: 804, PID: 2052839

Best regards,
Alexander

#85Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Alexander Lakhin (#84)
Re: ResourceOwner refactoring

On 02/02/2024 11:00, Alexander Lakhin wrote:

Please try the following script:
mkdir /tmp/50m
sudo mount -t tmpfs -o size=50M tmpfs /tmp/50m
export PGDATA=/tmp/50m/tmpdb

initdb
pg_ctl -l server.log start

cat << 'EOF' | psql
CREATE TEMP TABLE t (a name, b name, c name, d name);
INSERT INTO t SELECT 'a', 'b', 'c', 'd' FROM generate_series(1, 1000) g;

COPY t TO '/tmp/t.data';
SELECT 'COPY t FROM ''/tmp/t.data''' FROM generate_series(1, 100)
\gexec
EOF

which produces an unexpected error, a warning, and an assertion failure,
starting from b8bff07da:

Fixed, thanks for the report!

Comparing ExtendBufferedRelLocal() and ExtendBufferedRelShared(), it's
easy to see that ExtendBufferedRelLocal() was missing a
ResourceOwnerEnlarge() call in the loop. But it's actually a bit more
subtle: it was correct without the ResourceOwnerEnlarge() call until
commit b8bff07da, because ExtendBufferedRelLocal() unpins the old buffer
pinning the new one, while ExtendBufferedRelShared() does it the other
way 'round. The implicit assumption was that unpinning the old buffer
ensures that you can pin a new one. That no longer holds with commit
b8bff07da. Remembering a new resource expects there to be a free slot in
the fixed-size array, but if the forgotten resource was in the hash,
rather than the array, forgetting it doesn't make space in the array.

We also make that assumption here in BufferAlloc:

/*
* Got a collision. Someone has already done what we were about to do.
* We'll just handle this as if it were found in the buffer pool in
* the first place. First, give up the buffer we were planning to
* use.
*
* We could do this after releasing the partition lock, but then we'd
* have to call ResourceOwnerEnlarge() & ReservePrivateRefCountEntry()
* before acquiring the lock, for the rare case of such a collision.
*/
UnpinBuffer(victim_buf_hdr);

It turns out to be OK in that case, because it unpins the buffer that
was the last one pinned. That does ensure that you have one free slot in
the array, but forgetting anything other than the most recently
remembered resource does not.

I've added a note to that in ResourceOwnerForget. I read through the
other callers of ResourceOwnerRemember and PinBuffer, but didn't find
any other unsafe uses. I'm not too happy with this subtlety, but at
least it's documented now.

--
Heikki Linnakangas
Neon (https://neon.tech)

#86Alexander Lakhin
exclusion@gmail.com
In reply to: Heikki Linnakangas (#85)
Re: ResourceOwner refactoring

Hello Heikki,

I've stumbled upon yet another anomaly introduced with b8bff07da.

Please try the following script:
SELECT 'init' FROM pg_create_logical_replication_slot('slot',
  'test_decoding');
CREATE TABLE tbl(t text);
BEGIN;
TRUNCATE table tbl;

SELECT data FROM pg_logical_slot_get_changes('slot', NULL, NULL,
 'include-xids', '0', 'skip-empty-xacts', '1', 'stream-changes', '1');

On b8bff07da (and the current HEAD), it ends up with:
ERROR:  ResourceOwnerEnlarge called after release started

But on the previous commit, I get no error:
 data
------
(0 rows)

Best regards,
Alexander

#87Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Alexander Lakhin (#86)
Re: ResourceOwner refactoring

Thanks for the testing!

On 01/06/2024 11:00, Alexander Lakhin wrote:

Hello Heikki,

I've stumbled upon yet another anomaly introduced with b8bff07da.

Please try the following script:
SELECT 'init' FROM pg_create_logical_replication_slot('slot',
  'test_decoding');
CREATE TABLE tbl(t text);
BEGIN;
TRUNCATE table tbl;

SELECT data FROM pg_logical_slot_get_changes('slot', NULL, NULL,
 'include-xids', '0', 'skip-empty-xacts', '1', 'stream-changes', '1');

On b8bff07da (and the current HEAD), it ends up with:
ERROR:  ResourceOwnerEnlarge called after release started

But on the previous commit, I get no error:
 data
------
(0 rows)

This is another case of the issue I tried to fix in commit b8bff07da
with this:

--- b/src/backend/access/transam/xact.c
+++ a/src/backend/access/transam/xact.c
@@ -5279,7 +5279,20 @@
AtEOSubXact_RelationCshould be ache(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);

AtEOSubXact_Inval calls LocalExecuteInvalidationMessage(), which
ultimately calls RelationFlushRelation(). Your test case reaches
ReorderBufferExecuteInvalidations(), which also calls
LocalExecuteInvalidationMessage(), in an already aborted subtransaction.

Looking closer at relcache.c, I think we need to make
RelationFlushRelation() safe to call without a valid resource owner.
There are already IsTransactionState() checks in
RelationClearRelation(), and a comment noting that it does not touch
CurrentResourceOwner. So we should make sure RelationFlushRelation()
doesn't touch it either, and revert the above change to
AbortTransaction(). I didn't feel very good about it in the first place,
and now I see what the proper fix is.

A straightforward fix is to modify RelationFlushRelation() so that if
!IsTransactionState(), it just marks the entry as invalid instead of
calling RelationClearRelation(). That's what RelationClearRelation()
would do anyway, if it didn't hit the assertion first.

RelationClearRelation() is complicated. Depending on the circumstances,
it removes the relcache entry, rebuilds it, marks it as invalid, or
performs a partial reload of a nailed relation. So before going for the
straightforward fix, I'm going to see if I can refactor
RelationClearRelation() to be less complicated.

--
Heikki Linnakangas
Neon (https://neon.tech)

#88Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Heikki Linnakangas (#87)
1 attachment(s)
Re: ResourceOwner refactoring

On 04/06/2024 01:49, Heikki Linnakangas wrote:

A straightforward fix is to modify RelationFlushRelation() so that if
!IsTransactionState(), it just marks the entry as invalid instead of
calling RelationClearRelation(). That's what RelationClearRelation()
would do anyway, if it didn't hit the assertion first.

Here's a patch with that straightforward fix. Your test case hit the
"rd_createSubid != InvalidSubTransactionId" case, I expanded it to also
cover the "rd_firstRelfilelocatorSubid != InvalidSubTransactionId" case.
Barring objections, I'll commit this later today or tomorrow. Thanks for
the report!

I started a new thread with the bigger refactorings I had in mind:
/messages/by-id/9c9e8908-7b3e-4ce7-85a8-00c0e165a3d6@iki.fi

--
Heikki Linnakangas
Neon (https://neon.tech)

Attachments:

0001-Make-RelationFlushRelation-work-without-ResourceOwne.patchtext/x-patch; charset=UTF-8; name=0001-Make-RelationFlushRelation-work-without-ResourceOwne.patchDownload
From 227f173d1066955a2ab6aba12c508aaa1f416c27 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 5 Jun 2024 13:29:51 +0300
Subject: [PATCH 1/3] Make RelationFlushRelation() work without ResourceOwner
 during abort

ReorderBufferImmediateInvalidation() executes invalidation messages in
an aborted transaction. However, RelationFlushRelation sometimes
required a valid resource owner, to temporarily increment the refcount
of the relache entry. Commit b8bff07daa worked around that in the main
subtransaction abort function, AbortSubTransaction(), but missed this
similar case in ReorderBufferImmediateInvalidation().

To fix, introduce a separate function to invalidate a relcache
entry. It does the same thing as RelationClearRelation(rebuild==true)
does, when outside a transaction, but can be called without
incrementing the refcount.

Add regression test. Before this fix, it failed with:

ERROR: ResourceOwnerEnlarge called after release started

Reported-by: Alexander Lakhin <exclusion@gmail.com>
Disussion: https://www.postgresql.org/message-id/e56be7d9-14b1-664d-0bfc-00ce9772721c@gmail.com
---
 .../expected/decoding_in_xact.out             | 48 +++++++++++++++++
 .../test_decoding/sql/decoding_in_xact.sql    | 27 ++++++++++
 src/backend/access/transam/xact.c             | 13 -----
 src/backend/utils/cache/relcache.c            | 54 ++++++++++++++++---
 4 files changed, 122 insertions(+), 20 deletions(-)

diff --git a/contrib/test_decoding/expected/decoding_in_xact.out b/contrib/test_decoding/expected/decoding_in_xact.out
index b65253f4630..8555b3d9436 100644
--- a/contrib/test_decoding/expected/decoding_in_xact.out
+++ b/contrib/test_decoding/expected/decoding_in_xact.out
@@ -79,6 +79,54 @@ SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'inc
  COMMIT
 (6 rows)
 
+-- Decoding works in transaction that issues DDL
+--
+-- We had issues handling relcache invalidations with these, see
+-- https://www.postgresql.org/message-id/e56be7d9-14b1-664d-0bfc-00ce9772721c@gmail.com
+CREATE TABLE tbl_created_outside_xact(id SERIAL PRIMARY KEY);
+BEGIN;
+  -- TRUNCATE changes the relfilenode and sends relcache invalidation
+  TRUNCATE tbl_created_outside_xact;
+  INSERT INTO tbl_created_outside_xact(id) VALUES('1');
+  -- don't show yet, haven't committed
+  SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
+ data 
+------
+(0 rows)
+
+COMMIT;
+SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
+                             data                             
+--------------------------------------------------------------
+ BEGIN
+ table public.tbl_created_outside_xact: TRUNCATE: (no-flags)
+ table public.tbl_created_outside_xact: INSERT: id[integer]:1
+ COMMIT
+(4 rows)
+
+set debug_logical_replication_streaming=immediate;
+BEGIN;
+  CREATE TABLE tbl_created_in_xact(id SERIAL PRIMARY KEY);
+  INSERT INTO tbl_created_in_xact VALUES (1);
+  CHECKPOINT; -- Force WAL flush, so that the above changes will be streamed
+  SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'stream-changes', '1');
+                   data                   
+------------------------------------------
+ opening a streamed block for transaction
+ streaming change for transaction
+ closing a streamed block for transaction
+(3 rows)
+
+COMMIT;
+reset debug_logical_replication_streaming;
+SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
+                          data                           
+---------------------------------------------------------
+ BEGIN
+ table public.tbl_created_in_xact: INSERT: id[integer]:1
+ COMMIT
+(3 rows)
+
 SELECT 'stop' FROM pg_drop_replication_slot('regression_slot');
  ?column? 
 ----------
diff --git a/contrib/test_decoding/sql/decoding_in_xact.sql b/contrib/test_decoding/sql/decoding_in_xact.sql
index 108782dc2e9..841a92fa780 100644
--- a/contrib/test_decoding/sql/decoding_in_xact.sql
+++ b/contrib/test_decoding/sql/decoding_in_xact.sql
@@ -38,4 +38,31 @@ COMMIT;
 INSERT INTO nobarf(data) VALUES('3');
 SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
 
+-- Decoding works in transaction that issues DDL
+--
+-- We had issues handling relcache invalidations with these, see
+-- https://www.postgresql.org/message-id/e56be7d9-14b1-664d-0bfc-00ce9772721c@gmail.com
+CREATE TABLE tbl_created_outside_xact(id SERIAL PRIMARY KEY);
+BEGIN;
+  -- TRUNCATE changes the relfilenode and sends relcache invalidation
+  TRUNCATE tbl_created_outside_xact;
+  INSERT INTO tbl_created_outside_xact(id) VALUES('1');
+
+  -- don't show yet, haven't committed
+  SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
+COMMIT;
+SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
+
+set debug_logical_replication_streaming=immediate;
+BEGIN;
+  CREATE TABLE tbl_created_in_xact(id SERIAL PRIMARY KEY);
+  INSERT INTO tbl_created_in_xact VALUES (1);
+
+  CHECKPOINT; -- Force WAL flush, so that the above changes will be streamed
+
+  SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'stream-changes', '1');
+COMMIT;
+reset debug_logical_replication_streaming;
+SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
+
 SELECT 'stop' FROM pg_drop_replication_slot('regression_slot');
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 4f4ce757623..9bda1aa6bc6 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -5279,20 +5279,7 @@ AbortSubTransaction(void)
 
 		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/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index cc9b0c6524f..35dbb87ae3d 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -275,6 +275,7 @@ static HTAB *OpClassCache = NULL;
 
 static void RelationCloseCleanup(Relation relation);
 static void RelationDestroyRelation(Relation relation, bool remember_tupdesc);
+static void RelationInvalidateRelation(Relation relation);
 static void RelationClearRelation(Relation relation, bool rebuild);
 
 static void RelationReloadIndexInfo(Relation relation);
@@ -2512,6 +2513,31 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 	pfree(relation);
 }
 
+/*
+ * RelationInvalidateRelation - mark a relation cache entry as invalid
+ *
+ * An entry that's marked as invalid will be reloaded on next access.
+ */
+static void
+RelationInvalidateRelation(Relation relation)
+{
+	/*
+	 * Make sure smgr and lower levels close the relation's files, if they
+	 * weren't closed already.  If the relation is not getting deleted, the
+	 * next smgr access should reopen the files automatically.  This ensures
+	 * that the low-level file access state is updated after, say, a vacuum
+	 * truncation.
+	 */
+	RelationCloseSmgr(relation);
+
+	/* Free AM cached data, if any */
+	if (relation->rd_amcache)
+		pfree(relation->rd_amcache);
+	relation->rd_amcache = NULL;
+
+	relation->rd_isvalid = false;
+}
+
 /*
  * RelationClearRelation
  *
@@ -2846,14 +2872,28 @@ RelationFlushRelation(Relation relation)
 		 * New relcache entries are always rebuilt, not flushed; else we'd
 		 * forget the "new" status of the relation.  Ditto for the
 		 * new-relfilenumber status.
-		 *
-		 * The rel could have zero refcnt here, so temporarily increment the
-		 * refcnt to ensure it's safe to rebuild it.  We can assume that the
-		 * current transaction has some lock on the rel already.
 		 */
-		RelationIncrementReferenceCount(relation);
-		RelationClearRelation(relation, true);
-		RelationDecrementReferenceCount(relation);
+		if (IsTransactionState() && relation->rd_droppedSubid == InvalidSubTransactionId)
+		{
+			/*
+			 * The rel could have zero refcnt here, so temporarily increment
+			 * the refcnt to ensure it's safe to rebuild it.  We can assume
+			 * that the current transaction has some lock on the rel already.
+			 */
+			RelationIncrementReferenceCount(relation);
+			RelationClearRelation(relation, true);
+			RelationDecrementReferenceCount(relation);
+		}
+		else
+		{
+			/*
+			 * During abort processing, the current resource owner is not
+			 * valid and we cannot hold a refcnt.  Without a valid
+			 * transaction, RelationClearRelation() would just mark the rel as
+			 * invalid anyway, so we can do the same directly.
+			 */
+			RelationInvalidateRelation(relation);
+		}
 	}
 	else
 	{
-- 
2.39.2

#89Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Heikki Linnakangas (#88)
Re: ResourceOwner refactoring

On 05/06/2024 16:58, Heikki Linnakangas wrote:

On 04/06/2024 01:49, Heikki Linnakangas wrote:

A straightforward fix is to modify RelationFlushRelation() so that if
!IsTransactionState(), it just marks the entry as invalid instead of
calling RelationClearRelation(). That's what RelationClearRelation()
would do anyway, if it didn't hit the assertion first.

Here's a patch with that straightforward fix. Your test case hit the
"rd_createSubid != InvalidSubTransactionId" case, I expanded it to also
cover the "rd_firstRelfilelocatorSubid != InvalidSubTransactionId" case.

For the record, I got the above backwards: your test case covered the
rd_firstRelfilelocatorSubid case and I expanded it to also cover the
rd_createSubid case.

Barring objections, I'll commit this later today or tomorrow. Thanks for
the report!

Committed.

--
Heikki Linnakangas
Neon (https://neon.tech)

#90Robert Haas
robertmhaas@gmail.com
In reply to: Heikki Linnakangas (#89)
Re: ResourceOwner refactoring

On Thu, Jun 6, 2024 at 7:32 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

Barring objections, I'll commit this later today or tomorrow. Thanks for
the report!

Committed.

I think you may have forgotten to push.

--
Robert Haas
EDB: http://www.enterprisedb.com

#91Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Robert Haas (#90)
Re: ResourceOwner refactoring

On 06/06/2024 18:27, Robert Haas wrote:

On Thu, Jun 6, 2024 at 7:32 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

Barring objections, I'll commit this later today or tomorrow. Thanks for
the report!

Committed.

I think you may have forgotten to push.

Huh, you're right. I could swear I did...

Pushed now thanks!

--
Heikki Linnakangas
Neon (https://neon.tech)