blob: b45c92c77567f123365ba277f9a5df30a675fcef [file] [log] [blame]
/*-------------------------------------------------------------------------
*
* 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-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/resowner/resowner.c,v 1.22 2006/07/14 14:52:25 momjian Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/hash.h"
#include "cdb/cdbvars.h"
#include "storage/proc.h"
#include "utils/memutils.h"
#include "utils/resowner.h"
#include "utils/relcache.h"
#include "executor/execdesc.h"
#include "utils/resscheduler.h"
/*
* 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 owned buffers */
int nbuffers; /* number of owned buffer pins */
Buffer *buffers; /* dynamically allocated array */
int maxbuffers; /* currently allocated array size */
/* We have built-in support for remembering catcache references */
int ncatrefs; /* number of owned catcache pins */
HeapTuple *catrefs; /* dynamically allocated array */
int maxcatrefs; /* currently allocated array size */
int ncatlistrefs; /* number of owned catcache-list pins */
CatCList **catlistrefs; /* dynamically allocated array */
int maxcatlistrefs; /* currently allocated array size */
/* We have built-in support for remembering relcache references */
int nrelrefs; /* number of owned relcache pins */
Relation *relrefs; /* dynamically allocated array */
int maxrelrefs; /* currently allocated array size */
/* We have built-in support for remembering tupdesc references */
int ntupdescs; /* number of owned tupdesc references */
TupleDesc *tupdescs; /* dynamically allocated array */
int maxtupdescs; /* currently allocated array size */
} ResourceOwnerData;
/*****************************************************************************
* GLOBAL MEMORY *
*****************************************************************************/
ResourceOwner CurrentResourceOwner = NULL;
ResourceOwner CurTransactionResourceOwner = NULL;
ResourceOwner TopTransactionResourceOwner = 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 ResourceOwnerReleaseInternal(ResourceOwner owner,
ResourceReleasePhase phase,
bool isCommit,
bool isTopLevel);
static void PrintRelCacheLeakWarning(Relation rel);
static void PrintTupleDescLeakWarning(TupleDesc tupdesc);
/*****************************************************************************
* 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;
}
/*
* 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)
{
/* Rather than PG_TRY at every level of recursion, set it up once */
ResourceOwner save;
/*
* Greenplum: For some reason we've been calling this when the owner is NULL.
*/
if (owner == NULL)
{
elog((Debug_print_full_dtm ? LOG : DEBUG5),"ResourceOwnerRelease found owner = NULL");
return;
}
save = CurrentResourceOwner;
PG_TRY();
{
ResourceOwnerReleaseInternal(owner, phase, isCommit, isTopLevel);
}
PG_CATCH();
{
CurrentResourceOwner = save;
PG_RE_THROW();
}
PG_END_TRY();
CurrentResourceOwner = save;
}
static void
ResourceOwnerReleaseInternal(ResourceOwner owner,
ResourceReleasePhase phase,
bool isCommit,
bool isTopLevel)
{
ResourceOwner child;
ResourceOwner save;
ResourceReleaseCallbackItem *item;
/* 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. We needn't PG_TRY here because the outermost level will
* fix it on error abort.
*/
save = CurrentResourceOwner;
CurrentResourceOwner = owner;
if (phase == RESOURCE_RELEASE_BEFORE_LOCKS)
{
/*
* Release buffer pins. Note that ReleaseBuffer will remove the
* buffer entry from my list, so I 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.
*
* We are careful to do the releasing back-to-front, so as to avoid
* O(N^2) behavior in ResourceOwnerForgetBuffer().
*/
while (owner->nbuffers > 0)
{
if (isCommit)
PrintBufferLeakWarning(owner->buffers[owner->nbuffers - 1]);
ReleaseBuffer(owner->buffers[owner->nbuffers - 1]);
}
/*
* Release relcache references. Note that RelationClose will remove
* the relref entry from my list, so I just have to iterate till there
* are none.
*
* As with buffer pins, warn if any are left at commit time, and
* release back-to-front for speed.
*/
while (owner->nrelrefs > 0)
{
if (isCommit)
PrintRelCacheLeakWarning(owner->relrefs[owner->nrelrefs - 1]);
RelationClose(owner->relrefs[owner->nrelrefs - 1]);
}
}
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);
}
}
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.
*/
Assert(owner->parent != NULL);
if (isCommit)
LockReassignCurrentOwner();
else
LockReleaseCurrentOwner();
}
}
else if (phase == RESOURCE_RELEASE_AFTER_LOCKS)
{
/*
* Release catcache references. Note that ReleaseCatCache will remove
* the catref entry from my list, so I just have to iterate till there
* are none.
*
* As with buffer pins, warn if any are left at commit time, and
* release back-to-front for speed.
*/
while (owner->ncatrefs > 0)
{
if (isCommit)
PrintCatCacheLeakWarning(owner->catrefs[owner->ncatrefs - 1],
owner->name);
ReleaseCatCache(owner->catrefs[owner->ncatrefs - 1]);
}
/* Ditto for catcache lists */
while (owner->ncatlistrefs > 0)
{
if (isCommit)
PrintCatCacheListLeakWarning(owner->catlistrefs[owner->ncatlistrefs - 1],
owner->name);
ReleaseCatCacheList(owner->catlistrefs[owner->ncatlistrefs - 1]);
}
/* Ditto for tupdesc references */
while (owner->ntupdescs > 0)
{
if (isCommit)
PrintTupleDescLeakWarning(owner->tupdescs[owner->ntupdescs - 1]);
DecrTupleDescRefCount(owner->tupdescs[owner->ntupdescs - 1]);
}
/* Clean up index scans too */
ReleaseResources_hash();
}
/* Let add-on modules get a chance too */
for (item = ResourceRelease_callbacks; item; item = item->next)
(*item->callback) (phase, isCommit, isTopLevel, item->arg);
CurrentResourceOwner = save;
}
/*
* 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->nbuffers == 0);
Assert(owner->ncatrefs == 0);
Assert(owner->ncatlistrefs == 0);
Assert(owner->nrelrefs == 0);
Assert(owner->ntupdescs == 0);
/*
* 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->buffers)
pfree(owner->buffers);
if (owner->catrefs)
pfree(owner->catrefs);
if (owner->catlistrefs)
pfree(owner->catlistrefs);
if (owner->relrefs)
pfree(owner->relrefs);
if (owner->tupdescs)
pfree(owner->tupdescs);
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;
}
}
}
/*
* 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.
*
* We allow the case owner == NULL because the bufmgr is sometimes invoked
* outside any transaction (for example, during WAL recovery).
*/
void
ResourceOwnerEnlargeBuffers(ResourceOwner owner)
{
int newmax;
if (owner == NULL ||
owner->nbuffers < owner->maxbuffers)
return; /* nothing to do */
if (owner->buffers == NULL)
{
newmax = 16;
owner->buffers = (Buffer *)
MemoryContextAlloc(TopMemoryContext, newmax * sizeof(Buffer));
owner->maxbuffers = newmax;
}
else
{
newmax = owner->maxbuffers * 2;
owner->buffers = (Buffer *)
repalloc(owner->buffers, newmax * sizeof(Buffer));
owner->maxbuffers = newmax;
}
}
/*
* Remember that a buffer pin is owned by a ResourceOwner
*
* Caller must have previously done ResourceOwnerEnlargeBuffers()
*
* We allow the case owner == NULL because the bufmgr is sometimes invoked
* outside any transaction (for example, during WAL recovery).
*/
void
ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer)
{
if (owner != NULL)
{
Assert(owner->nbuffers < owner->maxbuffers);
owner->buffers[owner->nbuffers] = buffer;
owner->nbuffers++;
}
}
/*
* Forget that a buffer pin is owned by a ResourceOwner
*
* We allow the case owner == NULL because the bufmgr is sometimes invoked
* outside any transaction (for example, during WAL recovery).
*/
void
ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer)
{
if (owner != NULL)
{
Buffer *buffers = owner->buffers;
int nb1 = owner->nbuffers - 1;
int i;
/*
* Scan back-to-front because it's more likely we are releasing a
* recently pinned buffer. This isn't always the case of course, but
* it's the way to bet.
*/
for (i = nb1; i >= 0; i--)
{
if (buffers[i] == buffer)
{
while (i < nb1)
{
buffers[i] = buffers[i + 1];
i++;
}
owner->nbuffers = nb1;
return;
}
}
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
* 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)
{
int newmax;
if (owner->ncatrefs < owner->maxcatrefs)
return; /* nothing to do */
if (owner->catrefs == NULL)
{
newmax = 16;
owner->catrefs = (HeapTuple *)
MemoryContextAlloc(TopMemoryContext, newmax * sizeof(HeapTuple));
owner->maxcatrefs = newmax;
}
else
{
newmax = owner->maxcatrefs * 2;
owner->catrefs = (HeapTuple *)
repalloc(owner->catrefs, newmax * sizeof(HeapTuple));
owner->maxcatrefs = newmax;
}
}
/*
* Remember that a catcache reference is owned by a ResourceOwner
*
* Caller must have previously done ResourceOwnerEnlargeCatCacheRefs()
*/
void
ResourceOwnerRememberCatCacheRef(ResourceOwner owner, HeapTuple tuple)
{
Assert(owner->ncatrefs < owner->maxcatrefs);
owner->catrefs[owner->ncatrefs] = tuple;
owner->ncatrefs++;
}
/*
* Forget that a catcache reference is owned by a ResourceOwner
*/
void
ResourceOwnerForgetCatCacheRef(ResourceOwner owner, HeapTuple tuple)
{
HeapTuple *catrefs = owner->catrefs;
int nc1 = owner->ncatrefs - 1;
int i;
for (i = nc1; i >= 0; i--)
{
if (catrefs[i] == tuple)
{
while (i < nc1)
{
catrefs[i] = catrefs[i + 1];
i++;
}
owner->ncatrefs = nc1;
return;
}
}
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)
{
int newmax;
if (owner->ncatlistrefs < owner->maxcatlistrefs)
return; /* nothing to do */
if (owner->catlistrefs == NULL)
{
newmax = 16;
owner->catlistrefs = (CatCList **)
MemoryContextAlloc(TopMemoryContext, newmax * sizeof(CatCList *));
owner->maxcatlistrefs = newmax;
}
else
{
newmax = owner->maxcatlistrefs * 2;
owner->catlistrefs = (CatCList **)
repalloc(owner->catlistrefs, newmax * sizeof(CatCList *));
owner->maxcatlistrefs = newmax;
}
}
/*
* Remember that a catcache-list reference is owned by a ResourceOwner
*
* Caller must have previously done ResourceOwnerEnlargeCatCacheListRefs()
*/
void
ResourceOwnerRememberCatCacheListRef(ResourceOwner owner, CatCList *list)
{
Assert(owner->ncatlistrefs < owner->maxcatlistrefs);
owner->catlistrefs[owner->ncatlistrefs] = list;
owner->ncatlistrefs++;
}
/*
* Forget that a catcache-list reference is owned by a ResourceOwner
*/
void
ResourceOwnerForgetCatCacheListRef(ResourceOwner owner, CatCList *list)
{
CatCList **catlistrefs = owner->catlistrefs;
int nc1 = owner->ncatlistrefs - 1;
int i;
for (i = nc1; i >= 0; i--)
{
if (catlistrefs[i] == list)
{
while (i < nc1)
{
catlistrefs[i] = catlistrefs[i + 1];
i++;
}
owner->ncatlistrefs = nc1;
return;
}
}
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)
{
int newmax;
if (owner->nrelrefs < owner->maxrelrefs)
return; /* nothing to do */
if (owner->relrefs == NULL)
{
newmax = 16;
owner->relrefs = (Relation *)
MemoryContextAlloc(TopMemoryContext, newmax * sizeof(Relation));
owner->maxrelrefs = newmax;
}
else
{
newmax = owner->maxrelrefs * 2;
owner->relrefs = (Relation *)
repalloc(owner->relrefs, newmax * sizeof(Relation));
owner->maxrelrefs = newmax;
}
}
/*
* Remember that a relcache reference is owned by a ResourceOwner
*
* Caller must have previously done ResourceOwnerEnlargeRelationRefs()
*/
void
ResourceOwnerRememberRelationRef(ResourceOwner owner, Relation rel)
{
Assert(owner->nrelrefs < owner->maxrelrefs);
owner->relrefs[owner->nrelrefs] = rel;
owner->nrelrefs++;
}
/*
* Forget that a relcache reference is owned by a ResourceOwner
*/
void
ResourceOwnerForgetRelationRef(ResourceOwner owner, Relation rel)
{
Assert(NULL != owner);
Assert(NULL != rel);
Relation *relrefs = owner->relrefs;
int nr1 = owner->nrelrefs - 1;
int i;
for (i = nr1; i >= 0; i--)
{
if (relrefs[i] == rel)
{
while (i < nr1)
{
relrefs[i] = relrefs[i + 1];
i++;
}
owner->nrelrefs = nr1;
return;
}
}
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
* 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)
{
int newmax;
if (owner->ntupdescs < owner->maxtupdescs)
return; /* nothing to do */
if (owner->tupdescs == NULL)
{
newmax = 16;
owner->tupdescs = (TupleDesc *)
MemoryContextAlloc(TopMemoryContext, newmax * sizeof(TupleDesc));
owner->maxtupdescs = newmax;
}
else
{
newmax = owner->maxtupdescs * 2;
owner->tupdescs = (TupleDesc *)
repalloc(owner->tupdescs, newmax * sizeof(TupleDesc));
owner->maxtupdescs = newmax;
}
}
/*
* Remember that a tupdesc reference is owned by a ResourceOwner
*
* Caller must have previously done ResourceOwnerEnlargeTupleDescs()
*/
void
ResourceOwnerRememberTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
{
Assert(owner->ntupdescs < owner->maxtupdescs);
owner->tupdescs[owner->ntupdescs] = tupdesc;
owner->ntupdescs++;
}
/*
* Forget that a tupdesc reference is owned by a ResourceOwner
*/
void
ResourceOwnerForgetTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
{
TupleDesc *tupdescs = owner->tupdescs;
int nt1 = owner->ntupdescs - 1;
int i;
for (i = nt1; i >= 0; i--)
{
if (tupdescs[i] == tupdesc)
{
while (i < nt1)
{
tupdescs[i] = tupdescs[i + 1];
i++;
}
owner->ntupdescs = nt1;
return;
}
}
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);
}