mirror of https://github.com/postgres/postgres
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 the 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 old names, but they are now defined in the source files that use those resource kinds. 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. Each resource kind specifies a release priority, and ResourceOwnerReleaseAll releases the resources in priority order. To make that possible, we have to restrict what you can do between phases. After calling ResourceOwnerRelease(), you are no longer allowed 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. 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.fipull/146/head
parent
b70c2143bb
commit
b8bff07daa
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,4 @@ |
|||||||
|
# Generated subdirectories |
||||||
|
/log/ |
||||||
|
/results/ |
||||||
|
/tmp_check/ |
@ -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 |
@ -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; |
@ -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', |
||||||
|
], |
||||||
|
}, |
||||||
|
} |
@ -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; |
@ -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; |
@ -0,0 +1,4 @@ |
|||||||
|
comment = 'Test code for ResourceOwners' |
||||||
|
default_version = '1.0' |
||||||
|
module_pathname = '$libdir/test_resowner' |
||||||
|
relocatable = true |
@ -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(); |
||||||
|
} |
@ -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(); |
||||||
|
} |
Loading…
Reference in new issue