/*------------------------------------------------------------------------- * * 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-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/hashfn.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 * *****************************************************************************/ static inline uint32 hash_resource_elem(Datum value, const ResourceOwnerDesc *kind) { Datum data[2]; data[0] = value; data[1] = PointerGetDatum(kind); return hash_bytes((unsigned char *) &data, 2 * SIZEOF_DATUM); } /* * 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) { if (ra->kind->release_priority == rb->kind->release_priority) return 0; else if (ra->kind->release_priority > rb->kind->release_priority) return -1; else return 1; } 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 */ Assert(owner->releasing); Assert(owner->sorted); if (!owner->hash) { 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->hash) 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. */ 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); }