postgresql/src/backend/utils/resowner/README

178 lines
7.4 KiB
Plaintext

src/backend/utils/resowner/README
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. resowner.c automatically transfers the resources to the
parent object when releasing the child. 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.
Usage
-----
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
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
ResourceOwner transfers lock ownership to the parent instead of actually
releasing the lock, if isCommit is true.
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.
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.