1085 lines
30 KiB
C
1085 lines
30 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 on how to use it.
|
|
*
|
|
* The implementation consists of a small fixed-size array and a hash table.
|
|
* New entries are inserted to the fixed-size array, and when the array
|
|
* fills up, all the entries are moved to the hash table. This way, the
|
|
* array always contains a few most recently remembered references. To find
|
|
* a particular reference, you need to search both the array and the hash
|
|
* table.
|
|
*
|
|
* The most frequent usage is that a resource is remembered, and forgotten
|
|
* shortly thereafter. For example, pin a buffer, read one tuple from it,
|
|
* release the pin. Linearly scanning the small array handles that case
|
|
* efficiently. However, some resources are held for a longer time, and
|
|
* sometimes a lot of resources need to be held simultaneously. The hash
|
|
* table handles those cases.
|
|
*
|
|
* When it's time to release the resources, we sort them according to the
|
|
* release-priority of each resource, and release them in that order.
|
|
*
|
|
* Local lock references are special, they are not stored in the array or
|
|
* the hash table. Instead, each resource owner has a separate 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 the cache overflows, but
|
|
* traversing the hash table is slower when there are a lot of locks
|
|
* belonging to other resource owners. This is to speed up bulk releasing
|
|
* or reassigning locks from a resource owner to its parent.
|
|
*
|
|
*
|
|
* Portions Copyright (c) 1996-2024, 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/hashfn.h"
|
|
#include "common/int.h"
|
|
#include "storage/ipc.h"
|
|
#include "storage/predicate.h"
|
|
#include "storage/proc.h"
|
|
#include "utils/memutils.h"
|
|
#include "utils/resowner.h"
|
|
|
|
/*
|
|
* ResourceElem represents a reference associated with a resource owner.
|
|
*
|
|
* All objects managed by this code are required to fit into a Datum,
|
|
* which is fine since they are generally pointers or integers.
|
|
*/
|
|
typedef struct ResourceElem
|
|
{
|
|
Datum item;
|
|
const ResourceOwnerDesc *kind; /* NULL indicates a free hash table slot */
|
|
} ResourceElem;
|
|
|
|
/*
|
|
* Size of the fixed-size array to hold most-recently remembered resources.
|
|
*/
|
|
#define RESOWNER_ARRAY_SIZE 32
|
|
|
|
/*
|
|
* Initially allocated size of a ResourceOwner's hash table. Must be power of
|
|
* two because we use (capacity - 1) as mask for hashing.
|
|
*/
|
|
#define RESOWNER_HASH_INIT_SIZE 64
|
|
|
|
/*
|
|
* How many items may be stored in a hash table of given capacity. When this
|
|
* number is reached, we must resize.
|
|
*
|
|
* The hash table must always have enough free space that we can copy the
|
|
* entries from the array to it, in ResourceOwnerSort. We also insist that
|
|
* the initial size is large enough that we don't hit the max size immediately
|
|
* when it's created. Aside from those limitations, 0.75 is a reasonable fill
|
|
* factor.
|
|
*/
|
|
#define RESOWNER_HASH_MAX_ITEMS(capacity) \
|
|
Min(capacity - RESOWNER_ARRAY_SIZE, (capacity)/4 * 3)
|
|
|
|
StaticAssertDecl(RESOWNER_HASH_MAX_ITEMS(RESOWNER_HASH_INIT_SIZE) >= RESOWNER_ARRAY_SIZE,
|
|
"initial hash size too small compared to array size");
|
|
|
|
/*
|
|
* MAX_RESOWNER_LOCKS is the size of the per-resource owner locks 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) */
|
|
|
|
/*
|
|
* When ResourceOwnerRelease is called, we sort the 'hash' and 'arr' by
|
|
* the release priority. After that, no new resources can be remembered
|
|
* or forgotten in retail. We have separate flags because
|
|
* ResourceOwnerReleaseAllOfKind() temporarily sets 'releasing' without
|
|
* sorting the arrays.
|
|
*/
|
|
bool releasing;
|
|
bool sorted; /* are 'hash' and 'arr' sorted by priority? */
|
|
|
|
/*
|
|
* Number of items in the locks cache, array, and hash table respectively.
|
|
* (These are packed together to avoid padding in the struct.)
|
|
*/
|
|
uint8 nlocks; /* number of owned locks */
|
|
uint8 narr; /* how many items are stored in the array */
|
|
uint32 nhash; /* how many items are stored in the hash */
|
|
|
|
/*
|
|
* The fixed-size array for recent resources.
|
|
*
|
|
* If 'sorted' is set, the contents are sorted by release priority.
|
|
*/
|
|
ResourceElem arr[RESOWNER_ARRAY_SIZE];
|
|
|
|
/*
|
|
* The hash table. Uses open-addressing. 'nhash' is the number of items
|
|
* present; if it would exceed 'grow_at', we enlarge it and re-hash.
|
|
* 'grow_at' should be rather less than 'capacity' so that we don't waste
|
|
* too much time searching for empty slots.
|
|
*
|
|
* If 'sorted' is set, the contents are no longer hashed, but sorted by
|
|
* release priority. The first 'nhash' elements are occupied, the rest
|
|
* are empty.
|
|
*/
|
|
ResourceElem *hash;
|
|
uint32 capacity; /* allocated length of hash[] */
|
|
uint32 grow_at; /* grow hash when reach this */
|
|
|
|
/* The local locks cache. */
|
|
LOCALLOCK *locks[MAX_RESOWNER_LOCKS]; /* list of owned locks */
|
|
} ResourceOwnerData;
|
|
|
|
|
|
/*****************************************************************************
|
|
* GLOBAL MEMORY *
|
|
*****************************************************************************/
|
|
|
|
ResourceOwner CurrentResourceOwner = NULL;
|
|
ResourceOwner CurTransactionResourceOwner = NULL;
|
|
ResourceOwner TopTransactionResourceOwner = NULL;
|
|
ResourceOwner AuxProcessResourceOwner = NULL;
|
|
|
|
/* #define RESOWNER_STATS */
|
|
|
|
#ifdef RESOWNER_STATS
|
|
static int narray_lookups = 0;
|
|
static int nhash_lookups = 0;
|
|
#endif
|
|
|
|
/*
|
|
* 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 inline uint32 hash_resource_elem(Datum value, const ResourceOwnerDesc *kind);
|
|
static void ResourceOwnerAddToHash(ResourceOwner owner, Datum value,
|
|
const ResourceOwnerDesc *kind);
|
|
static int resource_priority_cmp(const void *a, const void *b);
|
|
static void ResourceOwnerSort(ResourceOwner owner);
|
|
static void ResourceOwnerReleaseAll(ResourceOwner owner,
|
|
ResourceReleasePhase phase,
|
|
bool printLeakWarnings);
|
|
static void ResourceOwnerReleaseInternal(ResourceOwner owner,
|
|
ResourceReleasePhase phase,
|
|
bool isCommit,
|
|
bool isTopLevel);
|
|
static void ReleaseAuxProcessResourcesCallback(int code, Datum arg);
|
|
|
|
|
|
/*****************************************************************************
|
|
* INTERNAL ROUTINES *
|
|
*****************************************************************************/
|
|
|
|
/*
|
|
* Hash function for value+kind combination.
|
|
*/
|
|
static inline uint32
|
|
hash_resource_elem(Datum value, const ResourceOwnerDesc *kind)
|
|
{
|
|
/*
|
|
* Most resource kinds store a pointer in 'value', and pointers are unique
|
|
* all on their own. But some resources store plain integers (Files and
|
|
* Buffers as of this writing), so we want to incorporate the 'kind' in
|
|
* the hash too, otherwise those resources will collide a lot. But
|
|
* because there are only a few resource kinds like that - and only a few
|
|
* resource kinds to begin with - we don't need to work too hard to mix
|
|
* 'kind' into the hash. Just add it with hash_combine(), it perturbs the
|
|
* result enough for our purposes.
|
|
*/
|
|
#if SIZEOF_DATUM == 8
|
|
return hash_combine64(murmurhash64((uint64) value), (uint64) kind);
|
|
#else
|
|
return hash_combine(murmurhash32((uint32) value), (uint32) kind);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Adds 'value' of given 'kind' to the ResourceOwner's hash table
|
|
*/
|
|
static void
|
|
ResourceOwnerAddToHash(ResourceOwner owner, Datum value, const ResourceOwnerDesc *kind)
|
|
{
|
|
uint32 mask = owner->capacity - 1;
|
|
uint32 idx;
|
|
|
|
Assert(kind != NULL);
|
|
|
|
/* Insert into first free slot at or after hash location. */
|
|
idx = hash_resource_elem(value, kind) & mask;
|
|
for (;;)
|
|
{
|
|
if (owner->hash[idx].kind == NULL)
|
|
break; /* found a free slot */
|
|
idx = (idx + 1) & mask;
|
|
}
|
|
owner->hash[idx].item = value;
|
|
owner->hash[idx].kind = kind;
|
|
owner->nhash++;
|
|
}
|
|
|
|
/*
|
|
* Comparison function to sort by release phase and priority
|
|
*/
|
|
static int
|
|
resource_priority_cmp(const void *a, const void *b)
|
|
{
|
|
const ResourceElem *ra = (const ResourceElem *) a;
|
|
const ResourceElem *rb = (const ResourceElem *) b;
|
|
|
|
/* Note: reverse order */
|
|
if (ra->kind->release_phase == rb->kind->release_phase)
|
|
return pg_cmp_u32(rb->kind->release_priority, ra->kind->release_priority);
|
|
else if (ra->kind->release_phase > rb->kind->release_phase)
|
|
return -1;
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Sort resources in reverse release priority.
|
|
*
|
|
* If the hash table is in use, all the elements from the fixed-size array are
|
|
* moved to the hash table, and then the hash table is sorted. If there is no
|
|
* hash table, then the fixed-size array is sorted directly. In either case,
|
|
* the result is one sorted array that contains all the resources.
|
|
*/
|
|
static void
|
|
ResourceOwnerSort(ResourceOwner owner)
|
|
{
|
|
ResourceElem *items;
|
|
uint32 nitems;
|
|
|
|
if (owner->nhash == 0)
|
|
{
|
|
items = owner->arr;
|
|
nitems = owner->narr;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Compact the hash table, so that all the elements are in the
|
|
* beginning of the 'hash' array, with no empty elements.
|
|
*/
|
|
uint32 dst = 0;
|
|
|
|
for (int idx = 0; idx < owner->capacity; idx++)
|
|
{
|
|
if (owner->hash[idx].kind != NULL)
|
|
{
|
|
if (dst != idx)
|
|
owner->hash[dst] = owner->hash[idx];
|
|
dst++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Move all entries from the fixed-size array to 'hash'.
|
|
*
|
|
* RESOWNER_HASH_MAX_ITEMS is defined so that there is always enough
|
|
* free space to move all the elements from the fixed-size array to
|
|
* the hash.
|
|
*/
|
|
Assert(dst + owner->narr <= owner->capacity);
|
|
for (int idx = 0; idx < owner->narr; idx++)
|
|
{
|
|
owner->hash[dst] = owner->arr[idx];
|
|
dst++;
|
|
}
|
|
Assert(dst == owner->nhash + owner->narr);
|
|
owner->narr = 0;
|
|
owner->nhash = dst;
|
|
|
|
items = owner->hash;
|
|
nitems = owner->nhash;
|
|
}
|
|
|
|
qsort(items, nitems, sizeof(ResourceElem), resource_priority_cmp);
|
|
}
|
|
|
|
/*
|
|
* Call the ReleaseResource callback on entries with given 'phase'.
|
|
*/
|
|
static void
|
|
ResourceOwnerReleaseAll(ResourceOwner owner, ResourceReleasePhase phase,
|
|
bool printLeakWarnings)
|
|
{
|
|
ResourceElem *items;
|
|
uint32 nitems;
|
|
|
|
/*
|
|
* ResourceOwnerSort must've been called already. All the resources are
|
|
* either in the array or the hash.
|
|
*/
|
|
Assert(owner->releasing);
|
|
Assert(owner->sorted);
|
|
if (owner->nhash == 0)
|
|
{
|
|
items = owner->arr;
|
|
nitems = owner->narr;
|
|
}
|
|
else
|
|
{
|
|
Assert(owner->narr == 0);
|
|
items = owner->hash;
|
|
nitems = owner->nhash;
|
|
}
|
|
|
|
/*
|
|
* The resources are sorted in reverse priority order. Release them
|
|
* starting from the end, until we hit the end of the phase that we are
|
|
* releasing now. We will continue from there when called again for the
|
|
* next phase.
|
|
*/
|
|
while (nitems > 0)
|
|
{
|
|
uint32 idx = nitems - 1;
|
|
Datum value = items[idx].item;
|
|
const ResourceOwnerDesc *kind = items[idx].kind;
|
|
|
|
if (kind->release_phase > phase)
|
|
break;
|
|
Assert(kind->release_phase == phase);
|
|
|
|
if (printLeakWarnings)
|
|
{
|
|
char *res_str;
|
|
|
|
res_str = kind->DebugPrint ?
|
|
kind->DebugPrint(value)
|
|
: psprintf("%s %p", kind->name, DatumGetPointer(value));
|
|
elog(WARNING, "resource was not closed: %s", res_str);
|
|
pfree(res_str);
|
|
}
|
|
kind->ReleaseResource(value);
|
|
nitems--;
|
|
}
|
|
if (owner->nhash == 0)
|
|
owner->narr = nitems;
|
|
else
|
|
owner->nhash = nitems;
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
* 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;
|
|
}
|
|
|
|
return owner;
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*
|
|
* NB: Make sure there are no unrelated ResourceOwnerRemember() calls between
|
|
* your ResourceOwnerEnlarge() call and the ResourceOwnerRemember() call that
|
|
* you reserved the space for!
|
|
*/
|
|
void
|
|
ResourceOwnerEnlarge(ResourceOwner owner)
|
|
{
|
|
/*
|
|
* Mustn't try to remember more resources after we have already started
|
|
* releasing
|
|
*/
|
|
if (owner->releasing)
|
|
elog(ERROR, "ResourceOwnerEnlarge called after release started");
|
|
|
|
if (owner->narr < RESOWNER_ARRAY_SIZE)
|
|
return; /* no work needed */
|
|
|
|
/*
|
|
* Is there space in the hash? If not, enlarge it.
|
|
*/
|
|
if (owner->narr + owner->nhash >= owner->grow_at)
|
|
{
|
|
uint32 i,
|
|
oldcap,
|
|
newcap;
|
|
ResourceElem *oldhash;
|
|
ResourceElem *newhash;
|
|
|
|
oldhash = owner->hash;
|
|
oldcap = owner->capacity;
|
|
|
|
/* Double the capacity (it must stay a power of 2!) */
|
|
newcap = (oldcap > 0) ? oldcap * 2 : RESOWNER_HASH_INIT_SIZE;
|
|
newhash = (ResourceElem *) MemoryContextAllocZero(TopMemoryContext,
|
|
newcap * sizeof(ResourceElem));
|
|
|
|
/*
|
|
* We assume we can't fail below this point, so OK to scribble on the
|
|
* owner
|
|
*/
|
|
owner->hash = newhash;
|
|
owner->capacity = newcap;
|
|
owner->grow_at = RESOWNER_HASH_MAX_ITEMS(newcap);
|
|
owner->nhash = 0;
|
|
|
|
if (oldhash != NULL)
|
|
{
|
|
/*
|
|
* Transfer any pre-existing entries into the new hash table; they
|
|
* don't necessarily go where they were before, so this simple
|
|
* logic is the best way.
|
|
*/
|
|
for (i = 0; i < oldcap; i++)
|
|
{
|
|
if (oldhash[i].kind != NULL)
|
|
ResourceOwnerAddToHash(owner, oldhash[i].item, oldhash[i].kind);
|
|
}
|
|
|
|
/* And release old hash table. */
|
|
pfree(oldhash);
|
|
}
|
|
}
|
|
|
|
/* Move items from the array to the hash */
|
|
for (int i = 0; i < owner->narr; i++)
|
|
ResourceOwnerAddToHash(owner, owner->arr[i].item, owner->arr[i].kind);
|
|
owner->narr = 0;
|
|
|
|
Assert(owner->nhash <= owner->grow_at);
|
|
}
|
|
|
|
/*
|
|
* Remember that an object is owned by a ResourceOwner
|
|
*
|
|
* Caller must have previously done ResourceOwnerEnlarge()
|
|
*/
|
|
void
|
|
ResourceOwnerRemember(ResourceOwner owner, Datum value, const ResourceOwnerDesc *kind)
|
|
{
|
|
uint32 idx;
|
|
|
|
/* sanity check the ResourceOwnerDesc */
|
|
Assert(kind->release_phase != 0);
|
|
Assert(kind->release_priority != 0);
|
|
|
|
/*
|
|
* Mustn't try to remember more resources after we have already started
|
|
* releasing. We already checked this in ResourceOwnerEnlarge.
|
|
*/
|
|
Assert(!owner->releasing);
|
|
Assert(!owner->sorted);
|
|
|
|
if (owner->narr >= RESOWNER_ARRAY_SIZE)
|
|
{
|
|
/* forgot to call ResourceOwnerEnlarge? */
|
|
elog(ERROR, "ResourceOwnerRemember called but array was full");
|
|
}
|
|
|
|
/* Append to the array. */
|
|
idx = owner->narr;
|
|
owner->arr[idx].item = value;
|
|
owner->arr[idx].kind = kind;
|
|
owner->narr++;
|
|
}
|
|
|
|
/*
|
|
* Forget that an object is owned by a ResourceOwner
|
|
*
|
|
* Note: If same resource ID is associated with the ResourceOwner more than
|
|
* once, one instance is removed.
|
|
*
|
|
* Note: Forgetting a resource does not guarantee that there is room to
|
|
* remember a new resource. One exception is when you forget the most
|
|
* recently remembered resource; that does make room for a new remember call.
|
|
* Some code callers rely on that exception.
|
|
*/
|
|
void
|
|
ResourceOwnerForget(ResourceOwner owner, Datum value, const ResourceOwnerDesc *kind)
|
|
{
|
|
/*
|
|
* Mustn't call this after we have already started releasing resources.
|
|
* (Release callback functions are not allowed to release additional
|
|
* resources.)
|
|
*/
|
|
if (owner->releasing)
|
|
elog(ERROR, "ResourceOwnerForget called for %s after release started", kind->name);
|
|
Assert(!owner->sorted);
|
|
|
|
/* Search through all items in the array first. */
|
|
for (int i = owner->narr - 1; i >= 0; i--)
|
|
{
|
|
if (owner->arr[i].item == value &&
|
|
owner->arr[i].kind == kind)
|
|
{
|
|
owner->arr[i] = owner->arr[owner->narr - 1];
|
|
owner->narr--;
|
|
|
|
#ifdef RESOWNER_STATS
|
|
narray_lookups++;
|
|
#endif
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Search hash */
|
|
if (owner->nhash > 0)
|
|
{
|
|
uint32 mask = owner->capacity - 1;
|
|
uint32 idx;
|
|
|
|
idx = hash_resource_elem(value, kind) & mask;
|
|
for (uint32 i = 0; i < owner->capacity; i++)
|
|
{
|
|
if (owner->hash[idx].item == value &&
|
|
owner->hash[idx].kind == kind)
|
|
{
|
|
owner->hash[idx].item = (Datum) 0;
|
|
owner->hash[idx].kind = NULL;
|
|
owner->nhash--;
|
|
|
|
#ifdef RESOWNER_STATS
|
|
nhash_lookups++;
|
|
#endif
|
|
return;
|
|
}
|
|
idx = (idx + 1) & mask;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Use %p to print the reference, since most objects tracked by a resource
|
|
* owner are pointers. It's a bit misleading if it's not a pointer, but
|
|
* this is a programmer error, anyway.
|
|
*/
|
|
elog(ERROR, "%s %p is not owned by resource owner %s",
|
|
kind->name, DatumGetPointer(value), owner->name);
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*
|
|
* NOTE: After starting the release process, by calling this function, no new
|
|
* resources can be remembered in the resource owner. You also cannot call
|
|
* ResourceOwnerForget on any previously remembered resources to release
|
|
* resources "in retail" after that, you must let the bulk release take care
|
|
* of them.
|
|
*/
|
|
void
|
|
ResourceOwnerRelease(ResourceOwner owner,
|
|
ResourceReleasePhase phase,
|
|
bool isCommit,
|
|
bool isTopLevel)
|
|
{
|
|
/* There's not currently any setup needed before recursing */
|
|
ResourceOwnerReleaseInternal(owner, phase, isCommit, isTopLevel);
|
|
|
|
#ifdef RESOWNER_STATS
|
|
if (isTopLevel)
|
|
{
|
|
elog(LOG, "RESOWNER STATS: lookups: array %d, hash %d",
|
|
narray_lookups, nhash_lookups);
|
|
narray_lookups = 0;
|
|
nhash_lookups = 0;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
ResourceOwnerReleaseInternal(ResourceOwner owner,
|
|
ResourceReleasePhase phase,
|
|
bool isCommit,
|
|
bool isTopLevel)
|
|
{
|
|
ResourceOwner child;
|
|
ResourceOwner save;
|
|
ResourceReleaseCallbackItem *item;
|
|
ResourceReleaseCallbackItem *next;
|
|
|
|
/* Recurse to handle descendants */
|
|
for (child = owner->firstchild; child != NULL; child = child->nextchild)
|
|
ResourceOwnerReleaseInternal(child, phase, isCommit, isTopLevel);
|
|
|
|
/*
|
|
* To release the resources in the right order, sort them by phase and
|
|
* priority.
|
|
*
|
|
* The ReleaseResource callback functions are not allowed to remember or
|
|
* forget any other resources after this. Otherwise we lose track of where
|
|
* we are in processing the hash/array.
|
|
*/
|
|
if (!owner->releasing)
|
|
{
|
|
Assert(phase == RESOURCE_RELEASE_BEFORE_LOCKS);
|
|
Assert(!owner->sorted);
|
|
owner->releasing = true;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Phase is normally > RESOURCE_RELEASE_BEFORE_LOCKS, if this is not
|
|
* the first call to ResourceOwnerRelease. But if an error happens
|
|
* between the release phases, we might get called again for the same
|
|
* ResourceOwner from AbortTransaction.
|
|
*/
|
|
}
|
|
if (!owner->sorted)
|
|
{
|
|
ResourceOwnerSort(owner);
|
|
owner->sorted = true;
|
|
}
|
|
|
|
/*
|
|
* Make CurrentResourceOwner point to me, so that the release callback
|
|
* functions know which resource owner is been released.
|
|
*/
|
|
save = CurrentResourceOwner;
|
|
CurrentResourceOwner = owner;
|
|
|
|
if (phase == RESOURCE_RELEASE_BEFORE_LOCKS)
|
|
{
|
|
/*
|
|
* Release all resources that need to be released before the locks.
|
|
*
|
|
* During a commit, there shouldn't be any remaining resources ---
|
|
* that would indicate failure to clean up the executor correctly ---
|
|
* so issue warnings. In the abort case, just clean up quietly.
|
|
*/
|
|
ResourceOwnerReleaseAll(owner, phase, isCommit);
|
|
}
|
|
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 all resources that need to be released after the locks.
|
|
*/
|
|
ResourceOwnerReleaseAll(owner, phase, isCommit);
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/*
|
|
* ResourceOwnerReleaseAllOfKind
|
|
* Release all resources of a certain type held by this owner.
|
|
*/
|
|
void
|
|
ResourceOwnerReleaseAllOfKind(ResourceOwner owner, const ResourceOwnerDesc *kind)
|
|
{
|
|
/* Mustn't call this after we have already started releasing resources. */
|
|
if (owner->releasing)
|
|
elog(ERROR, "ResourceOwnerForget called for %s after release started", kind->name);
|
|
Assert(!owner->sorted);
|
|
|
|
/*
|
|
* Temporarily set 'releasing', to prevent calls to ResourceOwnerRemember
|
|
* while we're scanning the owner. Enlarging the hash would cause us to
|
|
* lose track of the point we're scanning.
|
|
*/
|
|
owner->releasing = true;
|
|
|
|
/* Array first */
|
|
for (int i = 0; i < owner->narr; i++)
|
|
{
|
|
if (owner->arr[i].kind == kind)
|
|
{
|
|
Datum value = owner->arr[i].item;
|
|
|
|
owner->arr[i] = owner->arr[owner->narr - 1];
|
|
owner->narr--;
|
|
i--;
|
|
|
|
kind->ReleaseResource(value);
|
|
}
|
|
}
|
|
|
|
/* Then hash */
|
|
for (int i = 0; i < owner->capacity; i++)
|
|
{
|
|
if (owner->hash[i].kind == kind)
|
|
{
|
|
Datum value = owner->hash[i].item;
|
|
|
|
owner->hash[i].item = (Datum) 0;
|
|
owner->hash[i].kind = NULL;
|
|
owner->nhash--;
|
|
|
|
kind->ReleaseResource(value);
|
|
}
|
|
}
|
|
owner->releasing = false;
|
|
}
|
|
|
|
/*
|
|
* 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->narr == 0);
|
|
Assert(owner->nhash == 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. */
|
|
if (owner->hash)
|
|
pfree(owner->hash);
|
|
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 can be used by dynamically loaded modules. These used
|
|
* to be the only way for an extension to register custom resource types
|
|
* with a resource owner, but nowadays it is easier to define a new
|
|
* ResourceOwnerDesc with custom callbacks.
|
|
*/
|
|
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);
|
|
/* allow it to be reused */
|
|
AuxProcessResourceOwner->releasing = false;
|
|
AuxProcessResourceOwner->sorted = false;
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
|
|
/*
|
|
* Remember that a Local Lock is owned by a ResourceOwner
|
|
*
|
|
* This is different from the generic ResourceOwnerRemember 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);
|
|
}
|