diff options
Diffstat (limited to 'src/test')
| -rw-r--r-- | src/test/modules/Makefile | 1 | ||||
| -rw-r--r-- | src/test/modules/meson.build | 1 | ||||
| -rw-r--r-- | src/test/modules/test_resowner/.gitignore | 4 | ||||
| -rw-r--r-- | src/test/modules/test_resowner/Makefile | 24 | ||||
| -rw-r--r-- | src/test/modules/test_resowner/expected/test_resowner.out | 197 | ||||
| -rw-r--r-- | src/test/modules/test_resowner/meson.build | 34 | ||||
| -rw-r--r-- | src/test/modules/test_resowner/sql/test_resowner.sql | 25 | ||||
| -rw-r--r-- | src/test/modules/test_resowner/test_resowner--1.0.sql | 30 | ||||
| -rw-r--r-- | src/test/modules/test_resowner/test_resowner.control | 4 | ||||
| -rw-r--r-- | src/test/modules/test_resowner/test_resowner_basic.c | 211 | ||||
| -rw-r--r-- | src/test/modules/test_resowner/test_resowner_many.c | 296 |
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(); +} |
