postgresql/src/test/modules/test_resowner/test_resowner_many.c

297 lines
8.6 KiB
C

/*--------------------------------------------------------------------------
*
* 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();
}