三阶段释放是指ResourceOwnerRelease函数在使用时需要调用三次,按固定顺序调用每次删除特定的资源:
typedef enum
{
RESOURCE_RELEASE_BEFORE_LOCKS = 1,
RESOURCE_RELEASE_LOCKS,
RESOURCE_RELEASE_AFTER_LOCKS,
} ResourceReleasePhase;
例如事务提交时CommitTransaction:
static void
CommitTransaction(void)
...
...
...
ResourceOwnerRelease(TopTransactionResourceOwner,
RESOURCE_RELEASE_BEFORE_LOCKS,
true, true);
/* Check we've released all buffer pins */
AtEOXact_Buffers(true);
/* Clean up the relation cache */
AtEOXact_RelationCache(true);
/*
* Make catalog changes visible to all backends. This has to happen after
* relcache references are dropped (see comments for
* AtEOXact_RelationCache), but before locks are released (if anyone is
* waiting for lock on a relation we've modified, we want them to know
* about the catalog change before they start using the relation).
*/
AtEOXact_Inval(true);
AtEOXact_MultiXact();
ResourceOwnerRelease(TopTransactionResourceOwner,
RESOURCE_RELEASE_LOCKS,
true, true);
ResourceOwnerRelease(TopTransactionResourceOwner,
RESOURCE_RELEASE_AFTER_LOCKS,
true, true);
...
...
其他几个事务控制函数资源释放时,也是按照相同的顺序分三阶段释放的:
函数名 | phase | isCommit | isTopLevel |
---|---|---|---|
CommitTransaction | RESOURCE_RELEASE_BEFORE_LOCKS | true | true |
CommitTransaction | RESOURCE_RELEASE_LOCKS | true | true |
CommitTransaction | RESOURCE_RELEASE_AFTER_LOCKS | true | true |
PrepareTransaction | RESOURCE_RELEASE_BEFORE_LOCKS | true | true |
PrepareTransaction | RESOURCE_RELEASE_LOCKS | true | true |
PrepareTransaction | RESOURCE_RELEASE_AFTER_LOCKS | true | true |
AbortTransaction | RESOURCE_RELEASE_BEFORE_LOCKS | false | true |
AbortTransaction | RESOURCE_RELEASE_LOCKS | false | true |
AbortTransaction | RESOURCE_RELEASE_AFTER_LOCKS | false | true |
CommitSubTransaction | RESOURCE_RELEASE_BEFORE_LOCKS | true | false |
CommitSubTransaction | RESOURCE_RELEASE_LOCKS | true | false |
CommitSubTransaction | RESOURCE_RELEASE_AFTER_LOCKS | true | false |
AbortSubTransaction | RESOURCE_RELEASE_BEFORE_LOCKS | false | false |
AbortSubTransaction | RESOURCE_RELEASE_LOCKS | false | false |
AbortSubTransaction | RESOURCE_RELEASE_AFTER_LOCKS | false | false |
结论
因为如果先释放锁,没有释放一些共享资源(比如pin住的buffer),别人拿到锁后发现我们仍然持有一些资源,就会有问题。所以三阶段释放主要是以锁为分界线,先释放锁保护的资源,在释放锁,在清理私有资源。这样可以保证别人拿到锁后,一定也能拿到对应的资源。
下面这次提交后对resowner做了扩展性增强,代码逻辑没变但可读性有点差(PG17dev分支)
commit b8bff07daa85c837a2747b4d35cd5a27e73fb7b2
Author: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed Nov 8 13:30:50 2023 +0200
Make ResourceOwners more easily extensible.
围绕本篇主题,后面分析这次提交前代码(PG17前的逻辑)
static void
ResourceOwnerReleaseInternal(ResourceOwner owner,
ResourceReleasePhase phase,
bool isCommit,
bool isTopLevel)
{
ResourceOwner child;
ResourceOwner save;
ResourceReleaseCallbackItem *item;
ResourceReleaseCallbackItem *next;
Datum foundres;
resowner维护树形结构,递归释放自己的child资源。
for (child = owner->firstchild; child != NULL; child = child->nextchild)
ResourceOwnerReleaseInternal(child, phase, isCommit, isTopLevel);
...
if (phase == RESOURCE_RELEASE_BEFORE_LOCKS)
{
while (ResourceArrayGetAny(&(owner->bufferioarr), &foundres))
{
Buffer res = DatumGetBuffer(foundres);
if (isCommit)
elog(PANIC, "lost track of buffer IO on buffer %d", res);
AbortBufferIO(res);
}
while (ResourceArrayGetAny(&(owner->bufferarr), &foundres))
{
Buffer res = DatumGetBuffer(foundres);
if (isCommit)
PrintBufferLeakWarning(res);
ReleaseBuffer(res);
}
while (ResourceArrayGetAny(&(owner->relrefarr), &foundres))
{
Relation res = (Relation) DatumGetPointer(foundres);
if (isCommit)
PrintRelCacheLeakWarning(res);
RelationClose(res);
}
while (ResourceArrayGetAny(&(owner->dsmarr), &foundres))
{
dsm_segment *res = (dsm_segment *) DatumGetPointer(foundres);
if (isCommit)
PrintDSMLeakWarning(res);
dsm_detach(res);
}
while (ResourceArrayGetAny(&(owner->jitarr), &foundres))
{
JitContext *context = (JitContext *) DatumGetPointer(foundres);
jit_release_context(context);
}
...
...
}
}
else if (phase == RESOURCE_RELEASE_LOCKS)
{
if (isTopLevel)
{
if (owner == TopTransactionResourceOwner)
{
ProcReleaseLocks(isCommit);
ReleasePredicateLocks(isCommit, false);
}
}
else
{
LOCALLOCK **locks;
int nlocks;
...
if (isCommit)
LockReassignCurrentOwner(locks, nlocks);
else
LockReleaseCurrentOwner(locks, nlocks);
}
}
else if (phase == RESOURCE_RELEASE_AFTER_LOCKS)
{
while (ResourceArrayGetAny(&(owner->catrefarr), &foundres))
{
HeapTuple res = (HeapTuple) DatumGetPointer(foundres);
if (isCommit)
PrintCatCacheLeakWarning(res);
ReleaseCatCache(res);
}
while (ResourceArrayGetAny(&(owner->catlistrefarr), &foundres))
{
CatCList *res = (CatCList *) DatumGetPointer(foundres);
if (isCommit)
PrintCatCacheListLeakWarning(res);
ReleaseCatCacheList(res);
}
while (ResourceArrayGetAny(&(owner->planrefarr), &foundres))
{
CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres);
if (isCommit)
PrintPlanCacheLeakWarning(res);
ReleaseCachedPlan(res, owner);
}
while (ResourceArrayGetAny(&(owner->tupdescarr), &foundres))
{
TupleDesc res = (TupleDesc) DatumGetPointer(foundres);
if (isCommit)
PrintTupleDescLeakWarning(res);
DecrTupleDescRefCount(res);
}
while (ResourceArrayGetAny(&(owner->snapshotarr), &foundres))
{
Snapshot res = (Snapshot) DatumGetPointer(foundres);
if (isCommit)
PrintSnapshotLeakWarning(res);
UnregisterSnapshot(res);
}
/* Ditto for temporary files */
while (ResourceArrayGetAny(&(owner->filearr), &foundres))
{
File res = DatumGetFile(foundres);
if (isCommit)
PrintFileLeakWarning(res);
FileClose(res);
}
}
for (item = ResourceRelease_callbacks; item; item = next)
{
next = item->next;
item->callback(phase, isCommit, isTopLevel, item->arg);
}
CurrentResourceOwner = save;
}