1556 lines
43 KiB
C
1556 lines
43 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* resowner.c
|
|
* POSTGRES resource owner management code.
|
|
*
|
|
* Query-lifespan resources are tracked by associating them with
|
|
* ResourceOwner objects. This provides a simple mechanism for ensuring
|
|
* that such resources are freed at the right time.
|
|
* See utils/resowner/README for more info.
|
|
*
|
|
*
|
|
* Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/utils/resowner/resowner.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include "common/cryptohash.h"
|
|
#include "common/hashfn.h"
|
|
#include "common/hmac.h"
|
|
#include "jit/jit.h"
|
|
#include "storage/bufmgr.h"
|
|
#include "storage/ipc.h"
|
|
#include "storage/predicate.h"
|
|
#include "storage/proc.h"
|
|
#include "utils/memutils.h"
|
|
#include "utils/rel.h"
|
|
#include "utils/resowner_private.h"
|
|
#include "utils/snapmgr.h"
|
|
|
|
|
|
/*
|
|
* All resource IDs managed by this code are required to fit into a Datum,
|
|
* which is fine since they are generally pointers or integers.
|
|
*
|
|
* Provide Datum conversion macros for a couple of things that are really
|
|
* just "int".
|
|
*/
|
|
#define FileGetDatum(file) Int32GetDatum(file)
|
|
#define DatumGetFile(datum) ((File) DatumGetInt32(datum))
|
|
#define BufferGetDatum(buffer) Int32GetDatum(buffer)
|
|
#define DatumGetBuffer(datum) ((Buffer) DatumGetInt32(datum))
|
|
|
|
/*
|
|
* ResourceArray is a common structure for storing all types of resource IDs.
|
|
*
|
|
* We manage small sets of resource IDs by keeping them in a simple array:
|
|
* itemsarr[k] holds an ID, for 0 <= k < nitems <= maxitems = capacity.
|
|
*
|
|
* If a set grows large, we switch over to using open-addressing hashing.
|
|
* Then, itemsarr[] is a hash table of "capacity" slots, with each
|
|
* slot holding either an ID or "invalidval". nitems is the number of valid
|
|
* items present; if it would exceed maxitems, we enlarge the array and
|
|
* re-hash. In this mode, maxitems should be rather less than capacity so
|
|
* that we don't waste too much time searching for empty slots.
|
|
*
|
|
* In either mode, lastidx remembers the location of the last item inserted
|
|
* or returned by GetAny; this speeds up searches in ResourceArrayRemove.
|
|
*/
|
|
typedef struct ResourceArray
|
|
{
|
|
Datum *itemsarr; /* buffer for storing values */
|
|
Datum invalidval; /* value that is considered invalid */
|
|
uint32 capacity; /* allocated length of itemsarr[] */
|
|
uint32 nitems; /* how many items are stored in items array */
|
|
uint32 maxitems; /* current limit on nitems before enlarging */
|
|
uint32 lastidx; /* index of last item returned by GetAny */
|
|
} ResourceArray;
|
|
|
|
/*
|
|
* Initially allocated size of a ResourceArray. Must be power of two since
|
|
* we'll use (arraysize - 1) as mask for hashing.
|
|
*/
|
|
#define RESARRAY_INIT_SIZE 16
|
|
|
|
/*
|
|
* When to switch to hashing vs. simple array logic in a ResourceArray.
|
|
*/
|
|
#define RESARRAY_MAX_ARRAY 64
|
|
#define RESARRAY_IS_ARRAY(resarr) ((resarr)->capacity <= RESARRAY_MAX_ARRAY)
|
|
|
|
/*
|
|
* How many items may be stored in a resource array of given capacity.
|
|
* When this number is reached, we must resize.
|
|
*/
|
|
#define RESARRAY_MAX_ITEMS(capacity) \
|
|
((capacity) <= RESARRAY_MAX_ARRAY ? (capacity) : (capacity)/4 * 3)
|
|
|
|
/*
|
|
* To speed up bulk releasing or reassigning locks from a resource owner to
|
|
* its parent, each resource owner has a small cache of locks it owns. The
|
|
* lock manager has the same information in its local lock hash table, and
|
|
* we fall back on that if cache overflows, but traversing the hash table
|
|
* is slower when there are a lot of locks belonging to other resource owners.
|
|
*
|
|
* MAX_RESOWNER_LOCKS is the size of the per-resource owner cache. It's
|
|
* chosen based on some testing with pg_dump with a large schema. When the
|
|
* tests were done (on 9.2), resource owners in a pg_dump run contained up
|
|
* to 9 locks, regardless of the schema size, except for the top resource
|
|
* owner which contained much more (overflowing the cache). 15 seems like a
|
|
* nice round number that's somewhat higher than what pg_dump needs. Note that
|
|
* making this number larger is not free - the bigger the cache, the slower
|
|
* it is to release locks (in retail), when a resource owner holds many locks.
|
|
*/
|
|
#define MAX_RESOWNER_LOCKS 15
|
|
|
|
/*
|
|
* ResourceOwner objects look like this
|
|
*/
|
|
typedef struct ResourceOwnerData
|
|
{
|
|
ResourceOwner parent; /* NULL if no parent (toplevel owner) */
|
|
ResourceOwner firstchild; /* head of linked list of children */
|
|
ResourceOwner nextchild; /* next child of same parent */
|
|
const char *name; /* name (just for debugging) */
|
|
|
|
/* We have built-in support for remembering: */
|
|
ResourceArray bufferarr; /* owned buffers */
|
|
ResourceArray bufferioarr; /* in-progress buffer IO */
|
|
ResourceArray catrefarr; /* catcache references */
|
|
ResourceArray catlistrefarr; /* catcache-list pins */
|
|
ResourceArray relrefarr; /* relcache references */
|
|
ResourceArray planrefarr; /* plancache references */
|
|
ResourceArray tupdescarr; /* tupdesc references */
|
|
ResourceArray snapshotarr; /* snapshot references */
|
|
ResourceArray filearr; /* open temporary files */
|
|
ResourceArray dsmarr; /* dynamic shmem segments */
|
|
ResourceArray jitarr; /* JIT contexts */
|
|
ResourceArray cryptohasharr; /* cryptohash contexts */
|
|
ResourceArray hmacarr; /* HMAC contexts */
|
|
|
|
/* We can remember up to MAX_RESOWNER_LOCKS references to local locks. */
|
|
int nlocks; /* number of owned locks */
|
|
LOCALLOCK *locks[MAX_RESOWNER_LOCKS]; /* list of owned locks */
|
|
} ResourceOwnerData;
|
|
|
|
|
|
/*****************************************************************************
|
|
* GLOBAL MEMORY *
|
|
*****************************************************************************/
|
|
|
|
ResourceOwner CurrentResourceOwner = NULL;
|
|
ResourceOwner CurTransactionResourceOwner = NULL;
|
|
ResourceOwner TopTransactionResourceOwner = NULL;
|
|
ResourceOwner AuxProcessResourceOwner = NULL;
|
|
|
|
/*
|
|
* List of add-on callbacks for resource releasing
|
|
*/
|
|
typedef struct ResourceReleaseCallbackItem
|
|
{
|
|
struct ResourceReleaseCallbackItem *next;
|
|
ResourceReleaseCallback callback;
|
|
void *arg;
|
|
} ResourceReleaseCallbackItem;
|
|
|
|
static ResourceReleaseCallbackItem *ResourceRelease_callbacks = NULL;
|
|
|
|
|
|
/* Internal routines */
|
|
static void ResourceArrayInit(ResourceArray *resarr, Datum invalidval);
|
|
static void ResourceArrayEnlarge(ResourceArray *resarr);
|
|
static void ResourceArrayAdd(ResourceArray *resarr, Datum value);
|
|
static bool ResourceArrayRemove(ResourceArray *resarr, Datum value);
|
|
static bool ResourceArrayGetAny(ResourceArray *resarr, Datum *value);
|
|
static void ResourceArrayFree(ResourceArray *resarr);
|
|
static void ResourceOwnerReleaseInternal(ResourceOwner owner,
|
|
ResourceReleasePhase phase,
|
|
bool isCommit,
|
|
bool isTopLevel);
|
|
static void ReleaseAuxProcessResourcesCallback(int code, Datum arg);
|
|
static void PrintRelCacheLeakWarning(Relation rel);
|
|
static void PrintPlanCacheLeakWarning(CachedPlan *plan);
|
|
static void PrintTupleDescLeakWarning(TupleDesc tupdesc);
|
|
static void PrintSnapshotLeakWarning(Snapshot snapshot);
|
|
static void PrintFileLeakWarning(File file);
|
|
static void PrintDSMLeakWarning(dsm_segment *seg);
|
|
static void PrintCryptoHashLeakWarning(Datum handle);
|
|
static void PrintHMACLeakWarning(Datum handle);
|
|
|
|
|
|
/*****************************************************************************
|
|
* INTERNAL ROUTINES *
|
|
*****************************************************************************/
|
|
|
|
|
|
/*
|
|
* Initialize a ResourceArray
|
|
*/
|
|
static void
|
|
ResourceArrayInit(ResourceArray *resarr, Datum invalidval)
|
|
{
|
|
/* Assert it's empty */
|
|
Assert(resarr->itemsarr == NULL);
|
|
Assert(resarr->capacity == 0);
|
|
Assert(resarr->nitems == 0);
|
|
Assert(resarr->maxitems == 0);
|
|
/* Remember the appropriate "invalid" value */
|
|
resarr->invalidval = invalidval;
|
|
/* We don't allocate any storage until needed */
|
|
}
|
|
|
|
/*
|
|
* Make sure there is room for at least one more resource in an array.
|
|
*
|
|
* This is separate from actually inserting a resource because if we run out
|
|
* of memory, it's critical to do so *before* acquiring the resource.
|
|
*/
|
|
static void
|
|
ResourceArrayEnlarge(ResourceArray *resarr)
|
|
{
|
|
uint32 i,
|
|
oldcap,
|
|
newcap;
|
|
Datum *olditemsarr;
|
|
Datum *newitemsarr;
|
|
|
|
if (resarr->nitems < resarr->maxitems)
|
|
return; /* no work needed */
|
|
|
|
olditemsarr = resarr->itemsarr;
|
|
oldcap = resarr->capacity;
|
|
|
|
/* Double the capacity of the array (capacity must stay a power of 2!) */
|
|
newcap = (oldcap > 0) ? oldcap * 2 : RESARRAY_INIT_SIZE;
|
|
newitemsarr = (Datum *) MemoryContextAlloc(TopMemoryContext,
|
|
newcap * sizeof(Datum));
|
|
for (i = 0; i < newcap; i++)
|
|
newitemsarr[i] = resarr->invalidval;
|
|
|
|
/* We assume we can't fail below this point, so OK to scribble on resarr */
|
|
resarr->itemsarr = newitemsarr;
|
|
resarr->capacity = newcap;
|
|
resarr->maxitems = RESARRAY_MAX_ITEMS(newcap);
|
|
resarr->nitems = 0;
|
|
|
|
if (olditemsarr != NULL)
|
|
{
|
|
/*
|
|
* Transfer any pre-existing entries into the new array; they don't
|
|
* necessarily go where they were before, so this simple logic is the
|
|
* best way. Note that if we were managing the set as a simple array,
|
|
* the entries after nitems are garbage, but that shouldn't matter
|
|
* because we won't get here unless nitems was equal to oldcap.
|
|
*/
|
|
for (i = 0; i < oldcap; i++)
|
|
{
|
|
if (olditemsarr[i] != resarr->invalidval)
|
|
ResourceArrayAdd(resarr, olditemsarr[i]);
|
|
}
|
|
|
|
/* And release old array. */
|
|
pfree(olditemsarr);
|
|
}
|
|
|
|
Assert(resarr->nitems < resarr->maxitems);
|
|
}
|
|
|
|
/*
|
|
* Add a resource to ResourceArray
|
|
*
|
|
* Caller must have previously done ResourceArrayEnlarge()
|
|
*/
|
|
static void
|
|
ResourceArrayAdd(ResourceArray *resarr, Datum value)
|
|
{
|
|
uint32 idx;
|
|
|
|
Assert(value != resarr->invalidval);
|
|
Assert(resarr->nitems < resarr->maxitems);
|
|
|
|
if (RESARRAY_IS_ARRAY(resarr))
|
|
{
|
|
/* Append to linear array. */
|
|
idx = resarr->nitems;
|
|
}
|
|
else
|
|
{
|
|
/* Insert into first free slot at or after hash location. */
|
|
uint32 mask = resarr->capacity - 1;
|
|
|
|
idx = DatumGetUInt32(hash_any((void *) &value, sizeof(value))) & mask;
|
|
for (;;)
|
|
{
|
|
if (resarr->itemsarr[idx] == resarr->invalidval)
|
|
break;
|
|
idx = (idx + 1) & mask;
|
|
}
|
|
}
|
|
resarr->lastidx = idx;
|
|
resarr->itemsarr[idx] = value;
|
|
resarr->nitems++;
|
|
}
|
|
|
|
/*
|
|
* Remove a resource from ResourceArray
|
|
*
|
|
* Returns true on success, false if resource was not found.
|
|
*
|
|
* Note: if same resource ID appears more than once, one instance is removed.
|
|
*/
|
|
static bool
|
|
ResourceArrayRemove(ResourceArray *resarr, Datum value)
|
|
{
|
|
uint32 i,
|
|
idx,
|
|
lastidx = resarr->lastidx;
|
|
|
|
Assert(value != resarr->invalidval);
|
|
|
|
/* Search through all items, but try lastidx first. */
|
|
if (RESARRAY_IS_ARRAY(resarr))
|
|
{
|
|
if (lastidx < resarr->nitems &&
|
|
resarr->itemsarr[lastidx] == value)
|
|
{
|
|
resarr->itemsarr[lastidx] = resarr->itemsarr[resarr->nitems - 1];
|
|
resarr->nitems--;
|
|
/* Update lastidx to make reverse-order removals fast. */
|
|
resarr->lastidx = resarr->nitems - 1;
|
|
return true;
|
|
}
|
|
for (i = 0; i < resarr->nitems; i++)
|
|
{
|
|
if (resarr->itemsarr[i] == value)
|
|
{
|
|
resarr->itemsarr[i] = resarr->itemsarr[resarr->nitems - 1];
|
|
resarr->nitems--;
|
|
/* Update lastidx to make reverse-order removals fast. */
|
|
resarr->lastidx = resarr->nitems - 1;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
uint32 mask = resarr->capacity - 1;
|
|
|
|
if (lastidx < resarr->capacity &&
|
|
resarr->itemsarr[lastidx] == value)
|
|
{
|
|
resarr->itemsarr[lastidx] = resarr->invalidval;
|
|
resarr->nitems--;
|
|
return true;
|
|
}
|
|
idx = DatumGetUInt32(hash_any((void *) &value, sizeof(value))) & mask;
|
|
for (i = 0; i < resarr->capacity; i++)
|
|
{
|
|
if (resarr->itemsarr[idx] == value)
|
|
{
|
|
resarr->itemsarr[idx] = resarr->invalidval;
|
|
resarr->nitems--;
|
|
return true;
|
|
}
|
|
idx = (idx + 1) & mask;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Get any convenient entry in a ResourceArray.
|
|
*
|
|
* "Convenient" is defined as "easy for ResourceArrayRemove to remove";
|
|
* we help that along by setting lastidx to match. This avoids O(N^2) cost
|
|
* when removing all ResourceArray items during ResourceOwner destruction.
|
|
*
|
|
* Returns true if we found an element, or false if the array is empty.
|
|
*/
|
|
static bool
|
|
ResourceArrayGetAny(ResourceArray *resarr, Datum *value)
|
|
{
|
|
if (resarr->nitems == 0)
|
|
return false;
|
|
|
|
if (RESARRAY_IS_ARRAY(resarr))
|
|
{
|
|
/* Linear array: just return the first element. */
|
|
resarr->lastidx = 0;
|
|
}
|
|
else
|
|
{
|
|
/* Hash: search forward from wherever we were last. */
|
|
uint32 mask = resarr->capacity - 1;
|
|
|
|
for (;;)
|
|
{
|
|
resarr->lastidx &= mask;
|
|
if (resarr->itemsarr[resarr->lastidx] != resarr->invalidval)
|
|
break;
|
|
resarr->lastidx++;
|
|
}
|
|
}
|
|
|
|
*value = resarr->itemsarr[resarr->lastidx];
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Trash a ResourceArray (we don't care about its state after this)
|
|
*/
|
|
static void
|
|
ResourceArrayFree(ResourceArray *resarr)
|
|
{
|
|
if (resarr->itemsarr)
|
|
pfree(resarr->itemsarr);
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
* EXPORTED ROUTINES *
|
|
*****************************************************************************/
|
|
|
|
|
|
/*
|
|
* ResourceOwnerCreate
|
|
* Create an empty ResourceOwner.
|
|
*
|
|
* All ResourceOwner objects are kept in TopMemoryContext, since they should
|
|
* only be freed explicitly.
|
|
*/
|
|
ResourceOwner
|
|
ResourceOwnerCreate(ResourceOwner parent, const char *name)
|
|
{
|
|
ResourceOwner owner;
|
|
|
|
owner = (ResourceOwner) MemoryContextAllocZero(TopMemoryContext,
|
|
sizeof(ResourceOwnerData));
|
|
owner->name = name;
|
|
|
|
if (parent)
|
|
{
|
|
owner->parent = parent;
|
|
owner->nextchild = parent->firstchild;
|
|
parent->firstchild = owner;
|
|
}
|
|
|
|
ResourceArrayInit(&(owner->bufferarr), BufferGetDatum(InvalidBuffer));
|
|
ResourceArrayInit(&(owner->bufferioarr), BufferGetDatum(InvalidBuffer));
|
|
ResourceArrayInit(&(owner->catrefarr), PointerGetDatum(NULL));
|
|
ResourceArrayInit(&(owner->catlistrefarr), PointerGetDatum(NULL));
|
|
ResourceArrayInit(&(owner->relrefarr), PointerGetDatum(NULL));
|
|
ResourceArrayInit(&(owner->planrefarr), PointerGetDatum(NULL));
|
|
ResourceArrayInit(&(owner->tupdescarr), PointerGetDatum(NULL));
|
|
ResourceArrayInit(&(owner->snapshotarr), PointerGetDatum(NULL));
|
|
ResourceArrayInit(&(owner->filearr), FileGetDatum(-1));
|
|
ResourceArrayInit(&(owner->dsmarr), PointerGetDatum(NULL));
|
|
ResourceArrayInit(&(owner->jitarr), PointerGetDatum(NULL));
|
|
ResourceArrayInit(&(owner->cryptohasharr), PointerGetDatum(NULL));
|
|
ResourceArrayInit(&(owner->hmacarr), PointerGetDatum(NULL));
|
|
|
|
return owner;
|
|
}
|
|
|
|
/*
|
|
* ResourceOwnerRelease
|
|
* Release all resources owned by a ResourceOwner and its descendants,
|
|
* but don't delete the owner objects themselves.
|
|
*
|
|
* Note that this executes just one phase of release, and so typically
|
|
* must be called three times. We do it this way because (a) we want to
|
|
* do all the recursion separately for each phase, thereby preserving
|
|
* the needed order of operations; and (b) xact.c may have other operations
|
|
* to do between the phases.
|
|
*
|
|
* phase: release phase to execute
|
|
* isCommit: true for successful completion of a query or transaction,
|
|
* false for unsuccessful
|
|
* isTopLevel: true if completing a main transaction, else false
|
|
*
|
|
* isCommit is passed because some modules may expect that their resources
|
|
* were all released already if the transaction or portal finished normally.
|
|
* If so it is reasonable to give a warning (NOT an error) should any
|
|
* unreleased resources be present. When isCommit is false, such warnings
|
|
* are generally inappropriate.
|
|
*
|
|
* isTopLevel is passed when we are releasing TopTransactionResourceOwner
|
|
* at completion of a main transaction. This generally means that *all*
|
|
* resources will be released, and so we can optimize things a bit.
|
|
*/
|
|
void
|
|
ResourceOwnerRelease(ResourceOwner owner,
|
|
ResourceReleasePhase phase,
|
|
bool isCommit,
|
|
bool isTopLevel)
|
|
{
|
|
/* There's not currently any setup needed before recursing */
|
|
ResourceOwnerReleaseInternal(owner, phase, isCommit, isTopLevel);
|
|
}
|
|
|
|
static void
|
|
ResourceOwnerReleaseInternal(ResourceOwner owner,
|
|
ResourceReleasePhase phase,
|
|
bool isCommit,
|
|
bool isTopLevel)
|
|
{
|
|
ResourceOwner child;
|
|
ResourceOwner save;
|
|
ResourceReleaseCallbackItem *item;
|
|
ResourceReleaseCallbackItem *next;
|
|
Datum foundres;
|
|
|
|
/* Recurse to handle descendants */
|
|
for (child = owner->firstchild; child != NULL; child = child->nextchild)
|
|
ResourceOwnerReleaseInternal(child, phase, isCommit, isTopLevel);
|
|
|
|
/*
|
|
* Make CurrentResourceOwner point to me, so that ReleaseBuffer etc don't
|
|
* get confused.
|
|
*/
|
|
save = CurrentResourceOwner;
|
|
CurrentResourceOwner = owner;
|
|
|
|
if (phase == RESOURCE_RELEASE_BEFORE_LOCKS)
|
|
{
|
|
/*
|
|
* Abort failed buffer IO. AbortBufferIO()->TerminateBufferIO() calls
|
|
* ResourceOwnerForgetBufferIO(), so we just have to iterate till
|
|
* there are none.
|
|
*
|
|
* Needs to be before we release buffer pins.
|
|
*
|
|
* During a commit, there shouldn't be any in-progress IO.
|
|
*/
|
|
while (ResourceArrayGetAny(&(owner->bufferioarr), &foundres))
|
|
{
|
|
Buffer res = DatumGetBuffer(foundres);
|
|
|
|
if (isCommit)
|
|
elog(PANIC, "lost track of buffer IO on buffer %d", res);
|
|
AbortBufferIO(res);
|
|
}
|
|
|
|
/*
|
|
* Release buffer pins. Note that ReleaseBuffer will remove the
|
|
* buffer entry from our array, so we just have to iterate till there
|
|
* are none.
|
|
*
|
|
* During a commit, there shouldn't be any remaining pins --- that
|
|
* would indicate failure to clean up the executor correctly --- so
|
|
* issue warnings. In the abort case, just clean up quietly.
|
|
*/
|
|
while (ResourceArrayGetAny(&(owner->bufferarr), &foundres))
|
|
{
|
|
Buffer res = DatumGetBuffer(foundres);
|
|
|
|
if (isCommit)
|
|
PrintBufferLeakWarning(res);
|
|
ReleaseBuffer(res);
|
|
}
|
|
|
|
/* Ditto for relcache references */
|
|
while (ResourceArrayGetAny(&(owner->relrefarr), &foundres))
|
|
{
|
|
Relation res = (Relation) DatumGetPointer(foundres);
|
|
|
|
if (isCommit)
|
|
PrintRelCacheLeakWarning(res);
|
|
RelationClose(res);
|
|
}
|
|
|
|
/* Ditto for dynamic shared memory segments */
|
|
while (ResourceArrayGetAny(&(owner->dsmarr), &foundres))
|
|
{
|
|
dsm_segment *res = (dsm_segment *) DatumGetPointer(foundres);
|
|
|
|
if (isCommit)
|
|
PrintDSMLeakWarning(res);
|
|
dsm_detach(res);
|
|
}
|
|
|
|
/* Ditto for JIT contexts */
|
|
while (ResourceArrayGetAny(&(owner->jitarr), &foundres))
|
|
{
|
|
JitContext *context = (JitContext *) DatumGetPointer(foundres);
|
|
|
|
jit_release_context(context);
|
|
}
|
|
|
|
/* Ditto for cryptohash contexts */
|
|
while (ResourceArrayGetAny(&(owner->cryptohasharr), &foundres))
|
|
{
|
|
pg_cryptohash_ctx *context =
|
|
(pg_cryptohash_ctx *) DatumGetPointer(foundres);
|
|
|
|
if (isCommit)
|
|
PrintCryptoHashLeakWarning(foundres);
|
|
pg_cryptohash_free(context);
|
|
}
|
|
|
|
/* Ditto for HMAC contexts */
|
|
while (ResourceArrayGetAny(&(owner->hmacarr), &foundres))
|
|
{
|
|
pg_hmac_ctx *context = (pg_hmac_ctx *) DatumGetPointer(foundres);
|
|
|
|
if (isCommit)
|
|
PrintHMACLeakWarning(foundres);
|
|
pg_hmac_free(context);
|
|
}
|
|
}
|
|
else if (phase == RESOURCE_RELEASE_LOCKS)
|
|
{
|
|
if (isTopLevel)
|
|
{
|
|
/*
|
|
* For a top-level xact we are going to release all locks (or at
|
|
* least all non-session locks), so just do a single lmgr call at
|
|
* the top of the recursion.
|
|
*/
|
|
if (owner == TopTransactionResourceOwner)
|
|
{
|
|
ProcReleaseLocks(isCommit);
|
|
ReleasePredicateLocks(isCommit, false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Release locks retail. Note that if we are committing a
|
|
* subtransaction, we do NOT release its locks yet, but transfer
|
|
* them to the parent.
|
|
*/
|
|
LOCALLOCK **locks;
|
|
int nlocks;
|
|
|
|
Assert(owner->parent != NULL);
|
|
|
|
/*
|
|
* Pass the list of locks owned by this resource owner to the lock
|
|
* manager, unless it has overflowed.
|
|
*/
|
|
if (owner->nlocks > MAX_RESOWNER_LOCKS)
|
|
{
|
|
locks = NULL;
|
|
nlocks = 0;
|
|
}
|
|
else
|
|
{
|
|
locks = owner->locks;
|
|
nlocks = owner->nlocks;
|
|
}
|
|
|
|
if (isCommit)
|
|
LockReassignCurrentOwner(locks, nlocks);
|
|
else
|
|
LockReleaseCurrentOwner(locks, nlocks);
|
|
}
|
|
}
|
|
else if (phase == RESOURCE_RELEASE_AFTER_LOCKS)
|
|
{
|
|
/*
|
|
* Release catcache references. Note that ReleaseCatCache will remove
|
|
* the catref entry from our array, so we just have to iterate till
|
|
* there are none.
|
|
*
|
|
* As with buffer pins, warn if any are left at commit time.
|
|
*/
|
|
while (ResourceArrayGetAny(&(owner->catrefarr), &foundres))
|
|
{
|
|
HeapTuple res = (HeapTuple) DatumGetPointer(foundres);
|
|
|
|
if (isCommit)
|
|
PrintCatCacheLeakWarning(res);
|
|
ReleaseCatCache(res);
|
|
}
|
|
|
|
/* Ditto for catcache lists */
|
|
while (ResourceArrayGetAny(&(owner->catlistrefarr), &foundres))
|
|
{
|
|
CatCList *res = (CatCList *) DatumGetPointer(foundres);
|
|
|
|
if (isCommit)
|
|
PrintCatCacheListLeakWarning(res);
|
|
ReleaseCatCacheList(res);
|
|
}
|
|
|
|
/* Ditto for plancache references */
|
|
while (ResourceArrayGetAny(&(owner->planrefarr), &foundres))
|
|
{
|
|
CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres);
|
|
|
|
if (isCommit)
|
|
PrintPlanCacheLeakWarning(res);
|
|
ReleaseCachedPlan(res, owner);
|
|
}
|
|
|
|
/* Ditto for tupdesc references */
|
|
while (ResourceArrayGetAny(&(owner->tupdescarr), &foundres))
|
|
{
|
|
TupleDesc res = (TupleDesc) DatumGetPointer(foundres);
|
|
|
|
if (isCommit)
|
|
PrintTupleDescLeakWarning(res);
|
|
DecrTupleDescRefCount(res);
|
|
}
|
|
|
|
/* Ditto for snapshot references */
|
|
while (ResourceArrayGetAny(&(owner->snapshotarr), &foundres))
|
|
{
|
|
Snapshot res = (Snapshot) DatumGetPointer(foundres);
|
|
|
|
if (isCommit)
|
|
PrintSnapshotLeakWarning(res);
|
|
UnregisterSnapshot(res);
|
|
}
|
|
|
|
/* Ditto for temporary files */
|
|
while (ResourceArrayGetAny(&(owner->filearr), &foundres))
|
|
{
|
|
File res = DatumGetFile(foundres);
|
|
|
|
if (isCommit)
|
|
PrintFileLeakWarning(res);
|
|
FileClose(res);
|
|
}
|
|
}
|
|
|
|
/* Let add-on modules get a chance too */
|
|
for (item = ResourceRelease_callbacks; item; item = next)
|
|
{
|
|
/* allow callbacks to unregister themselves when called */
|
|
next = item->next;
|
|
item->callback(phase, isCommit, isTopLevel, item->arg);
|
|
}
|
|
|
|
CurrentResourceOwner = save;
|
|
}
|
|
|
|
/*
|
|
* ResourceOwnerReleaseAllPlanCacheRefs
|
|
* Release the plancache references (only) held by this owner.
|
|
*
|
|
* We might eventually add similar functions for other resource types,
|
|
* but for now, only this is needed.
|
|
*/
|
|
void
|
|
ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner)
|
|
{
|
|
Datum foundres;
|
|
|
|
while (ResourceArrayGetAny(&(owner->planrefarr), &foundres))
|
|
{
|
|
CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres);
|
|
|
|
ReleaseCachedPlan(res, owner);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ResourceOwnerDelete
|
|
* Delete an owner object and its descendants.
|
|
*
|
|
* The caller must have already released all resources in the object tree.
|
|
*/
|
|
void
|
|
ResourceOwnerDelete(ResourceOwner owner)
|
|
{
|
|
/* We had better not be deleting CurrentResourceOwner ... */
|
|
Assert(owner != CurrentResourceOwner);
|
|
|
|
/* And it better not own any resources, either */
|
|
Assert(owner->bufferarr.nitems == 0);
|
|
Assert(owner->bufferioarr.nitems == 0);
|
|
Assert(owner->catrefarr.nitems == 0);
|
|
Assert(owner->catlistrefarr.nitems == 0);
|
|
Assert(owner->relrefarr.nitems == 0);
|
|
Assert(owner->planrefarr.nitems == 0);
|
|
Assert(owner->tupdescarr.nitems == 0);
|
|
Assert(owner->snapshotarr.nitems == 0);
|
|
Assert(owner->filearr.nitems == 0);
|
|
Assert(owner->dsmarr.nitems == 0);
|
|
Assert(owner->jitarr.nitems == 0);
|
|
Assert(owner->cryptohasharr.nitems == 0);
|
|
Assert(owner->hmacarr.nitems == 0);
|
|
Assert(owner->nlocks == 0 || owner->nlocks == MAX_RESOWNER_LOCKS + 1);
|
|
|
|
/*
|
|
* Delete children. The recursive call will delink the child from me, so
|
|
* just iterate as long as there is a child.
|
|
*/
|
|
while (owner->firstchild != NULL)
|
|
ResourceOwnerDelete(owner->firstchild);
|
|
|
|
/*
|
|
* We delink the owner from its parent before deleting it, so that if
|
|
* there's an error we won't have deleted/busted owners still attached to
|
|
* the owner tree. Better a leak than a crash.
|
|
*/
|
|
ResourceOwnerNewParent(owner, NULL);
|
|
|
|
/* And free the object. */
|
|
ResourceArrayFree(&(owner->bufferarr));
|
|
ResourceArrayFree(&(owner->bufferioarr));
|
|
ResourceArrayFree(&(owner->catrefarr));
|
|
ResourceArrayFree(&(owner->catlistrefarr));
|
|
ResourceArrayFree(&(owner->relrefarr));
|
|
ResourceArrayFree(&(owner->planrefarr));
|
|
ResourceArrayFree(&(owner->tupdescarr));
|
|
ResourceArrayFree(&(owner->snapshotarr));
|
|
ResourceArrayFree(&(owner->filearr));
|
|
ResourceArrayFree(&(owner->dsmarr));
|
|
ResourceArrayFree(&(owner->jitarr));
|
|
ResourceArrayFree(&(owner->cryptohasharr));
|
|
ResourceArrayFree(&(owner->hmacarr));
|
|
|
|
pfree(owner);
|
|
}
|
|
|
|
/*
|
|
* Fetch parent of a ResourceOwner (returns NULL if top-level owner)
|
|
*/
|
|
ResourceOwner
|
|
ResourceOwnerGetParent(ResourceOwner owner)
|
|
{
|
|
return owner->parent;
|
|
}
|
|
|
|
/*
|
|
* Reassign a ResourceOwner to have a new parent
|
|
*/
|
|
void
|
|
ResourceOwnerNewParent(ResourceOwner owner,
|
|
ResourceOwner newparent)
|
|
{
|
|
ResourceOwner oldparent = owner->parent;
|
|
|
|
if (oldparent)
|
|
{
|
|
if (owner == oldparent->firstchild)
|
|
oldparent->firstchild = owner->nextchild;
|
|
else
|
|
{
|
|
ResourceOwner child;
|
|
|
|
for (child = oldparent->firstchild; child; child = child->nextchild)
|
|
{
|
|
if (owner == child->nextchild)
|
|
{
|
|
child->nextchild = owner->nextchild;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (newparent)
|
|
{
|
|
Assert(owner != newparent);
|
|
owner->parent = newparent;
|
|
owner->nextchild = newparent->firstchild;
|
|
newparent->firstchild = owner;
|
|
}
|
|
else
|
|
{
|
|
owner->parent = NULL;
|
|
owner->nextchild = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Register or deregister callback functions for resource cleanup
|
|
*
|
|
* These functions are intended for use by dynamically loaded modules.
|
|
* For built-in modules we generally just hardwire the appropriate calls.
|
|
*
|
|
* Note that the callback occurs post-commit or post-abort, so the callback
|
|
* functions can only do noncritical cleanup.
|
|
*/
|
|
void
|
|
RegisterResourceReleaseCallback(ResourceReleaseCallback callback, void *arg)
|
|
{
|
|
ResourceReleaseCallbackItem *item;
|
|
|
|
item = (ResourceReleaseCallbackItem *)
|
|
MemoryContextAlloc(TopMemoryContext,
|
|
sizeof(ResourceReleaseCallbackItem));
|
|
item->callback = callback;
|
|
item->arg = arg;
|
|
item->next = ResourceRelease_callbacks;
|
|
ResourceRelease_callbacks = item;
|
|
}
|
|
|
|
void
|
|
UnregisterResourceReleaseCallback(ResourceReleaseCallback callback, void *arg)
|
|
{
|
|
ResourceReleaseCallbackItem *item;
|
|
ResourceReleaseCallbackItem *prev;
|
|
|
|
prev = NULL;
|
|
for (item = ResourceRelease_callbacks; item; prev = item, item = item->next)
|
|
{
|
|
if (item->callback == callback && item->arg == arg)
|
|
{
|
|
if (prev)
|
|
prev->next = item->next;
|
|
else
|
|
ResourceRelease_callbacks = item->next;
|
|
pfree(item);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Establish an AuxProcessResourceOwner for the current process.
|
|
*/
|
|
void
|
|
CreateAuxProcessResourceOwner(void)
|
|
{
|
|
Assert(AuxProcessResourceOwner == NULL);
|
|
Assert(CurrentResourceOwner == NULL);
|
|
AuxProcessResourceOwner = ResourceOwnerCreate(NULL, "AuxiliaryProcess");
|
|
CurrentResourceOwner = AuxProcessResourceOwner;
|
|
|
|
/*
|
|
* Register a shmem-exit callback for cleanup of aux-process resource
|
|
* owner. (This needs to run after, e.g., ShutdownXLOG.)
|
|
*/
|
|
on_shmem_exit(ReleaseAuxProcessResourcesCallback, 0);
|
|
}
|
|
|
|
/*
|
|
* Convenience routine to release all resources tracked in
|
|
* AuxProcessResourceOwner (but that resowner is not destroyed here).
|
|
* Warn about leaked resources if isCommit is true.
|
|
*/
|
|
void
|
|
ReleaseAuxProcessResources(bool isCommit)
|
|
{
|
|
/*
|
|
* At this writing, the only thing that could actually get released is
|
|
* buffer pins; but we may as well do the full release protocol.
|
|
*/
|
|
ResourceOwnerRelease(AuxProcessResourceOwner,
|
|
RESOURCE_RELEASE_BEFORE_LOCKS,
|
|
isCommit, true);
|
|
ResourceOwnerRelease(AuxProcessResourceOwner,
|
|
RESOURCE_RELEASE_LOCKS,
|
|
isCommit, true);
|
|
ResourceOwnerRelease(AuxProcessResourceOwner,
|
|
RESOURCE_RELEASE_AFTER_LOCKS,
|
|
isCommit, true);
|
|
}
|
|
|
|
/*
|
|
* Shmem-exit callback for the same.
|
|
* Warn about leaked resources if process exit code is zero (ie normal).
|
|
*/
|
|
static void
|
|
ReleaseAuxProcessResourcesCallback(int code, Datum arg)
|
|
{
|
|
bool isCommit = (code == 0);
|
|
|
|
ReleaseAuxProcessResources(isCommit);
|
|
}
|
|
|
|
|
|
/*
|
|
* Make sure there is room for at least one more entry in a ResourceOwner's
|
|
* buffer array.
|
|
*
|
|
* This is separate from actually inserting an entry because if we run out
|
|
* of memory, it's critical to do so *before* acquiring the resource.
|
|
*/
|
|
void
|
|
ResourceOwnerEnlargeBuffers(ResourceOwner owner)
|
|
{
|
|
/* We used to allow pinning buffers without a resowner, but no more */
|
|
Assert(owner != NULL);
|
|
ResourceArrayEnlarge(&(owner->bufferarr));
|
|
}
|
|
|
|
/*
|
|
* Remember that a buffer pin is owned by a ResourceOwner
|
|
*
|
|
* Caller must have previously done ResourceOwnerEnlargeBuffers()
|
|
*/
|
|
void
|
|
ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer)
|
|
{
|
|
ResourceArrayAdd(&(owner->bufferarr), BufferGetDatum(buffer));
|
|
}
|
|
|
|
/*
|
|
* Forget that a buffer pin is owned by a ResourceOwner
|
|
*/
|
|
void
|
|
ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer)
|
|
{
|
|
if (!ResourceArrayRemove(&(owner->bufferarr), BufferGetDatum(buffer)))
|
|
elog(ERROR, "buffer %d is not owned by resource owner %s",
|
|
buffer, owner->name);
|
|
}
|
|
|
|
|
|
/*
|
|
* Make sure there is room for at least one more entry in a ResourceOwner's
|
|
* buffer array.
|
|
*
|
|
* This is separate from actually inserting an entry because if we run out
|
|
* of memory, it's critical to do so *before* acquiring the resource.
|
|
*/
|
|
void
|
|
ResourceOwnerEnlargeBufferIOs(ResourceOwner owner)
|
|
{
|
|
/* We used to allow pinning buffers without a resowner, but no more */
|
|
Assert(owner != NULL);
|
|
ResourceArrayEnlarge(&(owner->bufferioarr));
|
|
}
|
|
|
|
/*
|
|
* Remember that a buffer IO is owned by a ResourceOwner
|
|
*
|
|
* Caller must have previously done ResourceOwnerEnlargeBufferIOs()
|
|
*/
|
|
void
|
|
ResourceOwnerRememberBufferIO(ResourceOwner owner, Buffer buffer)
|
|
{
|
|
ResourceArrayAdd(&(owner->bufferioarr), BufferGetDatum(buffer));
|
|
}
|
|
|
|
/*
|
|
* Forget that a buffer IO is owned by a ResourceOwner
|
|
*/
|
|
void
|
|
ResourceOwnerForgetBufferIO(ResourceOwner owner, Buffer buffer)
|
|
{
|
|
if (!ResourceArrayRemove(&(owner->bufferioarr), BufferGetDatum(buffer)))
|
|
elog(PANIC, "buffer IO %d is not owned by resource owner %s",
|
|
buffer, owner->name);
|
|
}
|
|
|
|
/*
|
|
* Remember that a Local Lock is owned by a ResourceOwner
|
|
*
|
|
* This is different from the other Remember functions in that the list of
|
|
* locks is only a lossy cache. It can hold up to MAX_RESOWNER_LOCKS entries,
|
|
* and when it overflows, we stop tracking locks. The point of only remembering
|
|
* only up to MAX_RESOWNER_LOCKS entries is that if a lot of locks are held,
|
|
* ResourceOwnerForgetLock doesn't need to scan through a large array to find
|
|
* the entry.
|
|
*/
|
|
void
|
|
ResourceOwnerRememberLock(ResourceOwner owner, LOCALLOCK *locallock)
|
|
{
|
|
Assert(locallock != NULL);
|
|
|
|
if (owner->nlocks > MAX_RESOWNER_LOCKS)
|
|
return; /* we have already overflowed */
|
|
|
|
if (owner->nlocks < MAX_RESOWNER_LOCKS)
|
|
owner->locks[owner->nlocks] = locallock;
|
|
else
|
|
{
|
|
/* overflowed */
|
|
}
|
|
owner->nlocks++;
|
|
}
|
|
|
|
/*
|
|
* Forget that a Local Lock is owned by a ResourceOwner
|
|
*/
|
|
void
|
|
ResourceOwnerForgetLock(ResourceOwner owner, LOCALLOCK *locallock)
|
|
{
|
|
int i;
|
|
|
|
if (owner->nlocks > MAX_RESOWNER_LOCKS)
|
|
return; /* we have overflowed */
|
|
|
|
Assert(owner->nlocks > 0);
|
|
for (i = owner->nlocks - 1; i >= 0; i--)
|
|
{
|
|
if (locallock == owner->locks[i])
|
|
{
|
|
owner->locks[i] = owner->locks[owner->nlocks - 1];
|
|
owner->nlocks--;
|
|
return;
|
|
}
|
|
}
|
|
elog(ERROR, "lock reference %p is not owned by resource owner %s",
|
|
locallock, owner->name);
|
|
}
|
|
|
|
/*
|
|
* Make sure there is room for at least one more entry in a ResourceOwner's
|
|
* catcache reference array.
|
|
*
|
|
* This is separate from actually inserting an entry because if we run out
|
|
* of memory, it's critical to do so *before* acquiring the resource.
|
|
*/
|
|
void
|
|
ResourceOwnerEnlargeCatCacheRefs(ResourceOwner owner)
|
|
{
|
|
ResourceArrayEnlarge(&(owner->catrefarr));
|
|
}
|
|
|
|
/*
|
|
* Remember that a catcache reference is owned by a ResourceOwner
|
|
*
|
|
* Caller must have previously done ResourceOwnerEnlargeCatCacheRefs()
|
|
*/
|
|
void
|
|
ResourceOwnerRememberCatCacheRef(ResourceOwner owner, HeapTuple tuple)
|
|
{
|
|
ResourceArrayAdd(&(owner->catrefarr), PointerGetDatum(tuple));
|
|
}
|
|
|
|
/*
|
|
* Forget that a catcache reference is owned by a ResourceOwner
|
|
*/
|
|
void
|
|
ResourceOwnerForgetCatCacheRef(ResourceOwner owner, HeapTuple tuple)
|
|
{
|
|
if (!ResourceArrayRemove(&(owner->catrefarr), PointerGetDatum(tuple)))
|
|
elog(ERROR, "catcache reference %p is not owned by resource owner %s",
|
|
tuple, owner->name);
|
|
}
|
|
|
|
/*
|
|
* Make sure there is room for at least one more entry in a ResourceOwner's
|
|
* catcache-list reference array.
|
|
*
|
|
* This is separate from actually inserting an entry because if we run out
|
|
* of memory, it's critical to do so *before* acquiring the resource.
|
|
*/
|
|
void
|
|
ResourceOwnerEnlargeCatCacheListRefs(ResourceOwner owner)
|
|
{
|
|
ResourceArrayEnlarge(&(owner->catlistrefarr));
|
|
}
|
|
|
|
/*
|
|
* Remember that a catcache-list reference is owned by a ResourceOwner
|
|
*
|
|
* Caller must have previously done ResourceOwnerEnlargeCatCacheListRefs()
|
|
*/
|
|
void
|
|
ResourceOwnerRememberCatCacheListRef(ResourceOwner owner, CatCList *list)
|
|
{
|
|
ResourceArrayAdd(&(owner->catlistrefarr), PointerGetDatum(list));
|
|
}
|
|
|
|
/*
|
|
* Forget that a catcache-list reference is owned by a ResourceOwner
|
|
*/
|
|
void
|
|
ResourceOwnerForgetCatCacheListRef(ResourceOwner owner, CatCList *list)
|
|
{
|
|
if (!ResourceArrayRemove(&(owner->catlistrefarr), PointerGetDatum(list)))
|
|
elog(ERROR, "catcache list reference %p is not owned by resource owner %s",
|
|
list, owner->name);
|
|
}
|
|
|
|
/*
|
|
* Make sure there is room for at least one more entry in a ResourceOwner's
|
|
* relcache reference array.
|
|
*
|
|
* This is separate from actually inserting an entry because if we run out
|
|
* of memory, it's critical to do so *before* acquiring the resource.
|
|
*/
|
|
void
|
|
ResourceOwnerEnlargeRelationRefs(ResourceOwner owner)
|
|
{
|
|
ResourceArrayEnlarge(&(owner->relrefarr));
|
|
}
|
|
|
|
/*
|
|
* Remember that a relcache reference is owned by a ResourceOwner
|
|
*
|
|
* Caller must have previously done ResourceOwnerEnlargeRelationRefs()
|
|
*/
|
|
void
|
|
ResourceOwnerRememberRelationRef(ResourceOwner owner, Relation rel)
|
|
{
|
|
ResourceArrayAdd(&(owner->relrefarr), PointerGetDatum(rel));
|
|
}
|
|
|
|
/*
|
|
* Forget that a relcache reference is owned by a ResourceOwner
|
|
*/
|
|
void
|
|
ResourceOwnerForgetRelationRef(ResourceOwner owner, Relation rel)
|
|
{
|
|
if (!ResourceArrayRemove(&(owner->relrefarr), PointerGetDatum(rel)))
|
|
elog(ERROR, "relcache reference %s is not owned by resource owner %s",
|
|
RelationGetRelationName(rel), owner->name);
|
|
}
|
|
|
|
/*
|
|
* Debugging subroutine
|
|
*/
|
|
static void
|
|
PrintRelCacheLeakWarning(Relation rel)
|
|
{
|
|
elog(WARNING, "relcache reference leak: relation \"%s\" not closed",
|
|
RelationGetRelationName(rel));
|
|
}
|
|
|
|
/*
|
|
* Make sure there is room for at least one more entry in a ResourceOwner's
|
|
* plancache reference array.
|
|
*
|
|
* This is separate from actually inserting an entry because if we run out
|
|
* of memory, it's critical to do so *before* acquiring the resource.
|
|
*/
|
|
void
|
|
ResourceOwnerEnlargePlanCacheRefs(ResourceOwner owner)
|
|
{
|
|
ResourceArrayEnlarge(&(owner->planrefarr));
|
|
}
|
|
|
|
/*
|
|
* Remember that a plancache reference is owned by a ResourceOwner
|
|
*
|
|
* Caller must have previously done ResourceOwnerEnlargePlanCacheRefs()
|
|
*/
|
|
void
|
|
ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
|
|
{
|
|
ResourceArrayAdd(&(owner->planrefarr), PointerGetDatum(plan));
|
|
}
|
|
|
|
/*
|
|
* Forget that a plancache reference is owned by a ResourceOwner
|
|
*/
|
|
void
|
|
ResourceOwnerForgetPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
|
|
{
|
|
if (!ResourceArrayRemove(&(owner->planrefarr), PointerGetDatum(plan)))
|
|
elog(ERROR, "plancache reference %p is not owned by resource owner %s",
|
|
plan, owner->name);
|
|
}
|
|
|
|
/*
|
|
* Debugging subroutine
|
|
*/
|
|
static void
|
|
PrintPlanCacheLeakWarning(CachedPlan *plan)
|
|
{
|
|
elog(WARNING, "plancache reference leak: plan %p not closed", plan);
|
|
}
|
|
|
|
/*
|
|
* Make sure there is room for at least one more entry in a ResourceOwner's
|
|
* tupdesc reference array.
|
|
*
|
|
* This is separate from actually inserting an entry because if we run out
|
|
* of memory, it's critical to do so *before* acquiring the resource.
|
|
*/
|
|
void
|
|
ResourceOwnerEnlargeTupleDescs(ResourceOwner owner)
|
|
{
|
|
ResourceArrayEnlarge(&(owner->tupdescarr));
|
|
}
|
|
|
|
/*
|
|
* Remember that a tupdesc reference is owned by a ResourceOwner
|
|
*
|
|
* Caller must have previously done ResourceOwnerEnlargeTupleDescs()
|
|
*/
|
|
void
|
|
ResourceOwnerRememberTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
|
|
{
|
|
ResourceArrayAdd(&(owner->tupdescarr), PointerGetDatum(tupdesc));
|
|
}
|
|
|
|
/*
|
|
* Forget that a tupdesc reference is owned by a ResourceOwner
|
|
*/
|
|
void
|
|
ResourceOwnerForgetTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
|
|
{
|
|
if (!ResourceArrayRemove(&(owner->tupdescarr), PointerGetDatum(tupdesc)))
|
|
elog(ERROR, "tupdesc reference %p is not owned by resource owner %s",
|
|
tupdesc, owner->name);
|
|
}
|
|
|
|
/*
|
|
* Debugging subroutine
|
|
*/
|
|
static void
|
|
PrintTupleDescLeakWarning(TupleDesc tupdesc)
|
|
{
|
|
elog(WARNING,
|
|
"TupleDesc reference leak: TupleDesc %p (%u,%d) still referenced",
|
|
tupdesc, tupdesc->tdtypeid, tupdesc->tdtypmod);
|
|
}
|
|
|
|
/*
|
|
* Make sure there is room for at least one more entry in a ResourceOwner's
|
|
* snapshot reference array.
|
|
*
|
|
* This is separate from actually inserting an entry because if we run out
|
|
* of memory, it's critical to do so *before* acquiring the resource.
|
|
*/
|
|
void
|
|
ResourceOwnerEnlargeSnapshots(ResourceOwner owner)
|
|
{
|
|
ResourceArrayEnlarge(&(owner->snapshotarr));
|
|
}
|
|
|
|
/*
|
|
* Remember that a snapshot reference is owned by a ResourceOwner
|
|
*
|
|
* Caller must have previously done ResourceOwnerEnlargeSnapshots()
|
|
*/
|
|
void
|
|
ResourceOwnerRememberSnapshot(ResourceOwner owner, Snapshot snapshot)
|
|
{
|
|
ResourceArrayAdd(&(owner->snapshotarr), PointerGetDatum(snapshot));
|
|
}
|
|
|
|
/*
|
|
* Forget that a snapshot reference is owned by a ResourceOwner
|
|
*/
|
|
void
|
|
ResourceOwnerForgetSnapshot(ResourceOwner owner, Snapshot snapshot)
|
|
{
|
|
if (!ResourceArrayRemove(&(owner->snapshotarr), PointerGetDatum(snapshot)))
|
|
elog(ERROR, "snapshot reference %p is not owned by resource owner %s",
|
|
snapshot, owner->name);
|
|
}
|
|
|
|
/*
|
|
* Debugging subroutine
|
|
*/
|
|
static void
|
|
PrintSnapshotLeakWarning(Snapshot snapshot)
|
|
{
|
|
elog(WARNING, "Snapshot reference leak: Snapshot %p still referenced",
|
|
snapshot);
|
|
}
|
|
|
|
|
|
/*
|
|
* Make sure there is room for at least one more entry in a ResourceOwner's
|
|
* files reference array.
|
|
*
|
|
* This is separate from actually inserting an entry because if we run out
|
|
* of memory, it's critical to do so *before* acquiring the resource.
|
|
*/
|
|
void
|
|
ResourceOwnerEnlargeFiles(ResourceOwner owner)
|
|
{
|
|
ResourceArrayEnlarge(&(owner->filearr));
|
|
}
|
|
|
|
/*
|
|
* Remember that a temporary file is owned by a ResourceOwner
|
|
*
|
|
* Caller must have previously done ResourceOwnerEnlargeFiles()
|
|
*/
|
|
void
|
|
ResourceOwnerRememberFile(ResourceOwner owner, File file)
|
|
{
|
|
ResourceArrayAdd(&(owner->filearr), FileGetDatum(file));
|
|
}
|
|
|
|
/*
|
|
* Forget that a temporary file is owned by a ResourceOwner
|
|
*/
|
|
void
|
|
ResourceOwnerForgetFile(ResourceOwner owner, File file)
|
|
{
|
|
if (!ResourceArrayRemove(&(owner->filearr), FileGetDatum(file)))
|
|
elog(ERROR, "temporary file %d is not owned by resource owner %s",
|
|
file, owner->name);
|
|
}
|
|
|
|
/*
|
|
* Debugging subroutine
|
|
*/
|
|
static void
|
|
PrintFileLeakWarning(File file)
|
|
{
|
|
elog(WARNING, "temporary file leak: File %d still referenced",
|
|
file);
|
|
}
|
|
|
|
/*
|
|
* Make sure there is room for at least one more entry in a ResourceOwner's
|
|
* dynamic shmem segment reference array.
|
|
*
|
|
* This is separate from actually inserting an entry because if we run out
|
|
* of memory, it's critical to do so *before* acquiring the resource.
|
|
*/
|
|
void
|
|
ResourceOwnerEnlargeDSMs(ResourceOwner owner)
|
|
{
|
|
ResourceArrayEnlarge(&(owner->dsmarr));
|
|
}
|
|
|
|
/*
|
|
* Remember that a dynamic shmem segment is owned by a ResourceOwner
|
|
*
|
|
* Caller must have previously done ResourceOwnerEnlargeDSMs()
|
|
*/
|
|
void
|
|
ResourceOwnerRememberDSM(ResourceOwner owner, dsm_segment *seg)
|
|
{
|
|
ResourceArrayAdd(&(owner->dsmarr), PointerGetDatum(seg));
|
|
}
|
|
|
|
/*
|
|
* Forget that a dynamic shmem segment is owned by a ResourceOwner
|
|
*/
|
|
void
|
|
ResourceOwnerForgetDSM(ResourceOwner owner, dsm_segment *seg)
|
|
{
|
|
if (!ResourceArrayRemove(&(owner->dsmarr), PointerGetDatum(seg)))
|
|
elog(ERROR, "dynamic shared memory segment %u is not owned by resource owner %s",
|
|
dsm_segment_handle(seg), owner->name);
|
|
}
|
|
|
|
/*
|
|
* Debugging subroutine
|
|
*/
|
|
static void
|
|
PrintDSMLeakWarning(dsm_segment *seg)
|
|
{
|
|
elog(WARNING, "dynamic shared memory leak: segment %u still referenced",
|
|
dsm_segment_handle(seg));
|
|
}
|
|
|
|
/*
|
|
* Make sure there is room for at least one more entry in a ResourceOwner's
|
|
* JIT context reference array.
|
|
*
|
|
* This is separate from actually inserting an entry because if we run out of
|
|
* memory, it's critical to do so *before* acquiring the resource.
|
|
*/
|
|
void
|
|
ResourceOwnerEnlargeJIT(ResourceOwner owner)
|
|
{
|
|
ResourceArrayEnlarge(&(owner->jitarr));
|
|
}
|
|
|
|
/*
|
|
* Remember that a JIT context is owned by a ResourceOwner
|
|
*
|
|
* Caller must have previously done ResourceOwnerEnlargeJIT()
|
|
*/
|
|
void
|
|
ResourceOwnerRememberJIT(ResourceOwner owner, Datum handle)
|
|
{
|
|
ResourceArrayAdd(&(owner->jitarr), handle);
|
|
}
|
|
|
|
/*
|
|
* Forget that a JIT context is owned by a ResourceOwner
|
|
*/
|
|
void
|
|
ResourceOwnerForgetJIT(ResourceOwner owner, Datum handle)
|
|
{
|
|
if (!ResourceArrayRemove(&(owner->jitarr), handle))
|
|
elog(ERROR, "JIT context %p is not owned by resource owner %s",
|
|
DatumGetPointer(handle), owner->name);
|
|
}
|
|
|
|
/*
|
|
* Make sure there is room for at least one more entry in a ResourceOwner's
|
|
* cryptohash context reference array.
|
|
*
|
|
* This is separate from actually inserting an entry because if we run out of
|
|
* memory, it's critical to do so *before* acquiring the resource.
|
|
*/
|
|
void
|
|
ResourceOwnerEnlargeCryptoHash(ResourceOwner owner)
|
|
{
|
|
ResourceArrayEnlarge(&(owner->cryptohasharr));
|
|
}
|
|
|
|
/*
|
|
* Remember that a cryptohash context is owned by a ResourceOwner
|
|
*
|
|
* Caller must have previously done ResourceOwnerEnlargeCryptoHash()
|
|
*/
|
|
void
|
|
ResourceOwnerRememberCryptoHash(ResourceOwner owner, Datum handle)
|
|
{
|
|
ResourceArrayAdd(&(owner->cryptohasharr), handle);
|
|
}
|
|
|
|
/*
|
|
* Forget that a cryptohash context is owned by a ResourceOwner
|
|
*/
|
|
void
|
|
ResourceOwnerForgetCryptoHash(ResourceOwner owner, Datum handle)
|
|
{
|
|
if (!ResourceArrayRemove(&(owner->cryptohasharr), handle))
|
|
elog(ERROR, "cryptohash context %p is not owned by resource owner %s",
|
|
DatumGetPointer(handle), owner->name);
|
|
}
|
|
|
|
/*
|
|
* Debugging subroutine
|
|
*/
|
|
static void
|
|
PrintCryptoHashLeakWarning(Datum handle)
|
|
{
|
|
elog(WARNING, "cryptohash context reference leak: context %p still referenced",
|
|
DatumGetPointer(handle));
|
|
}
|
|
|
|
/*
|
|
* Make sure there is room for at least one more entry in a ResourceOwner's
|
|
* hmac context reference array.
|
|
*
|
|
* This is separate from actually inserting an entry because if we run out of
|
|
* memory, it's critical to do so *before* acquiring the resource.
|
|
*/
|
|
void
|
|
ResourceOwnerEnlargeHMAC(ResourceOwner owner)
|
|
{
|
|
ResourceArrayEnlarge(&(owner->hmacarr));
|
|
}
|
|
|
|
/*
|
|
* Remember that a HMAC context is owned by a ResourceOwner
|
|
*
|
|
* Caller must have previously done ResourceOwnerEnlargeHMAC()
|
|
*/
|
|
void
|
|
ResourceOwnerRememberHMAC(ResourceOwner owner, Datum handle)
|
|
{
|
|
ResourceArrayAdd(&(owner->hmacarr), handle);
|
|
}
|
|
|
|
/*
|
|
* Forget that a HMAC context is owned by a ResourceOwner
|
|
*/
|
|
void
|
|
ResourceOwnerForgetHMAC(ResourceOwner owner, Datum handle)
|
|
{
|
|
if (!ResourceArrayRemove(&(owner->hmacarr), handle))
|
|
elog(ERROR, "HMAC context %p is not owned by resource owner %s",
|
|
DatumGetPointer(handle), owner->name);
|
|
}
|
|
|
|
/*
|
|
* Debugging subroutine
|
|
*/
|
|
static void
|
|
PrintHMACLeakWarning(Datum handle)
|
|
{
|
|
elog(WARNING, "HMAC context reference leak: context %p still referenced",
|
|
DatumGetPointer(handle));
|
|
}
|