mirror of
https://github.com/postgres/postgres.git
synced 2025-11-22 12:22:45 +03:00
Make ResourceOwners more easily extensible.
Instead of having a separate array/hash for each resource kind, use a single array and hash to hold all kinds of resources. This makes it possible to introduce new resource "kinds" without having to modify the ResourceOwnerData struct. In particular, this makes it possible for extensions to register custom resource kinds. The old approach was to have a small array of resources of each kind, and if it fills up, switch to a hash table. The new approach also uses an array and a hash, but now the array and the hash are used at the same time. The array is used to hold the recently added resources, and when it fills up, they are moved to the hash. This keeps the access to recent entries fast, even when there are a lot of long-held resources. All the resource-specific ResourceOwnerEnlarge*(), ResourceOwnerRemember*(), and ResourceOwnerForget*() functions have been replaced with three generic functions that take resource kind as argument. For convenience, we still define resource-specific wrapper macros around the generic functions with the old names, but they are now defined in the source files that use those resource kinds. The release callback no longer needs to call ResourceOwnerForget on the resource being released. ResourceOwnerRelease unregisters the resource from the owner before calling the callback. That needed some changes in bufmgr.c and some other files, where releasing the resources previously always called ResourceOwnerForget. Each resource kind specifies a release priority, and ResourceOwnerReleaseAll releases the resources in priority order. To make that possible, we have to restrict what you can do between phases. After calling ResourceOwnerRelease(), you are no longer allowed to remember any more resources in it or to forget any previously remembered resources by calling ResourceOwnerForget. There was one case where that was done previously. At subtransaction commit, AtEOSubXact_Inval() would handle the invalidation messages and call RelationFlushRelation(), which temporarily increased the reference count on the relation being flushed. We now switch to the parent subtransaction's resource owner before calling AtEOSubXact_Inval(), so that there is a valid ResourceOwner to temporarily hold that relcache reference. Other end-of-xact routines make similar calls to AtEOXact_Inval() between release phases, but I didn't see any regression test failures from those, so I'm not sure if they could reach a codepath that needs remembering extra resources. There were two exceptions to how the resource leak WARNINGs on commit were printed previously: llvmjit silently released the context without printing the warning, and a leaked buffer io triggered a PANIC. Now everything prints a WARNING, including those cases. Add tests in src/test/modules/test_resowner. Reviewed-by: Aleksander Alekseev, Michael Paquier, Julien Rouhaud Reviewed-by: Kyotaro Horiguchi, Hayato Kuroda, Álvaro Herrera, Zhihong Yu Reviewed-by: Peter Eisentraut, Andres Freund Discussion: https://www.postgresql.org/message-id/cbfabeb0-cd3c-e951-a572-19b365ed314d%40iki.fi
This commit is contained in:
125
src/backend/utils/cache/catcache.c
vendored
125
src/backend/utils/cache/catcache.c
vendored
@@ -31,12 +31,13 @@
|
||||
#endif
|
||||
#include "storage/lmgr.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/catcache.h"
|
||||
#include "utils/datum.h"
|
||||
#include "utils/fmgroids.h"
|
||||
#include "utils/inval.h"
|
||||
#include "utils/memutils.h"
|
||||
#include "utils/rel.h"
|
||||
#include "utils/resowner_private.h"
|
||||
#include "utils/resowner.h"
|
||||
#include "utils/syscache.h"
|
||||
|
||||
|
||||
@@ -94,6 +95,8 @@ static CatCTup *CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp,
|
||||
uint32 hashValue, Index hashIndex,
|
||||
bool negative);
|
||||
|
||||
static void ReleaseCatCacheWithOwner(HeapTuple tuple, ResourceOwner resowner);
|
||||
static void ReleaseCatCacheListWithOwner(CatCList *list, ResourceOwner resowner);
|
||||
static void CatCacheFreeKeys(TupleDesc tupdesc, int nkeys, int *attnos,
|
||||
Datum *keys);
|
||||
static void CatCacheCopyKeys(TupleDesc tupdesc, int nkeys, int *attnos,
|
||||
@@ -104,6 +107,56 @@ static void CatCacheCopyKeys(TupleDesc tupdesc, int nkeys, int *attnos,
|
||||
* internal support functions
|
||||
*/
|
||||
|
||||
/* ResourceOwner callbacks to hold catcache references */
|
||||
|
||||
static void ResOwnerReleaseCatCache(Datum res);
|
||||
static char *ResOwnerPrintCatCache(Datum res);
|
||||
static void ResOwnerReleaseCatCacheList(Datum res);
|
||||
static char *ResOwnerPrintCatCacheList(Datum res);
|
||||
|
||||
static const ResourceOwnerDesc catcache_resowner_desc =
|
||||
{
|
||||
/* catcache references */
|
||||
.name = "catcache reference",
|
||||
.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
|
||||
.release_priority = RELEASE_PRIO_CATCACHE_REFS,
|
||||
.ReleaseResource = ResOwnerReleaseCatCache,
|
||||
.DebugPrint = ResOwnerPrintCatCache
|
||||
};
|
||||
|
||||
static const ResourceOwnerDesc catlistref_resowner_desc =
|
||||
{
|
||||
/* catcache-list pins */
|
||||
.name = "catcache list reference",
|
||||
.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
|
||||
.release_priority = RELEASE_PRIO_CATCACHE_LIST_REFS,
|
||||
.ReleaseResource = ResOwnerReleaseCatCacheList,
|
||||
.DebugPrint = ResOwnerPrintCatCacheList
|
||||
};
|
||||
|
||||
/* Convenience wrappers over ResourceOwnerRemember/Forget */
|
||||
static inline void
|
||||
ResourceOwnerRememberCatCacheRef(ResourceOwner owner, HeapTuple tuple)
|
||||
{
|
||||
ResourceOwnerRemember(owner, PointerGetDatum(tuple), &catcache_resowner_desc);
|
||||
}
|
||||
static inline void
|
||||
ResourceOwnerForgetCatCacheRef(ResourceOwner owner, HeapTuple tuple)
|
||||
{
|
||||
ResourceOwnerForget(owner, PointerGetDatum(tuple), &catcache_resowner_desc);
|
||||
}
|
||||
static inline void
|
||||
ResourceOwnerRememberCatCacheListRef(ResourceOwner owner, CatCList *list)
|
||||
{
|
||||
ResourceOwnerRemember(owner, PointerGetDatum(list), &catlistref_resowner_desc);
|
||||
}
|
||||
static inline void
|
||||
ResourceOwnerForgetCatCacheListRef(ResourceOwner owner, CatCList *list)
|
||||
{
|
||||
ResourceOwnerForget(owner, PointerGetDatum(list), &catlistref_resowner_desc);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Hash and equality functions for system types that are used as cache key
|
||||
* fields. In some cases, we just call the regular SQL-callable functions for
|
||||
@@ -1268,7 +1321,7 @@ SearchCatCacheInternal(CatCache *cache,
|
||||
*/
|
||||
if (!ct->negative)
|
||||
{
|
||||
ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner);
|
||||
ResourceOwnerEnlarge(CurrentResourceOwner);
|
||||
ct->refcount++;
|
||||
ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple);
|
||||
|
||||
@@ -1369,7 +1422,7 @@ SearchCatCacheMiss(CatCache *cache,
|
||||
hashValue, hashIndex,
|
||||
false);
|
||||
/* immediately set the refcount to 1 */
|
||||
ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner);
|
||||
ResourceOwnerEnlarge(CurrentResourceOwner);
|
||||
ct->refcount++;
|
||||
ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple);
|
||||
break; /* assume only one match */
|
||||
@@ -1436,6 +1489,12 @@ SearchCatCacheMiss(CatCache *cache,
|
||||
*/
|
||||
void
|
||||
ReleaseCatCache(HeapTuple tuple)
|
||||
{
|
||||
ReleaseCatCacheWithOwner(tuple, CurrentResourceOwner);
|
||||
}
|
||||
|
||||
static void
|
||||
ReleaseCatCacheWithOwner(HeapTuple tuple, ResourceOwner resowner)
|
||||
{
|
||||
CatCTup *ct = (CatCTup *) (((char *) tuple) -
|
||||
offsetof(CatCTup, tuple));
|
||||
@@ -1445,7 +1504,8 @@ ReleaseCatCache(HeapTuple tuple)
|
||||
Assert(ct->refcount > 0);
|
||||
|
||||
ct->refcount--;
|
||||
ResourceOwnerForgetCatCacheRef(CurrentResourceOwner, &ct->tuple);
|
||||
if (resowner)
|
||||
ResourceOwnerForgetCatCacheRef(CurrentResourceOwner, &ct->tuple);
|
||||
|
||||
if (
|
||||
#ifndef CATCACHE_FORCE_RELEASE
|
||||
@@ -1581,7 +1641,7 @@ SearchCatCacheList(CatCache *cache,
|
||||
dlist_move_head(&cache->cc_lists, &cl->cache_elem);
|
||||
|
||||
/* Bump the list's refcount and return it */
|
||||
ResourceOwnerEnlargeCatCacheListRefs(CurrentResourceOwner);
|
||||
ResourceOwnerEnlarge(CurrentResourceOwner);
|
||||
cl->refcount++;
|
||||
ResourceOwnerRememberCatCacheListRef(CurrentResourceOwner, cl);
|
||||
|
||||
@@ -1693,7 +1753,7 @@ SearchCatCacheList(CatCache *cache,
|
||||
table_close(relation, AccessShareLock);
|
||||
|
||||
/* Make sure the resource owner has room to remember this entry. */
|
||||
ResourceOwnerEnlargeCatCacheListRefs(CurrentResourceOwner);
|
||||
ResourceOwnerEnlarge(CurrentResourceOwner);
|
||||
|
||||
/* Now we can build the CatCList entry. */
|
||||
oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
|
||||
@@ -1778,12 +1838,19 @@ SearchCatCacheList(CatCache *cache,
|
||||
*/
|
||||
void
|
||||
ReleaseCatCacheList(CatCList *list)
|
||||
{
|
||||
ReleaseCatCacheListWithOwner(list, CurrentResourceOwner);
|
||||
}
|
||||
|
||||
static void
|
||||
ReleaseCatCacheListWithOwner(CatCList *list, ResourceOwner resowner)
|
||||
{
|
||||
/* Safety checks to ensure we were handed a cache entry */
|
||||
Assert(list->cl_magic == CL_MAGIC);
|
||||
Assert(list->refcount > 0);
|
||||
list->refcount--;
|
||||
ResourceOwnerForgetCatCacheListRef(CurrentResourceOwner, list);
|
||||
if (resowner)
|
||||
ResourceOwnerForgetCatCacheListRef(CurrentResourceOwner, list);
|
||||
|
||||
if (
|
||||
#ifndef CATCACHE_FORCE_RELEASE
|
||||
@@ -2059,31 +2126,43 @@ PrepareToInvalidateCacheTuple(Relation relation,
|
||||
}
|
||||
}
|
||||
|
||||
/* ResourceOwner callbacks */
|
||||
|
||||
/*
|
||||
* Subroutines for warning about reference leaks. These are exported so
|
||||
* that resowner.c can call them.
|
||||
*/
|
||||
void
|
||||
PrintCatCacheLeakWarning(HeapTuple tuple)
|
||||
static void
|
||||
ResOwnerReleaseCatCache(Datum res)
|
||||
{
|
||||
ReleaseCatCacheWithOwner((HeapTuple) DatumGetPointer(res), NULL);
|
||||
}
|
||||
|
||||
static char *
|
||||
ResOwnerPrintCatCache(Datum res)
|
||||
{
|
||||
HeapTuple tuple = (HeapTuple) DatumGetPointer(res);
|
||||
CatCTup *ct = (CatCTup *) (((char *) tuple) -
|
||||
offsetof(CatCTup, tuple));
|
||||
|
||||
/* Safety check to ensure we were handed a cache entry */
|
||||
Assert(ct->ct_magic == CT_MAGIC);
|
||||
|
||||
elog(WARNING, "cache reference leak: cache %s (%d), tuple %u/%u has count %d",
|
||||
ct->my_cache->cc_relname, ct->my_cache->id,
|
||||
ItemPointerGetBlockNumber(&(tuple->t_self)),
|
||||
ItemPointerGetOffsetNumber(&(tuple->t_self)),
|
||||
ct->refcount);
|
||||
return psprintf("cache %s (%d), tuple %u/%u has count %d",
|
||||
ct->my_cache->cc_relname, ct->my_cache->id,
|
||||
ItemPointerGetBlockNumber(&(tuple->t_self)),
|
||||
ItemPointerGetOffsetNumber(&(tuple->t_self)),
|
||||
ct->refcount);
|
||||
}
|
||||
|
||||
void
|
||||
PrintCatCacheListLeakWarning(CatCList *list)
|
||||
static void
|
||||
ResOwnerReleaseCatCacheList(Datum res)
|
||||
{
|
||||
elog(WARNING, "cache reference leak: cache %s (%d), list %p has count %d",
|
||||
list->my_cache->cc_relname, list->my_cache->id,
|
||||
list, list->refcount);
|
||||
ReleaseCatCacheListWithOwner((CatCList *) DatumGetPointer(res), NULL);
|
||||
}
|
||||
|
||||
static char *
|
||||
ResOwnerPrintCatCacheList(Datum res)
|
||||
{
|
||||
CatCList *list = (CatCList *) DatumGetPointer(res);
|
||||
|
||||
return psprintf("cache %s (%d), list %p has count %d",
|
||||
list->my_cache->cc_relname, list->my_cache->id,
|
||||
list, list->refcount);
|
||||
}
|
||||
|
||||
50
src/backend/utils/cache/plancache.c
vendored
50
src/backend/utils/cache/plancache.c
vendored
@@ -69,7 +69,7 @@
|
||||
#include "tcop/utility.h"
|
||||
#include "utils/inval.h"
|
||||
#include "utils/memutils.h"
|
||||
#include "utils/resowner_private.h"
|
||||
#include "utils/resowner.h"
|
||||
#include "utils/rls.h"
|
||||
#include "utils/snapmgr.h"
|
||||
#include "utils/syscache.h"
|
||||
@@ -119,6 +119,31 @@ static void PlanCacheRelCallback(Datum arg, Oid relid);
|
||||
static void PlanCacheObjectCallback(Datum arg, int cacheid, uint32 hashvalue);
|
||||
static void PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue);
|
||||
|
||||
/* ResourceOwner callbacks to track plancache references */
|
||||
static void ResOwnerReleaseCachedPlan(Datum res);
|
||||
|
||||
static const ResourceOwnerDesc planref_resowner_desc =
|
||||
{
|
||||
.name = "plancache reference",
|
||||
.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
|
||||
.release_priority = RELEASE_PRIO_PLANCACHE_REFS,
|
||||
.ReleaseResource = ResOwnerReleaseCachedPlan,
|
||||
.DebugPrint = NULL /* the default message is fine */
|
||||
};
|
||||
|
||||
/* Convenience wrappers over ResourceOwnerRemember/Forget */
|
||||
static inline void
|
||||
ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
|
||||
{
|
||||
ResourceOwnerRemember(owner, PointerGetDatum(plan), &planref_resowner_desc);
|
||||
}
|
||||
static inline void
|
||||
ResourceOwnerForgetPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
|
||||
{
|
||||
ResourceOwnerForget(owner, PointerGetDatum(plan), &planref_resowner_desc);
|
||||
}
|
||||
|
||||
|
||||
/* GUC parameter */
|
||||
int plan_cache_mode = PLAN_CACHE_MODE_AUTO;
|
||||
|
||||
@@ -1233,7 +1258,7 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
|
||||
|
||||
/* Flag the plan as in use by caller */
|
||||
if (owner)
|
||||
ResourceOwnerEnlargePlanCacheRefs(owner);
|
||||
ResourceOwnerEnlarge(owner);
|
||||
plan->refcount++;
|
||||
if (owner)
|
||||
ResourceOwnerRememberPlanCacheRef(owner, plan);
|
||||
@@ -1396,7 +1421,7 @@ CachedPlanAllowsSimpleValidityCheck(CachedPlanSource *plansource,
|
||||
/* Bump refcount if requested. */
|
||||
if (owner)
|
||||
{
|
||||
ResourceOwnerEnlargePlanCacheRefs(owner);
|
||||
ResourceOwnerEnlarge(owner);
|
||||
plan->refcount++;
|
||||
ResourceOwnerRememberPlanCacheRef(owner, plan);
|
||||
}
|
||||
@@ -1457,7 +1482,7 @@ CachedPlanIsSimplyValid(CachedPlanSource *plansource, CachedPlan *plan,
|
||||
/* It's still good. Bump refcount if requested. */
|
||||
if (owner)
|
||||
{
|
||||
ResourceOwnerEnlargePlanCacheRefs(owner);
|
||||
ResourceOwnerEnlarge(owner);
|
||||
plan->refcount++;
|
||||
ResourceOwnerRememberPlanCacheRef(owner, plan);
|
||||
}
|
||||
@@ -2203,3 +2228,20 @@ ResetPlanCache(void)
|
||||
cexpr->is_valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Release all CachedPlans remembered by 'owner'
|
||||
*/
|
||||
void
|
||||
ReleaseAllPlanCacheRefsInOwner(ResourceOwner owner)
|
||||
{
|
||||
ResourceOwnerReleaseAllOfKind(owner, &planref_resowner_desc);
|
||||
}
|
||||
|
||||
/* ResourceOwner callbacks */
|
||||
|
||||
static void
|
||||
ResOwnerReleaseCachedPlan(Datum res)
|
||||
{
|
||||
ReleaseCachedPlan((CachedPlan *) DatumGetPointer(res), NULL);
|
||||
}
|
||||
|
||||
64
src/backend/utils/cache/relcache.c
vendored
64
src/backend/utils/cache/relcache.c
vendored
@@ -80,13 +80,14 @@
|
||||
#include "storage/smgr.h"
|
||||
#include "utils/array.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/catcache.h"
|
||||
#include "utils/datum.h"
|
||||
#include "utils/fmgroids.h"
|
||||
#include "utils/inval.h"
|
||||
#include "utils/lsyscache.h"
|
||||
#include "utils/memutils.h"
|
||||
#include "utils/relmapper.h"
|
||||
#include "utils/resowner_private.h"
|
||||
#include "utils/resowner.h"
|
||||
#include "utils/snapmgr.h"
|
||||
#include "utils/syscache.h"
|
||||
|
||||
@@ -273,6 +274,7 @@ static HTAB *OpClassCache = NULL;
|
||||
|
||||
/* non-export function prototypes */
|
||||
|
||||
static void RelationCloseCleanup(Relation relation);
|
||||
static void RelationDestroyRelation(Relation relation, bool remember_tupdesc);
|
||||
static void RelationClearRelation(Relation relation, bool rebuild);
|
||||
|
||||
@@ -2115,6 +2117,31 @@ RelationIdGetRelation(Oid relationId)
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/* ResourceOwner callbacks to track relcache references */
|
||||
static void ResOwnerReleaseRelation(Datum res);
|
||||
static char *ResOwnerPrintRelCache(Datum res);
|
||||
|
||||
static const ResourceOwnerDesc relref_resowner_desc =
|
||||
{
|
||||
.name = "relcache reference",
|
||||
.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
|
||||
.release_priority = RELEASE_PRIO_RELCACHE_REFS,
|
||||
.ReleaseResource = ResOwnerReleaseRelation,
|
||||
.DebugPrint = ResOwnerPrintRelCache
|
||||
};
|
||||
|
||||
/* Convenience wrappers over ResourceOwnerRemember/Forget */
|
||||
static inline void
|
||||
ResourceOwnerRememberRelationRef(ResourceOwner owner, Relation rel)
|
||||
{
|
||||
ResourceOwnerRemember(owner, PointerGetDatum(rel), &relref_resowner_desc);
|
||||
}
|
||||
static inline void
|
||||
ResourceOwnerForgetRelationRef(ResourceOwner owner, Relation rel)
|
||||
{
|
||||
ResourceOwnerForget(owner, PointerGetDatum(rel), &relref_resowner_desc);
|
||||
}
|
||||
|
||||
/*
|
||||
* RelationIncrementReferenceCount
|
||||
* Increments relation reference count.
|
||||
@@ -2126,7 +2153,7 @@ RelationIdGetRelation(Oid relationId)
|
||||
void
|
||||
RelationIncrementReferenceCount(Relation rel)
|
||||
{
|
||||
ResourceOwnerEnlargeRelationRefs(CurrentResourceOwner);
|
||||
ResourceOwnerEnlarge(CurrentResourceOwner);
|
||||
rel->rd_refcnt += 1;
|
||||
if (!IsBootstrapProcessingMode())
|
||||
ResourceOwnerRememberRelationRef(CurrentResourceOwner, rel);
|
||||
@@ -2162,6 +2189,12 @@ RelationClose(Relation relation)
|
||||
/* Note: no locking manipulations needed */
|
||||
RelationDecrementReferenceCount(relation);
|
||||
|
||||
RelationCloseCleanup(relation);
|
||||
}
|
||||
|
||||
static void
|
||||
RelationCloseCleanup(Relation relation)
|
||||
{
|
||||
/*
|
||||
* If the relation is no longer open in this session, we can clean up any
|
||||
* stale partition descriptors it has. This is unlikely, so check to see
|
||||
@@ -6813,3 +6846,30 @@ unlink_initfile(const char *initfilename, int elevel)
|
||||
initfilename)));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ResourceOwner callbacks
|
||||
*/
|
||||
static char *
|
||||
ResOwnerPrintRelCache(Datum res)
|
||||
{
|
||||
Relation rel = (Relation) DatumGetPointer(res);
|
||||
|
||||
return psprintf("relation \"%s\"", RelationGetRelationName(rel));
|
||||
}
|
||||
|
||||
static void
|
||||
ResOwnerReleaseRelation(Datum res)
|
||||
{
|
||||
Relation rel = (Relation) DatumGetPointer(res);
|
||||
|
||||
/*
|
||||
* This reference has already been removed from the resource owner, so
|
||||
* just decrement reference count without calling
|
||||
* ResourceOwnerForgetRelationRef.
|
||||
*/
|
||||
Assert(rel->rd_refcnt > 0);
|
||||
rel->rd_refcnt -= 1;
|
||||
|
||||
RelationCloseCleanup((Relation) res);
|
||||
}
|
||||
|
||||
@@ -39,8 +39,8 @@ because transactions may initiate operations that require resources (such
|
||||
as query parsing) when no associated Portal exists yet.
|
||||
|
||||
|
||||
API Overview
|
||||
------------
|
||||
Usage
|
||||
-----
|
||||
|
||||
The basic operations on a ResourceOwner are:
|
||||
|
||||
@@ -54,13 +54,6 @@ The basic operations on a ResourceOwner are:
|
||||
* delete a ResourceOwner (including child owner objects); all resources
|
||||
must have been released beforehand
|
||||
|
||||
This API directly supports the resource types listed in the definition of
|
||||
ResourceOwnerData struct in src/backend/utils/resowner/resowner.c.
|
||||
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.
|
||||
|
||||
Locks are handled specially because in non-error situations a lock should
|
||||
be held until end of transaction, even if it was originally taken by a
|
||||
subtransaction or portal. Therefore, the "release" operation on a child
|
||||
@@ -79,3 +72,106 @@ 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.
|
||||
|
||||
Adding a new resource type
|
||||
--------------------------
|
||||
|
||||
ResourceOwner can track ownership of many different kinds of resources. In
|
||||
core PostgreSQL it is used for buffer pins, lmgr locks, and catalog cache
|
||||
references, to name a few examples.
|
||||
|
||||
To add a new kind of resource, define a ResourceOwnerDesc to describe it.
|
||||
For example:
|
||||
|
||||
static const ResourceOwnerDesc myresource_desc = {
|
||||
.name = "My fancy resource",
|
||||
.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
|
||||
.release_priority = RELEASE_PRIO_FIRST,
|
||||
.ReleaseResource = ReleaseMyResource,
|
||||
.DebugPrint = PrintMyResource
|
||||
};
|
||||
|
||||
ResourceOwnerRemember() and ResourceOwnerForget() functions take a pointer
|
||||
to that struct, along with a Datum to represent the resource. The meaning
|
||||
of the Datum depends on the resource type. Most resource types use it to
|
||||
store a pointer to some struct, but it can also be a file descriptor or
|
||||
library handle, for example.
|
||||
|
||||
The ReleaseResource callback is called when a resource owner is released or
|
||||
deleted. It should release any resources (e.g. close files, free memory)
|
||||
associated with the resource. Because the callback is called during
|
||||
transaction abort, it must perform only low-level cleanup with no user
|
||||
visible effects. The callback should not perform operations that could
|
||||
fail, like allocate memory.
|
||||
|
||||
The optional DebugPrint callback is used in the warning at transaction
|
||||
commit, if any resources are leaked. If not specified, a generic
|
||||
implementation that prints the resource name and the resource as a pointer
|
||||
is used.
|
||||
|
||||
There is another 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. See RegisterResourceReleaseCallback function.
|
||||
This used to be the only way for extensions to use the resource owner
|
||||
mechanism with new kinds of objects; nowadays it easier to define a custom
|
||||
ResourceOwnerDesc struct.
|
||||
|
||||
|
||||
Releasing
|
||||
---------
|
||||
|
||||
Releasing the resources of a ResourceOwner happens in three phases:
|
||||
|
||||
1. "Before-locks" resources
|
||||
|
||||
2. Locks
|
||||
|
||||
3. "After-locks" resources
|
||||
|
||||
Each resource type specifies whether it needs to be released before or after
|
||||
locks. Each resource type also has a priority, which determines the order
|
||||
that the resources are released in. Note that the phases are performed fully
|
||||
for the whole tree of resource owners, before moving to the next phase, but
|
||||
the priority within each phase only determines the order within that
|
||||
ResourceOwner. Child resource owners are always handled before the parent,
|
||||
within each phase.
|
||||
|
||||
For example, imagine that you have two ResourceOwners, parent and child,
|
||||
as follows:
|
||||
|
||||
Parent
|
||||
parent resource BEFORE_LOCKS priority 1
|
||||
parent resource BEFORE_LOCKS priority 2
|
||||
parent resource AFTER_LOCKS priority 10001
|
||||
parent resource AFTER_LOCKS priority 10002
|
||||
Child
|
||||
child resource BEFORE_LOCKS priority 1
|
||||
child resource BEFORE_LOCKS priority 2
|
||||
child resource AFTER_LOCKS priority 10001
|
||||
child resource AFTER_LOCKS priority 10002
|
||||
|
||||
These resources would be released in the following order:
|
||||
|
||||
child resource BEFORE_LOCKS priority 1
|
||||
child resource BEFORE_LOCKS priority 2
|
||||
parent resource BEFORE_LOCKS priority 1
|
||||
parent resource BEFORE_LOCKS priority 2
|
||||
(locks)
|
||||
child resource AFTER_LOCKS priority 10001
|
||||
child resource AFTER_LOCKS priority 10002
|
||||
parent resource AFTER_LOCKS priority 10001
|
||||
parent resource AFTER_LOCKS priority 10002
|
||||
|
||||
To release all the resources, you need to call ResourceOwnerRelease() three
|
||||
times, once for each phase. You may perform additional tasks between the
|
||||
phases, but after the first call to ResourceOwnerRelease(), you cannot use
|
||||
the ResourceOwner to remember any more resources. You also cannot call
|
||||
ResourceOwnerForget on the resource owner to release any previously
|
||||
remembered resources "in retail", after you have started the release process.
|
||||
|
||||
Normally, you are expected to call ResourceOwnerForget on every resource so
|
||||
that at commit, the ResourceOwner is empty (locks are an exception). If there
|
||||
are any resources still held at commit, ResourceOwnerRelease will print a
|
||||
WARNING on each such resource. At abort, however, we truly rely on the
|
||||
ResourceOwner mechanism and it is normal that there are resources to be
|
||||
released.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -57,6 +57,7 @@
|
||||
#include "lib/pairingheap.h"
|
||||
#include "miscadmin.h"
|
||||
#include "port/pg_lfind.h"
|
||||
#include "storage/fd.h"
|
||||
#include "storage/predicate.h"
|
||||
#include "storage/proc.h"
|
||||
#include "storage/procarray.h"
|
||||
@@ -66,7 +67,7 @@
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/memutils.h"
|
||||
#include "utils/rel.h"
|
||||
#include "utils/resowner_private.h"
|
||||
#include "utils/resowner.h"
|
||||
#include "utils/snapmgr.h"
|
||||
#include "utils/syscache.h"
|
||||
#include "utils/timestamp.h"
|
||||
@@ -162,9 +163,34 @@ static List *exportedSnapshots = NIL;
|
||||
|
||||
/* Prototypes for local functions */
|
||||
static Snapshot CopySnapshot(Snapshot snapshot);
|
||||
static void UnregisterSnapshotNoOwner(Snapshot snapshot);
|
||||
static void FreeSnapshot(Snapshot snapshot);
|
||||
static void SnapshotResetXmin(void);
|
||||
|
||||
/* ResourceOwner callbacks to track snapshot references */
|
||||
static void ResOwnerReleaseSnapshot(Datum res);
|
||||
|
||||
static const ResourceOwnerDesc snapshot_resowner_desc =
|
||||
{
|
||||
.name = "snapshot reference",
|
||||
.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
|
||||
.release_priority = RELEASE_PRIO_SNAPSHOT_REFS,
|
||||
.ReleaseResource = ResOwnerReleaseSnapshot,
|
||||
.DebugPrint = NULL /* the default message is fine */
|
||||
};
|
||||
|
||||
/* Convenience wrappers over ResourceOwnerRemember/Forget */
|
||||
static inline void
|
||||
ResourceOwnerRememberSnapshot(ResourceOwner owner, Snapshot snap)
|
||||
{
|
||||
ResourceOwnerRemember(owner, PointerGetDatum(snap), &snapshot_resowner_desc);
|
||||
}
|
||||
static inline void
|
||||
ResourceOwnerForgetSnapshot(ResourceOwner owner, Snapshot snap)
|
||||
{
|
||||
ResourceOwnerForget(owner, PointerGetDatum(snap), &snapshot_resowner_desc);
|
||||
}
|
||||
|
||||
/*
|
||||
* Snapshot fields to be serialized.
|
||||
*
|
||||
@@ -796,7 +822,7 @@ RegisterSnapshotOnOwner(Snapshot snapshot, ResourceOwner owner)
|
||||
snap = snapshot->copied ? snapshot : CopySnapshot(snapshot);
|
||||
|
||||
/* and tell resowner.c about it */
|
||||
ResourceOwnerEnlargeSnapshots(owner);
|
||||
ResourceOwnerEnlarge(owner);
|
||||
snap->regd_count++;
|
||||
ResourceOwnerRememberSnapshot(owner, snap);
|
||||
|
||||
@@ -832,11 +858,16 @@ UnregisterSnapshotFromOwner(Snapshot snapshot, ResourceOwner owner)
|
||||
if (snapshot == NULL)
|
||||
return;
|
||||
|
||||
ResourceOwnerForgetSnapshot(owner, snapshot);
|
||||
UnregisterSnapshotNoOwner(snapshot);
|
||||
}
|
||||
|
||||
static void
|
||||
UnregisterSnapshotNoOwner(Snapshot snapshot)
|
||||
{
|
||||
Assert(snapshot->regd_count > 0);
|
||||
Assert(!pairingheap_is_empty(&RegisteredSnapshots));
|
||||
|
||||
ResourceOwnerForgetSnapshot(owner, snapshot);
|
||||
|
||||
snapshot->regd_count--;
|
||||
if (snapshot->regd_count == 0)
|
||||
pairingheap_remove(&RegisteredSnapshots, &snapshot->ph_node);
|
||||
@@ -1923,3 +1954,11 @@ XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot)
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* ResourceOwner callbacks */
|
||||
|
||||
static void
|
||||
ResOwnerReleaseSnapshot(Datum res)
|
||||
{
|
||||
UnregisterSnapshotNoOwner((Snapshot) DatumGetPointer(res));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user