mirror of https://github.com/postgres/postgres
keep track of portal-related resources separately from transaction-related resources. This allows cursors to work in a somewhat sane fashion with nested transactions. For now, cursor behavior is non-subtransactional, that is a cursor's state does not roll back if you abort a subtransaction that fetched from the cursor. We might want to change that later.REL8_0_STABLE
parent
f4c069ca8f
commit
fe548629c5
@ -0,0 +1,30 @@ |
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Makefile--
|
||||
# Makefile for utils/resowner
|
||||
#
|
||||
# IDENTIFICATION
|
||||
# $PostgreSQL: pgsql/src/backend/utils/resowner/Makefile,v 1.1 2004/07/17 03:30:10 tgl Exp $
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
subdir = src/backend/utils/resowner
|
||||
top_builddir = ../../../..
|
||||
include $(top_builddir)/src/Makefile.global |
||||
|
||||
OBJS = resowner.o
|
||||
|
||||
all: SUBSYS.o |
||||
|
||||
SUBSYS.o: $(OBJS) |
||||
$(LD) $(LDREL) $(LDOUT) SUBSYS.o $(OBJS)
|
||||
|
||||
depend dep: |
||||
$(CC) -MM $(CFLAGS) *.c >depend
|
||||
|
||||
clean: |
||||
rm -f SUBSYS.o $(OBJS)
|
||||
|
||||
ifeq (depend,$(wildcard depend)) |
||||
include depend |
||||
endif |
||||
@ -0,0 +1,74 @@ |
||||
$PostgreSQL: pgsql/src/backend/utils/resowner/README,v 1.1 2004/07/17 03:30:10 tgl Exp $ |
||||
|
||||
Notes about resource owners |
||||
--------------------------- |
||||
|
||||
ResourceOwner objects are a concept invented to simplify management of |
||||
query-related resources, such as buffer pins and table locks. These |
||||
resources need to be tracked in a reliable way to ensure that they will |
||||
be released at query end, even if the query fails due to an error. |
||||
Rather than expecting the entire executor to have bulletproof data |
||||
structures, we localize the tracking of such resources into a single |
||||
module. |
||||
|
||||
The design of the ResourceOwner API is modeled on our MemoryContext API, |
||||
which has proven very flexible and successful in preventing memory leaks. |
||||
In particular we allow ResourceOwners to have child ResourceOwner objects |
||||
so that there can be forests of the things; releasing a parent |
||||
ResourceOwner acts on all its direct and indirect children as well. |
||||
|
||||
(It is tempting to consider unifying ResourceOwners and MemoryContexts |
||||
into a single object type, but their usage patterns are sufficiently |
||||
different that this is probably not really a helpful thing to do.) |
||||
|
||||
We create a ResourceOwner for each transaction or subtransaction as |
||||
well as one for each Portal. During execution of a Portal, the global |
||||
variable CurrentResourceOwner points to the Portal's ResourceOwner. |
||||
This causes operations such as ReadBuffer and LockAcquire to record |
||||
ownership of the acquired resources in that ResourceOwner object. |
||||
|
||||
When a Portal is closed, any remaining resources (typically only locks) |
||||
become the responsibility of the current transaction. This is represented |
||||
by making the Portal's ResourceOwner a child of the current transaction's |
||||
ResourceOwner. Similarly, subtransaction ResourceOwners are children of |
||||
their immediate parent. |
||||
|
||||
We need transaction-related ResourceOwners as well as Portal-related ones |
||||
because transactions may initiate operations that require resources (such |
||||
as query parsing) when no associated Portal exists yet. |
||||
|
||||
|
||||
API overview |
||||
------------ |
||||
|
||||
The basic operations on a ResourceOwner are: |
||||
|
||||
* create a ResourceOwner |
||||
|
||||
* associate or deassociate some resource with a ResourceOwner |
||||
|
||||
* release a ResourceOwner's assets (free all owned resources, but not the |
||||
owner object itself) |
||||
|
||||
* delete a ResourceOwner (including child owner objects); all resources |
||||
must have been released beforehand |
||||
|
||||
Currently, ResourceOwners contain direct support for recording ownership |
||||
of buffer pins, lmgr locks, and catcache and relcache references. Other |
||||
objects can be associated with a ResourceOwner by recording the address of |
||||
the owning ResourceOwner in such an object. There is an API for other |
||||
modules to get control during ResourceOwner release, so that they can scan |
||||
their own data structures to find the objects that need to be deleted. |
||||
|
||||
Whenever we are inside a transaction, the global variable |
||||
CurrentResourceOwner shows which resource owner should be assigned |
||||
ownership of acquired resources. Note however that CurrentResourceOwner |
||||
is NULL when not inside any transaction (or when inside a failed |
||||
transaction). In this case it is not valid to acquire query-lifespan |
||||
resources. |
||||
|
||||
When unpinning a buffer or releasing a lock or cache reference, |
||||
CurrentResourceOwner must point to the same resource owner that was current |
||||
when the buffer, lock, or cache reference was acquired. It would be possible |
||||
to relax this restriction given additional bookkeeping effort, but at present |
||||
there seems no need. |
||||
@ -0,0 +1,840 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* 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-2003, 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.1 2004/07/17 03:30:10 tgl Exp $ |
||||
* |
||||
*------------------------------------------------------------------------- |
||||
*/ |
||||
#include "postgres.h" |
||||
|
||||
#include "utils/resowner.h" |
||||
#include "access/gistscan.h" |
||||
#include "access/hash.h" |
||||
#include "access/rtree.h" |
||||
#include "storage/bufmgr.h" |
||||
#include "storage/proc.h" |
||||
#include "utils/memutils.h" |
||||
#include "utils/relcache.h" |
||||
|
||||
|
||||
/*
|
||||
* Info needed to identify/release a lock |
||||
*/ |
||||
typedef struct LockIdData |
||||
{ |
||||
/* we assume lockmethodid is part of locktag */ |
||||
LOCKTAG locktag; |
||||
TransactionId xid; |
||||
LOCKMODE lockmode; |
||||
} LockIdData; |
||||
|
||||
|
||||
/*
|
||||
* 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 owned locks */ |
||||
int nlocks; /* number of owned locks */ |
||||
LockIdData *locks; /* dynamically allocated array */ |
||||
int maxlocks; /* 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 */ |
||||
} 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; |
||||
|
||||
|
||||
/*****************************************************************************
|
||||
* 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) |
||||
{ |
||||
ResourceOwner child; |
||||
ResourceOwner save; |
||||
ResourceReleaseCallbackItem *item; |
||||
|
||||
/* Recurse to handle descendants */ |
||||
for (child = owner->firstchild; child != NULL; child = child->nextchild) |
||||
ResourceOwnerRelease(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) |
||||
{ |
||||
/* Release buffer pins */ |
||||
if (isTopLevel) |
||||
{ |
||||
/*
|
||||
* For a top-level xact we are going to release all buffers, |
||||
* so just do a single bufmgr call at the top of the recursion. |
||||
*/ |
||||
if (owner == TopTransactionResourceOwner) |
||||
AtEOXact_Buffers(isCommit); |
||||
/* Mark object as owning no buffers, just for sanity */ |
||||
owner->nbuffers = 0; |
||||
} |
||||
else |
||||
{ |
||||
/*
|
||||
* Release buffers retail. Note that ReleaseBuffer will remove |
||||
* the buffer entry from my list, so I just have to iterate till |
||||
* there are none. |
||||
* |
||||
* XXX this is fairly inefficient due to multiple BufMgrLock grabs |
||||
* if there are lots of buffers to be released, but we don't |
||||
* expect many (indeed none in the success case) so it's probably |
||||
* not worth optimizing. |
||||
* |
||||
* We are however careful to release back-to-front, so as to |
||||
* avoid O(N^2) behavior in ResourceOwnerForgetBuffer(). |
||||
*/ |
||||
while (owner->nbuffers > 0) |
||||
ReleaseBuffer(owner->buffers[owner->nbuffers - 1]); |
||||
} |
||||
/* Release relcache references */ |
||||
if (isTopLevel) |
||||
{ |
||||
/*
|
||||
* For a top-level xact we are going to release all references, |
||||
* so just do a single relcache call at the top of the recursion. |
||||
*/ |
||||
if (owner == TopTransactionResourceOwner) |
||||
AtEOXact_RelationCache(isCommit); |
||||
/* Mark object as owning no relrefs, just for sanity */ |
||||
owner->nrelrefs = 0; |
||||
} |
||||
else |
||||
{ |
||||
/*
|
||||
* Release relcache refs retail. Note that RelationClose will |
||||
* remove the relref entry from my list, so I just have to iterate |
||||
* till there are none. |
||||
*/ |
||||
while (owner->nrelrefs > 0) |
||||
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); |
||||
/* Mark object as holding no locks, just for sanity */ |
||||
owner->nlocks = 0; |
||||
} |
||||
else if (!isCommit) |
||||
{ |
||||
/*
|
||||
* Release locks retail. Note that LockRelease will remove |
||||
* the lock entry from my list, so I just have to iterate till |
||||
* there are none. Also note that if we are committing a |
||||
* subtransaction, we do NOT release its locks yet. |
||||
* |
||||
* XXX as above, this is a bit inefficient but probably not worth |
||||
* the trouble to optimize more. |
||||
*/ |
||||
while (owner->nlocks > 0) |
||||
{ |
||||
LockIdData *lockid = &owner->locks[owner->nlocks - 1]; |
||||
|
||||
LockRelease(lockid->locktag.lockmethodid, |
||||
&lockid->locktag, |
||||
lockid->xid, |
||||
lockid->lockmode); |
||||
} |
||||
} |
||||
} |
||||
else if (phase == RESOURCE_RELEASE_AFTER_LOCKS) |
||||
{ |
||||
/* Release catcache references */ |
||||
if (isTopLevel) |
||||
{ |
||||
/*
|
||||
* For a top-level xact we are going to release all references, |
||||
* so just do a single catcache call at the top of the recursion. |
||||
*/ |
||||
if (owner == TopTransactionResourceOwner) |
||||
AtEOXact_CatCache(isCommit); |
||||
/* Mark object as owning no catrefs, just for sanity */ |
||||
owner->ncatrefs = 0; |
||||
owner->ncatlistrefs = 0; |
||||
} |
||||
else |
||||
{ |
||||
/*
|
||||
* Release catcache refs retail. Note that ReleaseCatCache will |
||||
* remove the catref entry from my list, so I just have to iterate |
||||
* till there are none. Ditto for catcache lists. |
||||
*/ |
||||
while (owner->ncatrefs > 0) |
||||
ReleaseCatCache(owner->catrefs[owner->ncatrefs - 1]); |
||||
while (owner->ncatlistrefs > 0) |
||||
ReleaseCatCacheList(owner->catlistrefs[owner->ncatlistrefs - 1]); |
||||
} |
||||
/* Clean up index scans too */ |
||||
ReleaseResources_gist(); |
||||
ReleaseResources_hash(); |
||||
ReleaseResources_rtree(); |
||||
} |
||||
|
||||
/* 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->nlocks == 0); |
||||
Assert(owner->ncatrefs == 0); |
||||
Assert(owner->ncatlistrefs == 0); |
||||
Assert(owner->nrelrefs == 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->locks) |
||||
pfree(owner->locks); |
||||
if (owner->catrefs) |
||||
pfree(owner->catrefs); |
||||
if (owner->catlistrefs) |
||||
pfree(owner->catlistrefs); |
||||
if (owner->relrefs) |
||||
pfree(owner->relrefs); |
||||
|
||||
pfree(owner); |
||||
} |
||||
|
||||
/*
|
||||
* 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, in the bgwriter). |
||||
*/ |
||||
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, in the bgwriter). |
||||
*/ |
||||
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, in the bgwriter). |
||||
*/ |
||||
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 |
||||
* lock 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 |
||||
ResourceOwnerEnlargeLocks(ResourceOwner owner) |
||||
{ |
||||
int newmax; |
||||
|
||||
if (owner->nlocks < owner->maxlocks) |
||||
return; /* nothing to do */ |
||||
|
||||
if (owner->locks == NULL) |
||||
{ |
||||
newmax = 16; |
||||
owner->locks = (LockIdData *) |
||||
MemoryContextAlloc(TopMemoryContext, newmax * sizeof(LockIdData)); |
||||
owner->maxlocks = newmax; |
||||
} |
||||
else |
||||
{ |
||||
newmax = owner->maxlocks * 2; |
||||
owner->locks = (LockIdData *) |
||||
repalloc(owner->locks, newmax * sizeof(LockIdData)); |
||||
owner->maxlocks = newmax; |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
* Remember that a lock is owned by a ResourceOwner |
||||
* |
||||
* Caller must have previously done ResourceOwnerEnlargeLocks() |
||||
*/ |
||||
void |
||||
ResourceOwnerRememberLock(ResourceOwner owner, |
||||
LOCKTAG *locktag, |
||||
TransactionId xid, |
||||
LOCKMODE lockmode) |
||||
{ |
||||
/* Session locks and user locks are not transactional */ |
||||
if (xid != InvalidTransactionId && |
||||
locktag->lockmethodid == DEFAULT_LOCKMETHOD) |
||||
{ |
||||
Assert(owner->nlocks < owner->maxlocks); |
||||
owner->locks[owner->nlocks].locktag = *locktag; |
||||
owner->locks[owner->nlocks].xid = xid; |
||||
owner->locks[owner->nlocks].lockmode = lockmode; |
||||
owner->nlocks++; |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
* Forget that a lock is owned by a ResourceOwner |
||||
*/ |
||||
void |
||||
ResourceOwnerForgetLock(ResourceOwner owner, |
||||
LOCKTAG *locktag, |
||||
TransactionId xid, |
||||
LOCKMODE lockmode) |
||||
{ |
||||
/* Session locks and user locks are not transactional */ |
||||
if (xid != InvalidTransactionId && |
||||
locktag->lockmethodid == DEFAULT_LOCKMETHOD) |
||||
{ |
||||
LockIdData *locks = owner->locks; |
||||
int nl1 = owner->nlocks - 1; |
||||
int i; |
||||
|
||||
for (i = nl1; i >= 0; i--) |
||||
{ |
||||
if (memcmp(&locks[i].locktag, locktag, sizeof(LOCKTAG)) == 0 && |
||||
locks[i].xid == xid && |
||||
locks[i].lockmode == lockmode) |
||||
{ |
||||
while (i < nl1) |
||||
{ |
||||
locks[i] = locks[i + 1]; |
||||
i++; |
||||
} |
||||
owner->nlocks = nl1; |
||||
return; |
||||
} |
||||
} |
||||
elog(ERROR, "lock %u/%u/%u is not owned by resource owner %s", |
||||
locktag->relId, locktag->dbId, locktag->objId.xid, 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) |
||||
{ |
||||
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); |
||||
} |
||||
@ -0,0 +1,121 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* resowner.h |
||||
* POSTGRES resource owner definitions. |
||||
* |
||||
* 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-2003, PostgreSQL Global Development Group |
||||
* Portions Copyright (c) 1994, Regents of the University of California |
||||
* |
||||
* $PostgreSQL: pgsql/src/include/utils/resowner.h,v 1.1 2004/07/17 03:31:47 tgl Exp $ |
||||
* |
||||
*------------------------------------------------------------------------- |
||||
*/ |
||||
#ifndef RESOWNER_H |
||||
#define RESOWNER_H |
||||
|
||||
#include "storage/buf.h" |
||||
#include "storage/lock.h" |
||||
#include "utils/catcache.h" |
||||
#include "utils/rel.h" |
||||
|
||||
|
||||
/*
|
||||
* ResourceOwner objects are an opaque data structure known only within |
||||
* resowner.c. |
||||
*/ |
||||
typedef struct ResourceOwnerData *ResourceOwner; |
||||
|
||||
|
||||
/*
|
||||
* Globally known ResourceOwners |
||||
*/ |
||||
extern DLLIMPORT ResourceOwner CurrentResourceOwner; |
||||
extern DLLIMPORT ResourceOwner CurTransactionResourceOwner; |
||||
extern DLLIMPORT ResourceOwner TopTransactionResourceOwner; |
||||
|
||||
/*
|
||||
* Resource releasing is done in three phases: pre-locks, locks, and |
||||
* post-locks. The pre-lock phase must release any resources that are |
||||
* visible to other backends (such as pinned buffers); this ensures that |
||||
* when we release a lock that another backend may be waiting on, it will |
||||
* see us as being fully out of our transaction. The post-lock phase |
||||
* should be used for backend-internal cleanup. |
||||
*/ |
||||
typedef enum |
||||
{ |
||||
RESOURCE_RELEASE_BEFORE_LOCKS, |
||||
RESOURCE_RELEASE_LOCKS, |
||||
RESOURCE_RELEASE_AFTER_LOCKS |
||||
} ResourceReleasePhase; |
||||
|
||||
/*
|
||||
* Dynamically loaded modules can get control during ResourceOwnerRelease |
||||
* by providing a callback of this form. |
||||
*/ |
||||
typedef void (*ResourceReleaseCallback) (ResourceReleasePhase phase, |
||||
bool isCommit, |
||||
bool isTopLevel, |
||||
void *arg); |
||||
|
||||
|
||||
/*
|
||||
* Functions in resowner.c |
||||
*/ |
||||
|
||||
/* generic routines */ |
||||
extern ResourceOwner ResourceOwnerCreate(ResourceOwner parent, |
||||
const char *name); |
||||
extern void ResourceOwnerRelease(ResourceOwner owner, |
||||
ResourceReleasePhase phase, |
||||
bool isCommit, |
||||
bool isTopLevel); |
||||
extern void ResourceOwnerDelete(ResourceOwner owner); |
||||
extern void ResourceOwnerNewParent(ResourceOwner owner, |
||||
ResourceOwner newparent); |
||||
extern void RegisterResourceReleaseCallback(ResourceReleaseCallback callback, |
||||
void *arg); |
||||
extern void UnregisterResourceReleaseCallback(ResourceReleaseCallback callback, |
||||
void *arg); |
||||
|
||||
/* support for buffer refcount management */ |
||||
extern void ResourceOwnerEnlargeBuffers(ResourceOwner owner); |
||||
extern void ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer); |
||||
extern void ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer); |
||||
|
||||
/* support for lock management */ |
||||
extern void ResourceOwnerEnlargeLocks(ResourceOwner owner); |
||||
extern void ResourceOwnerRememberLock(ResourceOwner owner, |
||||
LOCKTAG *locktag, |
||||
TransactionId xid, |
||||
LOCKMODE lockmode); |
||||
extern void ResourceOwnerForgetLock(ResourceOwner owner, |
||||
LOCKTAG *locktag, |
||||
TransactionId xid, |
||||
LOCKMODE lockmode); |
||||
|
||||
/* support for catcache refcount management */ |
||||
extern void ResourceOwnerEnlargeCatCacheRefs(ResourceOwner owner); |
||||
extern void ResourceOwnerRememberCatCacheRef(ResourceOwner owner, |
||||
HeapTuple tuple); |
||||
extern void ResourceOwnerForgetCatCacheRef(ResourceOwner owner, |
||||
HeapTuple tuple); |
||||
extern void ResourceOwnerEnlargeCatCacheListRefs(ResourceOwner owner); |
||||
extern void ResourceOwnerRememberCatCacheListRef(ResourceOwner owner, |
||||
CatCList *list); |
||||
extern void ResourceOwnerForgetCatCacheListRef(ResourceOwner owner, |
||||
CatCList *list); |
||||
|
||||
/* support for relcache refcount management */ |
||||
extern void ResourceOwnerEnlargeRelationRefs(ResourceOwner owner); |
||||
extern void ResourceOwnerRememberRelationRef(ResourceOwner owner, |
||||
Relation rel); |
||||
extern void ResourceOwnerForgetRelationRef(ResourceOwner owner, |
||||
Relation rel); |
||||
|
||||
#endif /* RESOWNER_H */ |
||||
Loading…
Reference in new issue