summaryrefslogtreecommitdiff
path: root/src/test
diff options
context:
space:
mode:
Diffstat (limited to 'src/test')
-rw-r--r--src/test/modules/Makefile1
-rw-r--r--src/test/modules/meson.build1
-rw-r--r--src/test/modules/test_resowner/.gitignore4
-rw-r--r--src/test/modules/test_resowner/Makefile24
-rw-r--r--src/test/modules/test_resowner/expected/test_resowner.out197
-rw-r--r--src/test/modules/test_resowner/meson.build34
-rw-r--r--src/test/modules/test_resowner/sql/test_resowner.sql25
-rw-r--r--src/test/modules/test_resowner/test_resowner--1.0.sql30
-rw-r--r--src/test/modules/test_resowner/test_resowner.control4
-rw-r--r--src/test/modules/test_resowner/test_resowner_basic.c211
-rw-r--r--src/test/modules/test_resowner/test_resowner_many.c296
11 files changed, 827 insertions, 0 deletions
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..4be46e9ddfc
--- /dev/null
+++ b/src/test/modules/test_resowner/test_resowner_basic.c
@@ -0,0 +1,211 @@
+/*--------------------------------------------------------------------------
+ *
+ * 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 between 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..bb820793a19
--- /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 all the 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();
+}