From b8bff07daa85c837a2747b4d35cd5a27e73fb7b2 Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas Date: Wed, 8 Nov 2023 13:30:50 +0200 Subject: [PATCH] Make ResourceOwners more easily extensible. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/backend/access/common/tupdesc.c | 51 +- src/backend/access/transam/xact.c | 14 + src/backend/jit/jit.c | 2 - src/backend/jit/llvm/llvmjit.c | 45 +- src/backend/storage/buffer/bufmgr.c | 164 +- src/backend/storage/buffer/localbuf.c | 21 +- src/backend/storage/file/fd.c | 57 +- src/backend/storage/ipc/dsm.c | 53 +- src/backend/storage/lmgr/lock.c | 2 +- src/backend/utils/cache/catcache.c | 125 +- src/backend/utils/cache/plancache.c | 50 +- src/backend/utils/cache/relcache.c | 64 +- src/backend/utils/resowner/README | 114 +- src/backend/utils/resowner/resowner.c | 1583 ++++++----------- src/backend/utils/time/snapmgr.c | 47 +- src/common/cryptohash_openssl.c | 49 +- src/common/hmac_openssl.c | 47 +- src/include/storage/buf_internals.h | 28 + src/include/storage/bufmgr.h | 4 +- src/include/utils/catcache.h | 3 - src/include/utils/plancache.h | 2 + src/include/utils/resowner.h | 94 +- src/pl/plpgsql/src/pl_exec.c | 2 +- src/pl/plpgsql/src/pl_handler.c | 6 +- src/test/modules/Makefile | 1 + src/test/modules/meson.build | 1 + src/test/modules/test_resowner/.gitignore | 4 + src/test/modules/test_resowner/Makefile | 24 + .../test_resowner/expected/test_resowner.out | 197 ++ src/test/modules/test_resowner/meson.build | 34 + .../test_resowner/sql/test_resowner.sql | 25 + .../test_resowner/test_resowner--1.0.sql | 30 + .../test_resowner/test_resowner.control | 4 + .../test_resowner/test_resowner_basic.c | 211 +++ .../test_resowner/test_resowner_many.c | 296 +++ src/tools/pgindent/typedefs.list | 6 +- 36 files changed, 2297 insertions(+), 1163 deletions(-) create mode 100644 src/test/modules/test_resowner/.gitignore create mode 100644 src/test/modules/test_resowner/Makefile create mode 100644 src/test/modules/test_resowner/expected/test_resowner.out create mode 100644 src/test/modules/test_resowner/meson.build create mode 100644 src/test/modules/test_resowner/sql/test_resowner.sql create mode 100644 src/test/modules/test_resowner/test_resowner--1.0.sql create mode 100644 src/test/modules/test_resowner/test_resowner.control create mode 100644 src/test/modules/test_resowner/test_resowner_basic.c create mode 100644 src/test/modules/test_resowner/test_resowner_many.c diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c index d119cfafb5..8826519e5e 100644 --- a/src/backend/access/common/tupdesc.c +++ b/src/backend/access/common/tupdesc.c @@ -27,9 +27,34 @@ #include "common/hashfn.h" #include "utils/builtins.h" #include "utils/datum.h" -#include "utils/resowner_private.h" +#include "utils/resowner.h" #include "utils/syscache.h" +/* ResourceOwner callbacks to hold tupledesc references */ +static void ResOwnerReleaseTupleDesc(Datum res); +static char *ResOwnerPrintTupleDesc(Datum res); + +static const ResourceOwnerDesc tupdesc_resowner_desc = +{ + .name = "tupdesc reference", + .release_phase = RESOURCE_RELEASE_AFTER_LOCKS, + .release_priority = RELEASE_PRIO_TUPDESC_REFS, + .ReleaseResource = ResOwnerReleaseTupleDesc, + .DebugPrint = ResOwnerPrintTupleDesc +}; + +/* Convenience wrappers over ResourceOwnerRemember/Forget */ +static inline void +ResourceOwnerRememberTupleDesc(ResourceOwner owner, TupleDesc tupdesc) +{ + ResourceOwnerRemember(owner, PointerGetDatum(tupdesc), &tupdesc_resowner_desc); +} + +static inline void +ResourceOwnerForgetTupleDesc(ResourceOwner owner, TupleDesc tupdesc) +{ + ResourceOwnerForget(owner, PointerGetDatum(tupdesc), &tupdesc_resowner_desc); +} /* * CreateTemplateTupleDesc @@ -364,7 +389,7 @@ IncrTupleDescRefCount(TupleDesc tupdesc) { Assert(tupdesc->tdrefcount >= 0); - ResourceOwnerEnlargeTupleDescs(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); tupdesc->tdrefcount++; ResourceOwnerRememberTupleDesc(CurrentResourceOwner, tupdesc); } @@ -847,3 +872,25 @@ TupleDescGetDefault(TupleDesc tupdesc, AttrNumber attnum) return result; } + +/* ResourceOwner callbacks */ + +static void +ResOwnerReleaseTupleDesc(Datum res) +{ + TupleDesc tupdesc = (TupleDesc) DatumGetPointer(res); + + /* Like DecrTupleDescRefCount, but don't call ResourceOwnerForget() */ + Assert(tupdesc->tdrefcount > 0); + if (--tupdesc->tdrefcount == 0) + FreeTupleDesc(tupdesc); +} + +static char * +ResOwnerPrintTupleDesc(Datum res) +{ + TupleDesc tupdesc = (TupleDesc) DatumGetPointer(res); + + return psprintf("TupleDesc %p (%u,%d)", + tupdesc, tupdesc->tdtypeid, tupdesc->tdtypmod); +} diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index 5d89f82818..74ce5f9491 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -5172,9 +5172,23 @@ AbortSubTransaction(void) ResourceOwnerRelease(s->curTransactionOwner, RESOURCE_RELEASE_BEFORE_LOCKS, false, false); + AtEOSubXact_RelationCache(false, s->subTransactionId, s->parent->subTransactionId); + + + /* + * AtEOSubXact_Inval sometimes needs to temporarily bump the refcount + * on the relcache entries that it processes. We cannot use the + * subtransaction's resource owner anymore, because we've already + * started releasing it. But we can use the parent resource owner. + */ + CurrentResourceOwner = s->parent->curTransactionOwner; + AtEOSubXact_Inval(false); + + CurrentResourceOwner = s->curTransactionOwner; + ResourceOwnerRelease(s->curTransactionOwner, RESOURCE_RELEASE_LOCKS, false, false); diff --git a/src/backend/jit/jit.c b/src/backend/jit/jit.c index 4da8fee20b..07461b9963 100644 --- a/src/backend/jit/jit.c +++ b/src/backend/jit/jit.c @@ -26,7 +26,6 @@ #include "jit/jit.h" #include "miscadmin.h" #include "utils/fmgrprotos.h" -#include "utils/resowner_private.h" /* GUCs */ bool jit_enabled = true; @@ -140,7 +139,6 @@ jit_release_context(JitContext *context) if (provider_successfully_loaded) provider.release_context(context); - ResourceOwnerForgetJIT(context->resowner, PointerGetDatum(context)); pfree(context); } diff --git a/src/backend/jit/llvm/llvmjit.c b/src/backend/jit/llvm/llvmjit.c index 58f638859a..2c8ac02550 100644 --- a/src/backend/jit/llvm/llvmjit.c +++ b/src/backend/jit/llvm/llvmjit.c @@ -45,7 +45,7 @@ #include "portability/instr_time.h" #include "storage/ipc.h" #include "utils/memutils.h" -#include "utils/resowner_private.h" +#include "utils/resowner.h" #define LLVMJIT_LLVM_CONTEXT_REUSE_MAX 100 @@ -131,6 +131,30 @@ static LLVMOrcLLJITRef llvm_create_jit_instance(LLVMTargetMachineRef tm); static char *llvm_error_message(LLVMErrorRef error); #endif /* LLVM_VERSION_MAJOR > 11 */ +/* ResourceOwner callbacks to hold JitContexts */ +static void ResOwnerReleaseJitContext(Datum res); + +static const ResourceOwnerDesc jit_resowner_desc = +{ + .name = "LLVM JIT context", + .release_phase = RESOURCE_RELEASE_BEFORE_LOCKS, + .release_priority = RELEASE_PRIO_JIT_CONTEXTS, + .ReleaseResource = ResOwnerReleaseJitContext, + .DebugPrint = NULL /* the default message is fine */ +}; + +/* Convenience wrappers over ResourceOwnerRemember/Forget */ +static inline void +ResourceOwnerRememberJIT(ResourceOwner owner, LLVMJitContext *handle) +{ + ResourceOwnerRemember(owner, PointerGetDatum(handle), &jit_resowner_desc); +} +static inline void +ResourceOwnerForgetJIT(ResourceOwner owner, LLVMJitContext *handle) +{ + ResourceOwnerForget(owner, PointerGetDatum(handle), &jit_resowner_desc); +} + PG_MODULE_MAGIC; @@ -220,7 +244,7 @@ llvm_create_context(int jitFlags) llvm_recreate_llvm_context(); - ResourceOwnerEnlargeJIT(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); context = MemoryContextAllocZero(TopMemoryContext, sizeof(LLVMJitContext)); @@ -228,7 +252,7 @@ llvm_create_context(int jitFlags) /* ensure cleanup */ context->base.resowner = CurrentResourceOwner; - ResourceOwnerRememberJIT(CurrentResourceOwner, PointerGetDatum(context)); + ResourceOwnerRememberJIT(CurrentResourceOwner, context); llvm_jit_context_in_use_count++; @@ -300,6 +324,9 @@ llvm_release_context(JitContext *context) llvm_jit_context->handles = NIL; llvm_leave_fatal_on_oom(); + + if (context->resowner) + ResourceOwnerForgetJIT(context->resowner, llvm_jit_context); } /* @@ -1394,3 +1421,15 @@ llvm_error_message(LLVMErrorRef error) } #endif /* LLVM_VERSION_MAJOR > 11 */ + +/* + * ResourceOwner callbacks + */ +static void +ResOwnerReleaseJitContext(Datum res) +{ + JitContext *context = (JitContext *) DatumGetPointer(res); + + context->resowner = NULL; + jit_release_context(context); +} diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 506f71e653..f7c67d504c 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -47,6 +47,7 @@ #include "postmaster/bgwriter.h" #include "storage/buf_internals.h" #include "storage/bufmgr.h" +#include "storage/fd.h" #include "storage/ipc.h" #include "storage/lmgr.h" #include "storage/proc.h" @@ -55,7 +56,7 @@ #include "utils/memdebug.h" #include "utils/ps_status.h" #include "utils/rel.h" -#include "utils/resowner_private.h" +#include "utils/resowner.h" #include "utils/timestamp.h" @@ -205,6 +206,30 @@ static PrivateRefCountEntry *GetPrivateRefCountEntry(Buffer buffer, bool do_move static inline int32 GetPrivateRefCount(Buffer buffer); static void ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref); +/* ResourceOwner callbacks to hold in-progress I/Os and buffer pins */ +static void ResOwnerReleaseBufferIO(Datum res); +static char *ResOwnerPrintBufferIO(Datum res); +static void ResOwnerReleaseBufferPin(Datum res); +static char *ResOwnerPrintBufferPin(Datum res); + +const ResourceOwnerDesc buffer_io_resowner_desc = +{ + .name = "buffer io", + .release_phase = RESOURCE_RELEASE_BEFORE_LOCKS, + .release_priority = RELEASE_PRIO_BUFFER_IOS, + .ReleaseResource = ResOwnerReleaseBufferIO, + .DebugPrint = ResOwnerPrintBufferIO +}; + +const ResourceOwnerDesc buffer_pin_resowner_desc = +{ + .name = "buffer pin", + .release_phase = RESOURCE_RELEASE_BEFORE_LOCKS, + .release_priority = RELEASE_PRIO_BUFFER_PINS, + .ReleaseResource = ResOwnerReleaseBufferPin, + .DebugPrint = ResOwnerPrintBufferPin +}; + /* * Ensure that the PrivateRefCountArray has sufficient space to store one more * entry. This has to be called before using NewPrivateRefCountEntry() to fill @@ -470,6 +495,7 @@ static BlockNumber ExtendBufferedRelShared(BufferManagerRelation bmr, static bool PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy); static void PinBuffer_Locked(BufferDesc *buf); static void UnpinBuffer(BufferDesc *buf); +static void UnpinBufferNoOwner(BufferDesc *buf); static void BufferSync(int flags); static uint32 WaitBufHdrUnlocked(BufferDesc *buf); static int SyncOneBuffer(int buf_id, bool skip_recently_used, @@ -477,7 +503,8 @@ static int SyncOneBuffer(int buf_id, bool skip_recently_used, static void WaitIO(BufferDesc *buf); static bool StartBufferIO(BufferDesc *buf, bool forInput); static void TerminateBufferIO(BufferDesc *buf, bool clear_dirty, - uint32 set_flag_bits); + uint32 set_flag_bits, bool forget_owner); +static void AbortBufferIO(Buffer buffer); static void shared_buffer_write_error_callback(void *arg); static void local_buffer_write_error_callback(void *arg); static BufferDesc *BufferAlloc(SMgrRelation smgr, @@ -639,7 +666,7 @@ ReadRecentBuffer(RelFileLocator rlocator, ForkNumber forkNum, BlockNumber blockN Assert(BufferIsValid(recent_buffer)); - ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); ReservePrivateRefCountEntry(); InitBufferTag(&tag, &rlocator, forkNum, blockNum); @@ -1173,7 +1200,7 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum, else { /* Set BM_VALID, terminate IO, and wake up any waiters */ - TerminateBufferIO(bufHdr, false, BM_VALID); + TerminateBufferIO(bufHdr, false, BM_VALID, true); } VacuumPageMiss++; @@ -1228,7 +1255,7 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum, uint32 victim_buf_state; /* Make sure we will have room to remember the buffer pin */ - ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); ReservePrivateRefCountEntry(); /* create a tag so we can lookup the buffer */ @@ -1315,9 +1342,8 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum, * use. * * We could do this after releasing the partition lock, but then we'd - * have to call ResourceOwnerEnlargeBuffers() & - * ReservePrivateRefCountEntry() before acquiring the lock, for the - * rare case of such a collision. + * have to call ResourceOwnerEnlarge() & ReservePrivateRefCountEntry() + * before acquiring the lock, for the rare case of such a collision. */ UnpinBuffer(victim_buf_hdr); @@ -1595,7 +1621,7 @@ GetVictimBuffer(BufferAccessStrategy strategy, IOContext io_context) * entry, and a resource owner slot for the pin. */ ReservePrivateRefCountEntry(); - ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); /* we return here if a prospective victim buffer gets used concurrently */ again: @@ -1946,7 +1972,7 @@ ExtendBufferedRelShared(BufferManagerRelation bmr, int existing_id; /* in case we need to pin an existing buffer below */ - ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); ReservePrivateRefCountEntry(); InitBufferTag(&tag, &bmr.smgr->smgr_rlocator.locator, fork, first_block + i); @@ -2090,7 +2116,7 @@ ExtendBufferedRelShared(BufferManagerRelation bmr, if (lock) LWLockAcquire(BufferDescriptorGetContentLock(buf_hdr), LW_EXCLUSIVE); - TerminateBufferIO(buf_hdr, false, BM_VALID); + TerminateBufferIO(buf_hdr, false, BM_VALID, true); } pgBufferUsage.shared_blks_written += extend_by; @@ -2283,7 +2309,7 @@ ReleaseAndReadBuffer(Buffer buffer, * taking the buffer header lock; instead update the state variable in loop of * CAS operations. Hopefully it's just a single CAS. * - * Note that ResourceOwnerEnlargeBuffers and ReservePrivateRefCountEntry() + * Note that ResourceOwnerEnlarge() and ReservePrivateRefCountEntry() * must have been done already. * * Returns true if buffer is BM_VALID, else false. This provision allows @@ -2379,7 +2405,7 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy) * * As this function is called with the spinlock held, the caller has to * previously call ReservePrivateRefCountEntry() and - * ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + * ResourceOwnerEnlarge(CurrentResourceOwner); * * Currently, no callers of this function want to modify the buffer's * usage_count at all, so there's no need for a strategy parameter. @@ -2440,6 +2466,15 @@ PinBuffer_Locked(BufferDesc *buf) */ static void UnpinBuffer(BufferDesc *buf) +{ + Buffer b = BufferDescriptorGetBuffer(buf); + + ResourceOwnerForgetBuffer(CurrentResourceOwner, b); + UnpinBufferNoOwner(buf); +} + +static void +UnpinBufferNoOwner(BufferDesc *buf) { PrivateRefCountEntry *ref; Buffer b = BufferDescriptorGetBuffer(buf); @@ -2449,9 +2484,6 @@ UnpinBuffer(BufferDesc *buf) /* not moving as we're likely deleting it soon anyway */ ref = GetPrivateRefCountEntry(b, false); Assert(ref != NULL); - - ResourceOwnerForgetBuffer(CurrentResourceOwner, b); - Assert(ref->refcount > 0); ref->refcount--; if (ref->refcount == 0) @@ -3122,7 +3154,7 @@ SyncOneBuffer(int buf_id, bool skip_recently_used, WritebackContext *wb_context) /* Make sure we can handle the pin */ ReservePrivateRefCountEntry(); - ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); /* * Check whether buffer needs writing. @@ -3252,6 +3284,7 @@ CheckForBufferLeaks(void) int RefCountErrors = 0; PrivateRefCountEntry *res; int i; + char *s; /* check the array */ for (i = 0; i < REFCOUNT_ARRAY_ENTRIES; i++) @@ -3260,7 +3293,10 @@ CheckForBufferLeaks(void) if (res->buffer != InvalidBuffer) { - PrintBufferLeakWarning(res->buffer); + s = DebugPrintBufferRefcount(res->buffer); + elog(WARNING, "buffer refcount leak: %s", s); + pfree(s); + RefCountErrors++; } } @@ -3273,7 +3309,9 @@ CheckForBufferLeaks(void) hash_seq_init(&hstat, PrivateRefCountHash); while ((res = (PrivateRefCountEntry *) hash_seq_search(&hstat)) != NULL) { - PrintBufferLeakWarning(res->buffer); + s = DebugPrintBufferRefcount(res->buffer); + elog(WARNING, "buffer refcount leak: %s", s); + pfree(s); RefCountErrors++; } } @@ -3285,12 +3323,13 @@ CheckForBufferLeaks(void) /* * Helper routine to issue warnings when a buffer is unexpectedly pinned */ -void -PrintBufferLeakWarning(Buffer buffer) +char * +DebugPrintBufferRefcount(Buffer buffer) { BufferDesc *buf; int32 loccount; char *path; + char *result; BackendId backend; uint32 buf_state; @@ -3312,13 +3351,13 @@ PrintBufferLeakWarning(Buffer buffer) path = relpathbackend(BufTagGetRelFileLocator(&buf->tag), backend, BufTagGetForkNum(&buf->tag)); buf_state = pg_atomic_read_u32(&buf->state); - elog(WARNING, - "buffer refcount leak: [%03d] " - "(rel=%s, blockNum=%u, flags=0x%x, refcount=%u %d)", - buffer, path, - buf->tag.blockNum, buf_state & BUF_FLAG_MASK, - BUF_STATE_GET_REFCOUNT(buf_state), loccount); + + result = psprintf("[%03d] (rel=%s, blockNum=%u, flags=0x%x, refcount=%u %d)", + buffer, path, + buf->tag.blockNum, buf_state & BUF_FLAG_MASK, + BUF_STATE_GET_REFCOUNT(buf_state), loccount); pfree(path); + return result; } /* @@ -3522,7 +3561,7 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln, IOObject io_object, * Mark the buffer as clean (unless BM_JUST_DIRTIED has become set) and * end the BM_IO_IN_PROGRESS state. */ - TerminateBufferIO(buf, true, 0); + TerminateBufferIO(buf, true, 0, true); TRACE_POSTGRESQL_BUFFER_FLUSH_DONE(BufTagGetForkNum(&buf->tag), buf->tag.blockNum, @@ -4182,7 +4221,7 @@ FlushRelationBuffers(Relation rel) /* Make sure we can handle the pin */ ReservePrivateRefCountEntry(); - ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); buf_state = LockBufHdr(bufHdr); if (BufTagMatchesRelFileLocator(&bufHdr->tag, &rel->rd_locator) && @@ -4279,7 +4318,7 @@ FlushRelationsAllBuffers(SMgrRelation *smgrs, int nrels) /* Make sure we can handle the pin */ ReservePrivateRefCountEntry(); - ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); buf_state = LockBufHdr(bufHdr); if (BufTagMatchesRelFileLocator(&bufHdr->tag, &srelent->rlocator) && @@ -4489,7 +4528,7 @@ FlushDatabaseBuffers(Oid dbid) /* Make sure we can handle the pin */ ReservePrivateRefCountEntry(); - ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); buf_state = LockBufHdr(bufHdr); if (bufHdr->tag.dbOid == dbid && @@ -4566,7 +4605,7 @@ void IncrBufferRefCount(Buffer buffer) { Assert(BufferIsPinned(buffer)); - ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); if (BufferIsLocal(buffer)) LocalRefCount[-buffer - 1]++; else @@ -5164,7 +5203,7 @@ StartBufferIO(BufferDesc *buf, bool forInput) { uint32 buf_state; - ResourceOwnerEnlargeBufferIOs(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); for (;;) { @@ -5209,9 +5248,14 @@ StartBufferIO(BufferDesc *buf, bool forInput) * set_flag_bits gets ORed into the buffer's flags. It must include * BM_IO_ERROR in a failure case. For successful completion it could * be 0, or BM_VALID if we just finished reading in the page. + * + * If forget_owner is true, we release the buffer I/O from the current + * resource owner. (forget_owner=false is used when the resource owner itself + * is being released) */ static void -TerminateBufferIO(BufferDesc *buf, bool clear_dirty, uint32 set_flag_bits) +TerminateBufferIO(BufferDesc *buf, bool clear_dirty, uint32 set_flag_bits, + bool forget_owner) { uint32 buf_state; @@ -5226,8 +5270,9 @@ TerminateBufferIO(BufferDesc *buf, bool clear_dirty, uint32 set_flag_bits) buf_state |= set_flag_bits; UnlockBufHdr(buf, buf_state); - ResourceOwnerForgetBufferIO(CurrentResourceOwner, - BufferDescriptorGetBuffer(buf)); + if (forget_owner) + ResourceOwnerForgetBufferIO(CurrentResourceOwner, + BufferDescriptorGetBuffer(buf)); ConditionVariableBroadcast(BufferDescriptorGetIOCV(buf)); } @@ -5240,8 +5285,12 @@ TerminateBufferIO(BufferDesc *buf, bool clear_dirty, uint32 set_flag_bits) * * If I/O was in progress, we always set BM_IO_ERROR, even though it's * possible the error condition wasn't related to the I/O. + * + * Note: this does not remove the buffer I/O from the resource owner. + * That's correct when we're releasing the whole resource owner, but + * beware if you use this in other contexts. */ -void +static void AbortBufferIO(Buffer buffer) { BufferDesc *buf_hdr = GetBufferDescriptor(buffer - 1); @@ -5277,7 +5326,7 @@ AbortBufferIO(Buffer buffer) } } - TerminateBufferIO(buf_hdr, false, BM_IO_ERROR); + TerminateBufferIO(buf_hdr, false, BM_IO_ERROR, false); } /* @@ -5629,3 +5678,42 @@ IssuePendingWritebacks(WritebackContext *wb_context, IOContext io_context) wb_context->nr_pending = 0; } + +/* ResourceOwner callbacks */ + +static void +ResOwnerReleaseBufferIO(Datum res) +{ + Buffer buffer = DatumGetInt32(res); + + AbortBufferIO(buffer); +} + +static char * +ResOwnerPrintBufferIO(Datum res) +{ + Buffer buffer = DatumGetInt32(res); + + return psprintf("lost track of buffer IO on buffer %d", buffer); +} + +static void +ResOwnerReleaseBufferPin(Datum res) +{ + Buffer buffer = DatumGetInt32(res); + + /* Like ReleaseBuffer, but don't call ResourceOwnerForgetBuffer */ + if (!BufferIsValid(buffer)) + elog(ERROR, "bad buffer ID: %d", buffer); + + if (BufferIsLocal(buffer)) + UnpinLocalBufferNoOwner(buffer); + else + UnpinBufferNoOwner(GetBufferDescriptor(buffer - 1)); +} + +static char * +ResOwnerPrintBufferPin(Datum res) +{ + return DebugPrintBufferRefcount(DatumGetInt32(res)); +} diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c index b8715b5465..4efb34b75a 100644 --- a/src/backend/storage/buffer/localbuf.c +++ b/src/backend/storage/buffer/localbuf.c @@ -21,9 +21,10 @@ #include "pgstat.h" #include "storage/buf_internals.h" #include "storage/bufmgr.h" +#include "storage/fd.h" #include "utils/guc_hooks.h" #include "utils/memutils.h" -#include "utils/resowner_private.h" +#include "utils/resowner.h" /*#define LBDEBUG*/ @@ -130,7 +131,7 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum, if (LocalBufHash == NULL) InitLocalBuffers(); - ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); /* See if the desired buffer already exists */ hresult = (LocalBufferLookupEnt *) @@ -182,7 +183,7 @@ GetLocalVictimBuffer(void) uint32 buf_state; BufferDesc *bufHdr; - ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); /* * Need to get a new buffer. We use a clock sweep algorithm (essentially @@ -674,6 +675,13 @@ PinLocalBuffer(BufferDesc *buf_hdr, bool adjust_usagecount) void UnpinLocalBuffer(Buffer buffer) +{ + UnpinLocalBufferNoOwner(buffer); + ResourceOwnerForgetBuffer(CurrentResourceOwner, buffer); +} + +void +UnpinLocalBufferNoOwner(Buffer buffer) { int buffid = -buffer - 1; @@ -681,7 +689,6 @@ UnpinLocalBuffer(Buffer buffer) Assert(LocalRefCount[buffid] > 0); Assert(NLocalPinnedBuffers > 0); - ResourceOwnerForgetBuffer(CurrentResourceOwner, buffer); if (--LocalRefCount[buffid] == 0) NLocalPinnedBuffers--; } @@ -785,8 +792,12 @@ CheckForLocalBufferLeaks(void) if (LocalRefCount[i] != 0) { Buffer b = -i - 1; + char *s; + + s = DebugPrintBufferRefcount(b); + elog(WARNING, "local buffer refcount leak: %s", s); + pfree(s); - PrintBufferLeakWarning(b); RefCountErrors++; } } diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c index b884df71bf..f691ba0932 100644 --- a/src/backend/storage/file/fd.c +++ b/src/backend/storage/file/fd.c @@ -99,7 +99,7 @@ #include "storage/ipc.h" #include "utils/guc.h" #include "utils/guc_hooks.h" -#include "utils/resowner_private.h" +#include "utils/resowner.h" #include "utils/varlena.h" /* Define PG_FLUSH_DATA_WORKS if we have an implementation for pg_flush_data */ @@ -354,6 +354,31 @@ static void unlink_if_exists_fname(const char *fname, bool isdir, int elevel); static int fsync_parent_path(const char *fname, int elevel); +/* ResourceOwner callbacks to hold virtual file descriptors */ +static void ResOwnerReleaseFile(Datum res); +static char *ResOwnerPrintFile(Datum res); + +static const ResourceOwnerDesc file_resowner_desc = +{ + .name = "File", + .release_phase = RESOURCE_RELEASE_AFTER_LOCKS, + .release_priority = RELEASE_PRIO_FILES, + .ReleaseResource = ResOwnerReleaseFile, + .DebugPrint = ResOwnerPrintFile +}; + +/* Convenience wrappers over ResourceOwnerRemember/Forget */ +static inline void +ResourceOwnerRememberFile(ResourceOwner owner, File file) +{ + ResourceOwnerRemember(owner, Int32GetDatum(file), &file_resowner_desc); +} +static inline void +ResourceOwnerForgetFile(ResourceOwner owner, File file) +{ + ResourceOwnerForget(owner, Int32GetDatum(file), &file_resowner_desc); +} + /* * pg_fsync --- do fsync with or without writethrough */ @@ -1492,7 +1517,7 @@ ReportTemporaryFileUsage(const char *path, off_t size) /* * Called to register a temporary file for automatic close. - * ResourceOwnerEnlargeFiles(CurrentResourceOwner) must have been called + * ResourceOwnerEnlarge(CurrentResourceOwner) must have been called * before the file was opened. */ static void @@ -1684,7 +1709,7 @@ OpenTemporaryFile(bool interXact) * open it, if we'll be registering it below. */ if (!interXact) - ResourceOwnerEnlargeFiles(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); /* * If some temp tablespace(s) have been given to us, try to use the next @@ -1816,7 +1841,7 @@ PathNameCreateTemporaryFile(const char *path, bool error_on_failure) Assert(temporary_files_allowed); /* check temp file access is up */ - ResourceOwnerEnlargeFiles(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); /* * Open the file. Note: we don't use O_EXCL, in case there is an orphaned @@ -1856,7 +1881,7 @@ PathNameOpenTemporaryFile(const char *path, int mode) Assert(temporary_files_allowed); /* check temp file access is up */ - ResourceOwnerEnlargeFiles(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); file = PathNameOpenFile(path, mode | PG_BINARY); @@ -3972,3 +3997,25 @@ assign_debug_io_direct(const char *newval, void *extra) io_direct_flags = *flags; } + +/* ResourceOwner callbacks */ + +static void +ResOwnerReleaseFile(Datum res) +{ + File file = (File) DatumGetInt32(res); + Vfd *vfdP; + + Assert(FileIsValid(file)); + + vfdP = &VfdCache[file]; + vfdP->resowner = NULL; + + FileClose(file); +} + +static char * +ResOwnerPrintFile(Datum res) +{ + return psprintf("File %d", DatumGetInt32(res)); +} diff --git a/src/backend/storage/ipc/dsm.c b/src/backend/storage/ipc/dsm.c index 7e4e27810e..628f3ecd3f 100644 --- a/src/backend/storage/ipc/dsm.c +++ b/src/backend/storage/ipc/dsm.c @@ -38,13 +38,15 @@ #include "miscadmin.h" #include "port/pg_bitutils.h" #include "storage/dsm.h" +#include "storage/fd.h" #include "storage/ipc.h" #include "storage/lwlock.h" #include "storage/pg_shmem.h" +#include "storage/shmem.h" #include "utils/freepage.h" #include "utils/guc.h" #include "utils/memutils.h" -#include "utils/resowner_private.h" +#include "utils/resowner.h" #define PG_DYNSHMEM_CONTROL_MAGIC 0x9a503d32 @@ -140,6 +142,32 @@ static dsm_control_header *dsm_control; static Size dsm_control_mapped_size = 0; static void *dsm_control_impl_private = NULL; + +/* ResourceOwner callbacks to hold DSM segments */ +static void ResOwnerReleaseDSM(Datum res); +static char *ResOwnerPrintDSM(Datum res); + +static const ResourceOwnerDesc dsm_resowner_desc = +{ + .name = "dynamic shared memory segment", + .release_phase = RESOURCE_RELEASE_BEFORE_LOCKS, + .release_priority = RELEASE_PRIO_DSMS, + .ReleaseResource = ResOwnerReleaseDSM, + .DebugPrint = ResOwnerPrintDSM +}; + +/* Convenience wrappers over ResourceOwnerRemember/Forget */ +static inline void +ResourceOwnerRememberDSM(ResourceOwner owner, dsm_segment *seg) +{ + ResourceOwnerRemember(owner, PointerGetDatum(seg), &dsm_resowner_desc); +} +static inline void +ResourceOwnerForgetDSM(ResourceOwner owner, dsm_segment *seg) +{ + ResourceOwnerForget(owner, PointerGetDatum(seg), &dsm_resowner_desc); +} + /* * Start up the dynamic shared memory system. * @@ -907,7 +935,7 @@ void dsm_unpin_mapping(dsm_segment *seg) { Assert(seg->resowner == NULL); - ResourceOwnerEnlargeDSMs(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); seg->resowner = CurrentResourceOwner; ResourceOwnerRememberDSM(seg->resowner, seg); } @@ -1176,7 +1204,7 @@ dsm_create_descriptor(void) dsm_segment *seg; if (CurrentResourceOwner) - ResourceOwnerEnlargeDSMs(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); seg = MemoryContextAlloc(TopMemoryContext, sizeof(dsm_segment)); dlist_push_head(&dsm_segment_list, &seg->node); @@ -1255,3 +1283,22 @@ is_main_region_dsm_handle(dsm_handle handle) { return handle & 1; } + +/* ResourceOwner callbacks */ + +static void +ResOwnerReleaseDSM(Datum res) +{ + dsm_segment *seg = (dsm_segment *) DatumGetPointer(res); + + seg->resowner = NULL; + dsm_detach(seg); +} +static char * +ResOwnerPrintDSM(Datum res) +{ + dsm_segment *seg = (dsm_segment *) DatumGetPointer(res); + + return psprintf("dynamic shared memory segment %u", + dsm_segment_handle(seg)); +} diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index ec6240fbae..b8c57b3e16 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -48,7 +48,7 @@ #include "storage/standby.h" #include "utils/memutils.h" #include "utils/ps_status.h" -#include "utils/resowner_private.h" +#include "utils/resowner.h" /* This configuration variable is used to set the lock table size */ diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c index 18db7e78e2..2e2e4d9f1f 100644 --- a/src/backend/utils/cache/catcache.c +++ b/src/backend/utils/cache/catcache.c @@ -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); } diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c index 7d4168f82f..8f95520f2f 100644 --- a/src/backend/utils/cache/plancache.c +++ b/src/backend/utils/cache/plancache.c @@ -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); +} diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index a49ab465b3..b3faccbefe 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -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); +} diff --git a/src/backend/utils/resowner/README b/src/backend/utils/resowner/README index f94c9700df..d67df3faed 100644 --- a/src/backend/utils/resowner/README +++ b/src/backend/utils/resowner/README @@ -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. diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c index f926f1faad..6e4020f241 100644 --- a/src/backend/utils/resowner/resowner.c +++ b/src/backend/utils/resowner/resowner.c @@ -6,7 +6,32 @@ * 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. + * See utils/resowner/README for more info on how to use it. + * + * The implementation consists of a small fixed-size array and a hash table. + * New entries are inserted to the fixed-size array, and when the array + * fills up, all the entries are moved to the hash table. This way, the + * array always contains a few most recently remembered references. To find + * a particular reference, you need to search both the array and the hash + * table. + * + * The most frequent usage is that a resource is remembered, and forgotten + * shortly thereafter. For example, pin a buffer, read one tuple from it, + * release the pin. Linearly scanning the small array handles that case + * efficiently. However, some resources are held for a longer time, and + * sometimes a lot of resources need to be held simultaneously. The hash + * table handles those cases. + * + * When it's time to release the resources, we sort them according to the + * release-priority of each resource, and release them in that order. + * + * Local lock references are special, they are not stored in the array or + * the hash table. Instead, each resource owner has a separate small cache + * of locks it owns. The lock manager has the same information in its local + * lock hash table, and we fall back on that if the cache overflows, but + * traversing the hash table is slower when there are a lot of locks + * belonging to other resource owners. This is to speed up bulk releasing + * or reassigning locks from a resource owner to its parent. * * * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group @@ -20,85 +45,54 @@ */ #include "postgres.h" -#include "common/cryptohash.h" #include "common/hashfn.h" -#include "common/hmac.h" -#include "jit/jit.h" -#include "storage/bufmgr.h" #include "storage/ipc.h" #include "storage/predicate.h" #include "storage/proc.h" #include "utils/memutils.h" -#include "utils/rel.h" -#include "utils/resowner_private.h" -#include "utils/snapmgr.h" - +#include "utils/resowner.h" /* - * All resource IDs managed by this code are required to fit into a Datum, + * ResourceElem represents a reference associated with a resource owner. + * + * All objects managed by this code are required to fit into a Datum, * which is fine since they are generally pointers or integers. - * - * Provide Datum conversion macros for a couple of things that are really - * just "int". */ -#define FileGetDatum(file) Int32GetDatum(file) -#define DatumGetFile(datum) ((File) DatumGetInt32(datum)) -#define BufferGetDatum(buffer) Int32GetDatum(buffer) -#define DatumGetBuffer(datum) ((Buffer) DatumGetInt32(datum)) - -/* - * ResourceArray is a common structure for storing all types of resource IDs. - * - * We manage small sets of resource IDs by keeping them in a simple array: - * itemsarr[k] holds an ID, for 0 <= k < nitems <= maxitems = capacity. - * - * If a set grows large, we switch over to using open-addressing hashing. - * Then, itemsarr[] is a hash table of "capacity" slots, with each - * slot holding either an ID or "invalidval". nitems is the number of valid - * items present; if it would exceed maxitems, we enlarge the array and - * re-hash. In this mode, maxitems should be rather less than capacity so - * that we don't waste too much time searching for empty slots. - * - * In either mode, lastidx remembers the location of the last item inserted - * or returned by GetAny; this speeds up searches in ResourceArrayRemove. - */ -typedef struct ResourceArray +typedef struct ResourceElem { - Datum *itemsarr; /* buffer for storing values */ - Datum invalidval; /* value that is considered invalid */ - uint32 capacity; /* allocated length of itemsarr[] */ - uint32 nitems; /* how many items are stored in items array */ - uint32 maxitems; /* current limit on nitems before enlarging */ - uint32 lastidx; /* index of last item returned by GetAny */ -} ResourceArray; + Datum item; + const ResourceOwnerDesc *kind; /* NULL indicates a free hash table slot */ +} ResourceElem; /* - * Initially allocated size of a ResourceArray. Must be power of two since - * we'll use (arraysize - 1) as mask for hashing. + * Size of the fixed-size array to hold most-recently remembered resources. */ -#define RESARRAY_INIT_SIZE 16 +#define RESOWNER_ARRAY_SIZE 32 /* - * When to switch to hashing vs. simple array logic in a ResourceArray. + * Initially allocated size of a ResourceOwner's hash table. Must be power of + * two because we use (capacity - 1) as mask for hashing. */ -#define RESARRAY_MAX_ARRAY 64 -#define RESARRAY_IS_ARRAY(resarr) ((resarr)->capacity <= RESARRAY_MAX_ARRAY) +#define RESOWNER_HASH_INIT_SIZE 64 /* - * How many items may be stored in a resource array of given capacity. - * When this number is reached, we must resize. - */ -#define RESARRAY_MAX_ITEMS(capacity) \ - ((capacity) <= RESARRAY_MAX_ARRAY ? (capacity) : (capacity)/4 * 3) - -/* - * To speed up bulk releasing or reassigning locks from a resource owner to - * its parent, each resource owner has a small cache of locks it owns. The - * lock manager has the same information in its local lock hash table, and - * we fall back on that if cache overflows, but traversing the hash table - * is slower when there are a lot of locks belonging to other resource owners. + * How many items may be stored in a hash table of given capacity. When this + * number is reached, we must resize. * - * MAX_RESOWNER_LOCKS is the size of the per-resource owner cache. It's + * The hash table must always have enough free space that we can copy the + * entries from the array to it, in ResourceOwnerSort. We also insist that + * the initial size is large enough that we don't hit the max size immediately + * when it's created. Aside from those limitations, 0.75 is a reasonable fill + * factor. + */ +#define RESOWNER_HASH_MAX_ITEMS(capacity) \ + Min(capacity - RESOWNER_ARRAY_SIZE, (capacity)/4 * 3) + +StaticAssertDecl(RESOWNER_HASH_MAX_ITEMS(RESOWNER_HASH_INIT_SIZE) >= RESOWNER_ARRAY_SIZE, + "initial hash size too small compared to array size"); + +/* + * MAX_RESOWNER_LOCKS is the size of the per-resource owner locks cache. It's * chosen based on some testing with pg_dump with a large schema. When the * tests were done (on 9.2), resource owners in a pg_dump run contained up * to 9 locks, regardless of the schema size, except for the top resource @@ -119,25 +113,48 @@ typedef struct ResourceOwnerData ResourceOwner nextchild; /* next child of same parent */ const char *name; /* name (just for debugging) */ - /* We have built-in support for remembering: */ - ResourceArray bufferarr; /* owned buffers */ - ResourceArray bufferioarr; /* in-progress buffer IO */ - ResourceArray catrefarr; /* catcache references */ - ResourceArray catlistrefarr; /* catcache-list pins */ - ResourceArray relrefarr; /* relcache references */ - ResourceArray planrefarr; /* plancache references */ - ResourceArray tupdescarr; /* tupdesc references */ - ResourceArray snapshotarr; /* snapshot references */ - ResourceArray filearr; /* open temporary files */ - ResourceArray dsmarr; /* dynamic shmem segments */ - ResourceArray jitarr; /* JIT contexts */ - ResourceArray cryptohasharr; /* cryptohash contexts */ - ResourceArray hmacarr; /* HMAC contexts */ + /* + * When ResourceOwnerRelease is called, we sort the 'hash' and 'arr' by + * the release priority. After that, no new resources can be remembered + * or forgotten in retail. We have separate flags because + * ResourceOwnerReleaseAllOfKind() temporarily sets 'releasing' without + * sorting the arrays. + */ + bool releasing; + bool sorted; /* are 'hash' and 'arr' sorted by priority? */ - /* We can remember up to MAX_RESOWNER_LOCKS references to local locks. */ - int nlocks; /* number of owned locks */ + /* + * Number of items in the locks cache, array, and hash table respectively. + * (These are packed together to avoid padding in the struct.) + */ + uint8 nlocks; /* number of owned locks */ + uint8 narr; /* how many items are stored in the array */ + uint32 nhash; /* how many items are stored in the hash */ + + /* + * The fixed-size array for recent resources. + * + * If 'sorted' is set, the contents are sorted by release priority. + */ + ResourceElem arr[RESOWNER_ARRAY_SIZE]; + + /* + * The hash table. Uses open-addressing. 'nhash' is the number of items + * present; if it would exceed 'grow_at', we enlarge it and re-hash. + * 'grow_at' should be rather less than 'capacity' so that we don't waste + * too much time searching for empty slots. + * + * If 'sorted' is set, the contents are no longer hashed, but sorted by + * release priority. The first 'nhash' elements are occupied, the rest + * are empty. + */ + ResourceElem *hash; + uint32 capacity; /* allocated length of hash[] */ + uint32 grow_at; /* grow hash when reach this */ + + /* The local locks cache. */ LOCALLOCK *locks[MAX_RESOWNER_LOCKS]; /* list of owned locks */ -} ResourceOwnerData; +} ResourceOwnerData; /***************************************************************************** @@ -149,6 +166,13 @@ ResourceOwner CurTransactionResourceOwner = NULL; ResourceOwner TopTransactionResourceOwner = NULL; ResourceOwner AuxProcessResourceOwner = NULL; +/* #define RESOWNER_STATS */ + +#ifdef RESOWNER_STATS +static int narray_lookups = 0; +static int nhash_lookups = 0; +#endif + /* * List of add-on callbacks for resource releasing */ @@ -163,253 +187,204 @@ static ResourceReleaseCallbackItem *ResourceRelease_callbacks = NULL; /* Internal routines */ -static void ResourceArrayInit(ResourceArray *resarr, Datum invalidval); -static void ResourceArrayEnlarge(ResourceArray *resarr); -static void ResourceArrayAdd(ResourceArray *resarr, Datum value); -static bool ResourceArrayRemove(ResourceArray *resarr, Datum value); -static bool ResourceArrayGetAny(ResourceArray *resarr, Datum *value); -static void ResourceArrayFree(ResourceArray *resarr); +static inline uint32 hash_resource_elem(Datum value, const ResourceOwnerDesc *kind); +static void ResourceOwnerAddToHash(ResourceOwner owner, Datum value, + const ResourceOwnerDesc *kind); +static int resource_priority_cmp(const void *a, const void *b); +static void ResourceOwnerSort(ResourceOwner owner); +static void ResourceOwnerReleaseAll(ResourceOwner owner, + ResourceReleasePhase phase, + bool printLeakWarnings); static void ResourceOwnerReleaseInternal(ResourceOwner owner, ResourceReleasePhase phase, bool isCommit, bool isTopLevel); static void ReleaseAuxProcessResourcesCallback(int code, Datum arg); -static void PrintRelCacheLeakWarning(Relation rel); -static void PrintPlanCacheLeakWarning(CachedPlan *plan); -static void PrintTupleDescLeakWarning(TupleDesc tupdesc); -static void PrintSnapshotLeakWarning(Snapshot snapshot); -static void PrintFileLeakWarning(File file); -static void PrintDSMLeakWarning(dsm_segment *seg); -static void PrintCryptoHashLeakWarning(Datum handle); -static void PrintHMACLeakWarning(Datum handle); /***************************************************************************** * INTERNAL ROUTINES * *****************************************************************************/ - -/* - * Initialize a ResourceArray - */ -static void -ResourceArrayInit(ResourceArray *resarr, Datum invalidval) +static inline uint32 +hash_resource_elem(Datum value, const ResourceOwnerDesc *kind) { - /* Assert it's empty */ - Assert(resarr->itemsarr == NULL); - Assert(resarr->capacity == 0); - Assert(resarr->nitems == 0); - Assert(resarr->maxitems == 0); - /* Remember the appropriate "invalid" value */ - resarr->invalidval = invalidval; - /* We don't allocate any storage until needed */ + Datum data[2]; + + data[0] = value; + data[1] = PointerGetDatum(kind); + + return hash_bytes((unsigned char *) &data, 2 * SIZEOF_DATUM); } /* - * Make sure there is room for at least one more resource in an array. - * - * This is separate from actually inserting a resource because if we run out - * of memory, it's critical to do so *before* acquiring the resource. + * Adds 'value' of given 'kind' to the ResourceOwner's hash table */ static void -ResourceArrayEnlarge(ResourceArray *resarr) -{ - uint32 i, - oldcap, - newcap; - Datum *olditemsarr; - Datum *newitemsarr; - - if (resarr->nitems < resarr->maxitems) - return; /* no work needed */ - - olditemsarr = resarr->itemsarr; - oldcap = resarr->capacity; - - /* Double the capacity of the array (capacity must stay a power of 2!) */ - newcap = (oldcap > 0) ? oldcap * 2 : RESARRAY_INIT_SIZE; - newitemsarr = (Datum *) MemoryContextAlloc(TopMemoryContext, - newcap * sizeof(Datum)); - for (i = 0; i < newcap; i++) - newitemsarr[i] = resarr->invalidval; - - /* We assume we can't fail below this point, so OK to scribble on resarr */ - resarr->itemsarr = newitemsarr; - resarr->capacity = newcap; - resarr->maxitems = RESARRAY_MAX_ITEMS(newcap); - resarr->nitems = 0; - - if (olditemsarr != NULL) - { - /* - * Transfer any pre-existing entries into the new array; they don't - * necessarily go where they were before, so this simple logic is the - * best way. Note that if we were managing the set as a simple array, - * the entries after nitems are garbage, but that shouldn't matter - * because we won't get here unless nitems was equal to oldcap. - */ - for (i = 0; i < oldcap; i++) - { - if (olditemsarr[i] != resarr->invalidval) - ResourceArrayAdd(resarr, olditemsarr[i]); - } - - /* And release old array. */ - pfree(olditemsarr); - } - - Assert(resarr->nitems < resarr->maxitems); -} - -/* - * Add a resource to ResourceArray - * - * Caller must have previously done ResourceArrayEnlarge() - */ -static void -ResourceArrayAdd(ResourceArray *resarr, Datum value) +ResourceOwnerAddToHash(ResourceOwner owner, Datum value, const ResourceOwnerDesc *kind) { + uint32 mask = owner->capacity - 1; uint32 idx; - Assert(value != resarr->invalidval); - Assert(resarr->nitems < resarr->maxitems); + Assert(kind != NULL); - if (RESARRAY_IS_ARRAY(resarr)) + /* Insert into first free slot at or after hash location. */ + idx = hash_resource_elem(value, kind) & mask; + for (;;) { - /* Append to linear array. */ - idx = resarr->nitems; + if (owner->hash[idx].kind == NULL) + break; /* found a free slot */ + idx = (idx + 1) & mask; } - else - { - /* Insert into first free slot at or after hash location. */ - uint32 mask = resarr->capacity - 1; - - idx = DatumGetUInt32(hash_any((void *) &value, sizeof(value))) & mask; - for (;;) - { - if (resarr->itemsarr[idx] == resarr->invalidval) - break; - idx = (idx + 1) & mask; - } - } - resarr->lastidx = idx; - resarr->itemsarr[idx] = value; - resarr->nitems++; + owner->hash[idx].item = value; + owner->hash[idx].kind = kind; + owner->nhash++; } /* - * Remove a resource from ResourceArray - * - * Returns true on success, false if resource was not found. - * - * Note: if same resource ID appears more than once, one instance is removed. + * Comparison function to sort by release phase and priority */ -static bool -ResourceArrayRemove(ResourceArray *resarr, Datum value) +static int +resource_priority_cmp(const void *a, const void *b) { - uint32 i, - idx, - lastidx = resarr->lastidx; + const ResourceElem *ra = (const ResourceElem *) a; + const ResourceElem *rb = (const ResourceElem *) b; - Assert(value != resarr->invalidval); - - /* Search through all items, but try lastidx first. */ - if (RESARRAY_IS_ARRAY(resarr)) + /* Note: reverse order */ + if (ra->kind->release_phase == rb->kind->release_phase) { - if (lastidx < resarr->nitems && - resarr->itemsarr[lastidx] == value) - { - resarr->itemsarr[lastidx] = resarr->itemsarr[resarr->nitems - 1]; - resarr->nitems--; - /* Update lastidx to make reverse-order removals fast. */ - resarr->lastidx = resarr->nitems - 1; - return true; - } - for (i = 0; i < resarr->nitems; i++) - { - if (resarr->itemsarr[i] == value) - { - resarr->itemsarr[i] = resarr->itemsarr[resarr->nitems - 1]; - resarr->nitems--; - /* Update lastidx to make reverse-order removals fast. */ - resarr->lastidx = resarr->nitems - 1; - return true; - } - } + if (ra->kind->release_priority == rb->kind->release_priority) + return 0; + else if (ra->kind->release_priority > rb->kind->release_priority) + return -1; + else + return 1; } + else if (ra->kind->release_phase > rb->kind->release_phase) + return -1; else - { - uint32 mask = resarr->capacity - 1; - - if (lastidx < resarr->capacity && - resarr->itemsarr[lastidx] == value) - { - resarr->itemsarr[lastidx] = resarr->invalidval; - resarr->nitems--; - return true; - } - idx = DatumGetUInt32(hash_any((void *) &value, sizeof(value))) & mask; - for (i = 0; i < resarr->capacity; i++) - { - if (resarr->itemsarr[idx] == value) - { - resarr->itemsarr[idx] = resarr->invalidval; - resarr->nitems--; - return true; - } - idx = (idx + 1) & mask; - } - } - - return false; + return 1; } /* - * Get any convenient entry in a ResourceArray. + * Sort resources in reverse release priority. * - * "Convenient" is defined as "easy for ResourceArrayRemove to remove"; - * we help that along by setting lastidx to match. This avoids O(N^2) cost - * when removing all ResourceArray items during ResourceOwner destruction. - * - * Returns true if we found an element, or false if the array is empty. - */ -static bool -ResourceArrayGetAny(ResourceArray *resarr, Datum *value) -{ - if (resarr->nitems == 0) - return false; - - if (RESARRAY_IS_ARRAY(resarr)) - { - /* Linear array: just return the first element. */ - resarr->lastidx = 0; - } - else - { - /* Hash: search forward from wherever we were last. */ - uint32 mask = resarr->capacity - 1; - - for (;;) - { - resarr->lastidx &= mask; - if (resarr->itemsarr[resarr->lastidx] != resarr->invalidval) - break; - resarr->lastidx++; - } - } - - *value = resarr->itemsarr[resarr->lastidx]; - return true; -} - -/* - * Trash a ResourceArray (we don't care about its state after this) + * If the hash table is in use, all the elements from the fixed-size array are + * moved to the hash table, and then the hash table is sorted. If there is no + * hash table, then the fixed-size array is sorted directly. In either case, + * the result is one sorted array that contains all the resources. */ static void -ResourceArrayFree(ResourceArray *resarr) +ResourceOwnerSort(ResourceOwner owner) { - if (resarr->itemsarr) - pfree(resarr->itemsarr); + ResourceElem *items; + uint32 nitems; + + if (owner->nhash == 0) + { + items = owner->arr; + nitems = owner->narr; + } + else + { + /* + * Compact the hash table, so that all the elements are in the + * beginning of the 'hash' array, with no empty elements. + */ + uint32 dst = 0; + + for (int idx = 0; idx < owner->capacity; idx++) + { + if (owner->hash[idx].kind != NULL) + { + if (dst != idx) + owner->hash[dst] = owner->hash[idx]; + dst++; + } + } + + /* + * Move all entries from the fixed-size array to 'hash'. + * + * RESOWNER_HASH_MAX_ITEMS is defined so that there is always enough + * free space to move all the elements from the fixed-size array to + * the hash. + */ + Assert(dst + owner->narr <= owner->capacity); + for (int idx = 0; idx < owner->narr; idx++) + { + owner->hash[dst] = owner->arr[idx]; + dst++; + } + Assert(dst == owner->nhash + owner->narr); + owner->narr = 0; + owner->nhash = dst; + + items = owner->hash; + nitems = owner->nhash; + } + + qsort(items, nitems, sizeof(ResourceElem), resource_priority_cmp); +} + +/* + * Call the ReleaseResource callback on entries with given 'phase'. + */ +static void +ResourceOwnerReleaseAll(ResourceOwner owner, ResourceReleasePhase phase, + bool printLeakWarnings) +{ + ResourceElem *items; + uint32 nitems; + + /* ResourceOwnerSort must've been called already */ + Assert(owner->releasing); + Assert(owner->sorted); + if (!owner->hash) + { + items = owner->arr; + nitems = owner->narr; + } + else + { + Assert(owner->narr == 0); + items = owner->hash; + nitems = owner->nhash; + } + + /* + * The resources are sorted in reverse priority order. Release them + * starting from the end, until we hit the end of the phase that we are + * releasing now. We will continue from there when called again for the + * next phase. + */ + while (nitems > 0) + { + uint32 idx = nitems - 1; + Datum value = items[idx].item; + const ResourceOwnerDesc *kind = items[idx].kind; + + if (kind->release_phase > phase) + break; + Assert(kind->release_phase == phase); + + if (printLeakWarnings) + { + char *res_str; + + res_str = kind->DebugPrint ? + kind->DebugPrint(value) + : psprintf("%s %p", kind->name, DatumGetPointer(value)); + elog(WARNING, "resource was not closed: %s", res_str); + pfree(res_str); + } + kind->ReleaseResource(value); + nitems--; + } + if (!owner->hash) + owner->narr = nitems; + else + owner->nhash = nitems; } @@ -441,23 +416,188 @@ ResourceOwnerCreate(ResourceOwner parent, const char *name) parent->firstchild = owner; } - ResourceArrayInit(&(owner->bufferarr), BufferGetDatum(InvalidBuffer)); - ResourceArrayInit(&(owner->bufferioarr), BufferGetDatum(InvalidBuffer)); - ResourceArrayInit(&(owner->catrefarr), PointerGetDatum(NULL)); - ResourceArrayInit(&(owner->catlistrefarr), PointerGetDatum(NULL)); - ResourceArrayInit(&(owner->relrefarr), PointerGetDatum(NULL)); - ResourceArrayInit(&(owner->planrefarr), PointerGetDatum(NULL)); - ResourceArrayInit(&(owner->tupdescarr), PointerGetDatum(NULL)); - ResourceArrayInit(&(owner->snapshotarr), PointerGetDatum(NULL)); - ResourceArrayInit(&(owner->filearr), FileGetDatum(-1)); - ResourceArrayInit(&(owner->dsmarr), PointerGetDatum(NULL)); - ResourceArrayInit(&(owner->jitarr), PointerGetDatum(NULL)); - ResourceArrayInit(&(owner->cryptohasharr), PointerGetDatum(NULL)); - ResourceArrayInit(&(owner->hmacarr), PointerGetDatum(NULL)); - return owner; } +/* + * Make sure there is room for at least one more resource in an array. + * + * This is separate from actually inserting a resource because if we run out + * of memory, it's critical to do so *before* acquiring the resource. + * + * NB: Make sure there are no unrelated ResourceOwnerRemember() calls between + * your ResourceOwnerEnlarge() call and the ResourceOwnerRemember() call that + * you reserved the space for! + */ +void +ResourceOwnerEnlarge(ResourceOwner owner) +{ + /* + * Mustn't try to remember more resources after we have already started + * releasing + */ + if (owner->releasing) + elog(ERROR, "ResourceOwnerEnlarge called after release started"); + + if (owner->narr < RESOWNER_ARRAY_SIZE) + return; /* no work needed */ + + /* + * Is there space in the hash? If not, enlarge it. + */ + if (owner->narr + owner->nhash >= owner->grow_at) + { + uint32 i, + oldcap, + newcap; + ResourceElem *oldhash; + ResourceElem *newhash; + + oldhash = owner->hash; + oldcap = owner->capacity; + + /* Double the capacity (it must stay a power of 2!) */ + newcap = (oldcap > 0) ? oldcap * 2 : RESOWNER_HASH_INIT_SIZE; + newhash = (ResourceElem *) MemoryContextAllocZero(TopMemoryContext, + newcap * sizeof(ResourceElem)); + + /* + * We assume we can't fail below this point, so OK to scribble on the + * owner + */ + owner->hash = newhash; + owner->capacity = newcap; + owner->grow_at = RESOWNER_HASH_MAX_ITEMS(newcap); + owner->nhash = 0; + + if (oldhash != NULL) + { + /* + * Transfer any pre-existing entries into the new hash table; they + * don't necessarily go where they were before, so this simple + * logic is the best way. + */ + for (i = 0; i < oldcap; i++) + { + if (oldhash[i].kind != NULL) + ResourceOwnerAddToHash(owner, oldhash[i].item, oldhash[i].kind); + } + + /* And release old hash table. */ + pfree(oldhash); + } + } + + /* Move items from the array to the hash */ + for (int i = 0; i < owner->narr; i++) + ResourceOwnerAddToHash(owner, owner->arr[i].item, owner->arr[i].kind); + owner->narr = 0; + + Assert(owner->nhash <= owner->grow_at); +} + +/* + * Remember that an object is owned by a ResourceOwner + * + * Caller must have previously done ResourceOwnerEnlarge() + */ +void +ResourceOwnerRemember(ResourceOwner owner, Datum value, const ResourceOwnerDesc *kind) +{ + uint32 idx; + + /* sanity check the ResourceOwnerDesc */ + Assert(kind->release_phase != 0); + Assert(kind->release_priority != 0); + + /* + * Mustn't try to remember more resources after we have already started + * releasing. We already checked this in ResourceOwnerEnlarge. + */ + Assert(!owner->releasing); + Assert(!owner->sorted); + + if (owner->narr >= RESOWNER_ARRAY_SIZE) + { + /* forgot to call ResourceOwnerEnlarge? */ + elog(ERROR, "ResourceOwnerRemember called but array was full"); + } + + /* Append to the array. */ + idx = owner->narr; + owner->arr[idx].item = value; + owner->arr[idx].kind = kind; + owner->narr++; +} + +/* + * Forget that an object is owned by a ResourceOwner + * + * Note: if same resource ID is associated with the ResourceOwner more than + * once, one instance is removed. + */ +void +ResourceOwnerForget(ResourceOwner owner, Datum value, const ResourceOwnerDesc *kind) +{ + /* + * Mustn't call this after we have already started releasing resources. + * (Release callback functions are not allowed to release additional + * resources.) + */ + if (owner->releasing) + elog(ERROR, "ResourceOwnerForget called for %s after release started", kind->name); + Assert(!owner->sorted); + + /* Search through all items in the array first. */ + for (int i = owner->narr - 1; i >= 0; i--) + { + if (owner->arr[i].item == value && + owner->arr[i].kind == kind) + { + owner->arr[i] = owner->arr[owner->narr - 1]; + owner->narr--; + +#ifdef RESOWNER_STATS + narray_lookups++; +#endif + return; + } + } + + /* Search hash */ + if (owner->nhash > 0) + { + uint32 mask = owner->capacity - 1; + uint32 idx; + + idx = hash_resource_elem(value, kind) & mask; + for (uint32 i = 0; i < owner->capacity; i++) + { + if (owner->hash[idx].item == value && + owner->hash[idx].kind == kind) + { + owner->hash[idx].item = (Datum) 0; + owner->hash[idx].kind = NULL; + owner->nhash--; + +#ifdef RESOWNER_STATS + nhash_lookups++; +#endif + return; + } + idx = (idx + 1) & mask; + } + } + + /* + * Use %p to print the reference, since most objects tracked by a resource + * owner are pointers. It's a bit misleading if it's not a pointer, but + * this is a programmer error, anyway. + */ + elog(ERROR, "%s %p is not owned by resource owner %s", + kind->name, DatumGetPointer(value), owner->name); +} + /* * ResourceOwnerRelease * Release all resources owned by a ResourceOwner and its descendants, @@ -483,6 +623,12 @@ ResourceOwnerCreate(ResourceOwner parent, const char *name) * 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. + * + * NOTE: After starting the release process, by calling this function, no new + * resources can be remembered in the resource owner. You also cannot call + * ResourceOwnerForget on any previously remembered resources to release + * resources "in retail" after that, you must let the bulk release take care + * of them. */ void ResourceOwnerRelease(ResourceOwner owner, @@ -492,6 +638,16 @@ ResourceOwnerRelease(ResourceOwner owner, { /* There's not currently any setup needed before recursing */ ResourceOwnerReleaseInternal(owner, phase, isCommit, isTopLevel); + +#ifdef RESOWNER_STATS + if (isTopLevel) + { + elog(LOG, "RESOWNER STATS: lookups: array %d, hash %d", + narray_lookups, nhash_lookups); + narray_lookups = 0; + nhash_lookups = 0; + } +#endif } static void @@ -504,15 +660,43 @@ ResourceOwnerReleaseInternal(ResourceOwner owner, ResourceOwner save; ResourceReleaseCallbackItem *item; ResourceReleaseCallbackItem *next; - Datum foundres; /* 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. + * To release the resources in the right order, sort them by phase and + * priority. + * + * The ReleaseResource callback functions are not allowed to remember or + * forget any other resources after this. Otherwise we lose track of where + * we are in processing the hash/array. + */ + if (!owner->releasing) + { + Assert(phase == RESOURCE_RELEASE_BEFORE_LOCKS); + Assert(!owner->sorted); + owner->releasing = true; + } + else + { + /* + * Phase is normally > RESOURCE_RELEASE_BEFORE_LOCKS, if this is not + * the first call to ResourceOwnerRelease. But if an error happens + * between the release phases, we might get called again for the same + * ResourceOwner from AbortTransaction. + */ + } + if (!owner->sorted) + { + ResourceOwnerSort(owner); + owner->sorted = true; + } + + /* + * Make CurrentResourceOwner point to me, so that the release callback + * functions know which resource owner is been released. */ save = CurrentResourceOwner; CurrentResourceOwner = owner; @@ -520,89 +704,13 @@ ResourceOwnerReleaseInternal(ResourceOwner owner, if (phase == RESOURCE_RELEASE_BEFORE_LOCKS) { /* - * Abort failed buffer IO. AbortBufferIO()->TerminateBufferIO() calls - * ResourceOwnerForgetBufferIO(), so we just have to iterate till - * there are none. + * Release all resources that need to be released before the locks. * - * Needs to be before we release buffer pins. - * - * During a commit, there shouldn't be any in-progress IO. + * During a commit, there shouldn't be any remaining resources --- + * that would indicate failure to clean up the executor correctly --- + * so issue warnings. In the abort case, just clean up quietly. */ - while (ResourceArrayGetAny(&(owner->bufferioarr), &foundres)) - { - Buffer res = DatumGetBuffer(foundres); - - if (isCommit) - elog(PANIC, "lost track of buffer IO on buffer %d", res); - AbortBufferIO(res); - } - - /* - * Release buffer pins. Note that ReleaseBuffer will remove the - * buffer entry from our array, so we 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. - */ - while (ResourceArrayGetAny(&(owner->bufferarr), &foundres)) - { - Buffer res = DatumGetBuffer(foundres); - - if (isCommit) - PrintBufferLeakWarning(res); - ReleaseBuffer(res); - } - - /* Ditto for relcache references */ - while (ResourceArrayGetAny(&(owner->relrefarr), &foundres)) - { - Relation res = (Relation) DatumGetPointer(foundres); - - if (isCommit) - PrintRelCacheLeakWarning(res); - RelationClose(res); - } - - /* Ditto for dynamic shared memory segments */ - while (ResourceArrayGetAny(&(owner->dsmarr), &foundres)) - { - dsm_segment *res = (dsm_segment *) DatumGetPointer(foundres); - - if (isCommit) - PrintDSMLeakWarning(res); - dsm_detach(res); - } - - /* Ditto for JIT contexts */ - while (ResourceArrayGetAny(&(owner->jitarr), &foundres)) - { - JitContext *context = (JitContext *) DatumGetPointer(foundres); - - jit_release_context(context); - } - - /* Ditto for cryptohash contexts */ - while (ResourceArrayGetAny(&(owner->cryptohasharr), &foundres)) - { - pg_cryptohash_ctx *context = - (pg_cryptohash_ctx *) DatumGetPointer(foundres); - - if (isCommit) - PrintCryptoHashLeakWarning(foundres); - pg_cryptohash_free(context); - } - - /* Ditto for HMAC contexts */ - while (ResourceArrayGetAny(&(owner->hmacarr), &foundres)) - { - pg_hmac_ctx *context = (pg_hmac_ctx *) DatumGetPointer(foundres); - - if (isCommit) - PrintHMACLeakWarning(foundres); - pg_hmac_free(context); - } + ResourceOwnerReleaseAll(owner, phase, isCommit); } else if (phase == RESOURCE_RELEASE_LOCKS) { @@ -655,70 +763,9 @@ ResourceOwnerReleaseInternal(ResourceOwner owner, else if (phase == RESOURCE_RELEASE_AFTER_LOCKS) { /* - * Release catcache references. Note that ReleaseCatCache will remove - * the catref entry from our array, so we just have to iterate till - * there are none. - * - * As with buffer pins, warn if any are left at commit time. + * Release all resources that need to be released after the locks. */ - while (ResourceArrayGetAny(&(owner->catrefarr), &foundres)) - { - HeapTuple res = (HeapTuple) DatumGetPointer(foundres); - - if (isCommit) - PrintCatCacheLeakWarning(res); - ReleaseCatCache(res); - } - - /* Ditto for catcache lists */ - while (ResourceArrayGetAny(&(owner->catlistrefarr), &foundres)) - { - CatCList *res = (CatCList *) DatumGetPointer(foundres); - - if (isCommit) - PrintCatCacheListLeakWarning(res); - ReleaseCatCacheList(res); - } - - /* Ditto for plancache references */ - while (ResourceArrayGetAny(&(owner->planrefarr), &foundres)) - { - CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres); - - if (isCommit) - PrintPlanCacheLeakWarning(res); - ReleaseCachedPlan(res, owner); - } - - /* Ditto for tupdesc references */ - while (ResourceArrayGetAny(&(owner->tupdescarr), &foundres)) - { - TupleDesc res = (TupleDesc) DatumGetPointer(foundres); - - if (isCommit) - PrintTupleDescLeakWarning(res); - DecrTupleDescRefCount(res); - } - - /* Ditto for snapshot references */ - 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); - } + ResourceOwnerReleaseAll(owner, phase, isCommit); } /* Let add-on modules get a chance too */ @@ -733,23 +780,54 @@ ResourceOwnerReleaseInternal(ResourceOwner owner, } /* - * ResourceOwnerReleaseAllPlanCacheRefs - * Release the plancache references (only) held by this owner. - * - * We might eventually add similar functions for other resource types, - * but for now, only this is needed. + * ResourceOwnerReleaseAllOfKind + * Release all resources of a certain type held by this owner. */ void -ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner) +ResourceOwnerReleaseAllOfKind(ResourceOwner owner, const ResourceOwnerDesc *kind) { - Datum foundres; + /* Mustn't call this after we have already started releasing resources. */ + if (owner->releasing) + elog(ERROR, "ResourceOwnerForget called for %s after release started", kind->name); + Assert(!owner->sorted); - while (ResourceArrayGetAny(&(owner->planrefarr), &foundres)) + /* + * Temporarily set 'releasing', to prevent calls to ResourceOwnerRemember + * while we're scanning the owner. Enlarging the hash would cause us to + * lose track of the point we're scanning. + */ + owner->releasing = true; + + /* Array first */ + for (int i = 0; i < owner->narr; i++) { - CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres); + if (owner->arr[i].kind == kind) + { + Datum value = owner->arr[i].item; - ReleaseCachedPlan(res, owner); + owner->arr[i] = owner->arr[owner->narr - 1]; + owner->narr--; + i--; + + kind->ReleaseResource(value); + } } + + /* Then hash */ + for (int i = 0; i < owner->capacity; i++) + { + if (owner->hash[i].kind == kind) + { + Datum value = owner->hash[i].item; + + owner->hash[i].item = (Datum) 0; + owner->hash[i].kind = NULL; + owner->nhash--; + + kind->ReleaseResource(value); + } + } + owner->releasing = false; } /* @@ -765,19 +843,8 @@ ResourceOwnerDelete(ResourceOwner owner) Assert(owner != CurrentResourceOwner); /* And it better not own any resources, either */ - Assert(owner->bufferarr.nitems == 0); - Assert(owner->bufferioarr.nitems == 0); - Assert(owner->catrefarr.nitems == 0); - Assert(owner->catlistrefarr.nitems == 0); - Assert(owner->relrefarr.nitems == 0); - Assert(owner->planrefarr.nitems == 0); - Assert(owner->tupdescarr.nitems == 0); - Assert(owner->snapshotarr.nitems == 0); - Assert(owner->filearr.nitems == 0); - Assert(owner->dsmarr.nitems == 0); - Assert(owner->jitarr.nitems == 0); - Assert(owner->cryptohasharr.nitems == 0); - Assert(owner->hmacarr.nitems == 0); + Assert(owner->narr == 0); + Assert(owner->nhash == 0); Assert(owner->nlocks == 0 || owner->nlocks == MAX_RESOWNER_LOCKS + 1); /* @@ -795,20 +862,8 @@ ResourceOwnerDelete(ResourceOwner owner) ResourceOwnerNewParent(owner, NULL); /* And free the object. */ - ResourceArrayFree(&(owner->bufferarr)); - ResourceArrayFree(&(owner->bufferioarr)); - ResourceArrayFree(&(owner->catrefarr)); - ResourceArrayFree(&(owner->catlistrefarr)); - ResourceArrayFree(&(owner->relrefarr)); - ResourceArrayFree(&(owner->planrefarr)); - ResourceArrayFree(&(owner->tupdescarr)); - ResourceArrayFree(&(owner->snapshotarr)); - ResourceArrayFree(&(owner->filearr)); - ResourceArrayFree(&(owner->dsmarr)); - ResourceArrayFree(&(owner->jitarr)); - ResourceArrayFree(&(owner->cryptohasharr)); - ResourceArrayFree(&(owner->hmacarr)); - + if (owner->hash) + pfree(owner->hash); pfree(owner); } @@ -866,11 +921,10 @@ ResourceOwnerNewParent(ResourceOwner owner, /* * 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. + * These functions can be used by dynamically loaded modules. These used + * to be the only way for an extension to register custom resource types + * with a resource owner, but nowadays it is easier to define a new + * ResourceOwnerDesc with custom callbacks. */ void RegisterResourceReleaseCallback(ResourceReleaseCallback callback, void *arg) @@ -946,6 +1000,9 @@ ReleaseAuxProcessResources(bool isCommit) ResourceOwnerRelease(AuxProcessResourceOwner, RESOURCE_RELEASE_AFTER_LOCKS, isCommit, true); + /* allow it to be reused */ + AuxProcessResourceOwner->releasing = false; + AuxProcessResourceOwner->sorted = false; } /* @@ -960,88 +1017,12 @@ ReleaseAuxProcessResourcesCallback(int code, Datum arg) ReleaseAuxProcessResources(isCommit); } - -/* - * 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. - */ -void -ResourceOwnerEnlargeBuffers(ResourceOwner owner) -{ - /* We used to allow pinning buffers without a resowner, but no more */ - Assert(owner != NULL); - ResourceArrayEnlarge(&(owner->bufferarr)); -} - -/* - * Remember that a buffer pin is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeBuffers() - */ -void -ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer) -{ - ResourceArrayAdd(&(owner->bufferarr), BufferGetDatum(buffer)); -} - -/* - * Forget that a buffer pin is owned by a ResourceOwner - */ -void -ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer) -{ - if (!ResourceArrayRemove(&(owner->bufferarr), BufferGetDatum(buffer))) - 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 - * 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. - */ -void -ResourceOwnerEnlargeBufferIOs(ResourceOwner owner) -{ - /* We used to allow pinning buffers without a resowner, but no more */ - Assert(owner != NULL); - ResourceArrayEnlarge(&(owner->bufferioarr)); -} - -/* - * Remember that a buffer IO is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeBufferIOs() - */ -void -ResourceOwnerRememberBufferIO(ResourceOwner owner, Buffer buffer) -{ - ResourceArrayAdd(&(owner->bufferioarr), BufferGetDatum(buffer)); -} - -/* - * Forget that a buffer IO is owned by a ResourceOwner - */ -void -ResourceOwnerForgetBufferIO(ResourceOwner owner, Buffer buffer) -{ - if (!ResourceArrayRemove(&(owner->bufferioarr), BufferGetDatum(buffer))) - elog(PANIC, "buffer IO %d is not owned by resource owner %s", - buffer, owner->name); -} - /* * Remember that a Local Lock is owned by a ResourceOwner * - * This is different from the other Remember functions in that the list of - * locks is only a lossy cache. It can hold up to MAX_RESOWNER_LOCKS entries, - * and when it overflows, we stop tracking locks. The point of only remembering + * This is different from the generic ResourceOwnerRemember in that the list of + * locks is only a lossy cache. It can hold up to MAX_RESOWNER_LOCKS entries, + * and when it overflows, we stop tracking locks. The point of only remembering * only up to MAX_RESOWNER_LOCKS entries is that if a lot of locks are held, * ResourceOwnerForgetLock doesn't need to scan through a large array to find * the entry. @@ -1087,469 +1068,3 @@ ResourceOwnerForgetLock(ResourceOwner owner, LOCALLOCK *locallock) elog(ERROR, "lock reference %p is not owned by resource owner %s", locallock, 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) -{ - ResourceArrayEnlarge(&(owner->catrefarr)); -} - -/* - * Remember that a catcache reference is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeCatCacheRefs() - */ -void -ResourceOwnerRememberCatCacheRef(ResourceOwner owner, HeapTuple tuple) -{ - ResourceArrayAdd(&(owner->catrefarr), PointerGetDatum(tuple)); -} - -/* - * Forget that a catcache reference is owned by a ResourceOwner - */ -void -ResourceOwnerForgetCatCacheRef(ResourceOwner owner, HeapTuple tuple) -{ - if (!ResourceArrayRemove(&(owner->catrefarr), PointerGetDatum(tuple))) - 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) -{ - ResourceArrayEnlarge(&(owner->catlistrefarr)); -} - -/* - * Remember that a catcache-list reference is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeCatCacheListRefs() - */ -void -ResourceOwnerRememberCatCacheListRef(ResourceOwner owner, CatCList *list) -{ - ResourceArrayAdd(&(owner->catlistrefarr), PointerGetDatum(list)); -} - -/* - * Forget that a catcache-list reference is owned by a ResourceOwner - */ -void -ResourceOwnerForgetCatCacheListRef(ResourceOwner owner, CatCList *list) -{ - if (!ResourceArrayRemove(&(owner->catlistrefarr), PointerGetDatum(list))) - 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) -{ - ResourceArrayEnlarge(&(owner->relrefarr)); -} - -/* - * Remember that a relcache reference is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeRelationRefs() - */ -void -ResourceOwnerRememberRelationRef(ResourceOwner owner, Relation rel) -{ - ResourceArrayAdd(&(owner->relrefarr), PointerGetDatum(rel)); -} - -/* - * Forget that a relcache reference is owned by a ResourceOwner - */ -void -ResourceOwnerForgetRelationRef(ResourceOwner owner, Relation rel) -{ - if (!ResourceArrayRemove(&(owner->relrefarr), PointerGetDatum(rel))) - 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 - * plancache 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 -ResourceOwnerEnlargePlanCacheRefs(ResourceOwner owner) -{ - ResourceArrayEnlarge(&(owner->planrefarr)); -} - -/* - * Remember that a plancache reference is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargePlanCacheRefs() - */ -void -ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, CachedPlan *plan) -{ - ResourceArrayAdd(&(owner->planrefarr), PointerGetDatum(plan)); -} - -/* - * Forget that a plancache reference is owned by a ResourceOwner - */ -void -ResourceOwnerForgetPlanCacheRef(ResourceOwner owner, CachedPlan *plan) -{ - if (!ResourceArrayRemove(&(owner->planrefarr), PointerGetDatum(plan))) - elog(ERROR, "plancache reference %p is not owned by resource owner %s", - plan, owner->name); -} - -/* - * Debugging subroutine - */ -static void -PrintPlanCacheLeakWarning(CachedPlan *plan) -{ - elog(WARNING, "plancache reference leak: plan %p not closed", plan); -} - -/* - * 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) -{ - ResourceArrayEnlarge(&(owner->tupdescarr)); -} - -/* - * Remember that a tupdesc reference is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeTupleDescs() - */ -void -ResourceOwnerRememberTupleDesc(ResourceOwner owner, TupleDesc tupdesc) -{ - ResourceArrayAdd(&(owner->tupdescarr), PointerGetDatum(tupdesc)); -} - -/* - * Forget that a tupdesc reference is owned by a ResourceOwner - */ -void -ResourceOwnerForgetTupleDesc(ResourceOwner owner, TupleDesc tupdesc) -{ - if (!ResourceArrayRemove(&(owner->tupdescarr), PointerGetDatum(tupdesc))) - 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); -} - -/* - * Make sure there is room for at least one more entry in a ResourceOwner's - * snapshot 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 -ResourceOwnerEnlargeSnapshots(ResourceOwner owner) -{ - ResourceArrayEnlarge(&(owner->snapshotarr)); -} - -/* - * Remember that a snapshot reference is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeSnapshots() - */ -void -ResourceOwnerRememberSnapshot(ResourceOwner owner, Snapshot snapshot) -{ - ResourceArrayAdd(&(owner->snapshotarr), PointerGetDatum(snapshot)); -} - -/* - * Forget that a snapshot reference is owned by a ResourceOwner - */ -void -ResourceOwnerForgetSnapshot(ResourceOwner owner, Snapshot snapshot) -{ - if (!ResourceArrayRemove(&(owner->snapshotarr), PointerGetDatum(snapshot))) - elog(ERROR, "snapshot reference %p is not owned by resource owner %s", - snapshot, owner->name); -} - -/* - * Debugging subroutine - */ -static void -PrintSnapshotLeakWarning(Snapshot snapshot) -{ - elog(WARNING, "Snapshot reference leak: Snapshot %p still referenced", - snapshot); -} - - -/* - * Make sure there is room for at least one more entry in a ResourceOwner's - * files 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 -ResourceOwnerEnlargeFiles(ResourceOwner owner) -{ - ResourceArrayEnlarge(&(owner->filearr)); -} - -/* - * Remember that a temporary file is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeFiles() - */ -void -ResourceOwnerRememberFile(ResourceOwner owner, File file) -{ - ResourceArrayAdd(&(owner->filearr), FileGetDatum(file)); -} - -/* - * Forget that a temporary file is owned by a ResourceOwner - */ -void -ResourceOwnerForgetFile(ResourceOwner owner, File file) -{ - if (!ResourceArrayRemove(&(owner->filearr), FileGetDatum(file))) - elog(ERROR, "temporary file %d is not owned by resource owner %s", - file, owner->name); -} - -/* - * Debugging subroutine - */ -static void -PrintFileLeakWarning(File file) -{ - elog(WARNING, "temporary file leak: File %d still referenced", - file); -} - -/* - * Make sure there is room for at least one more entry in a ResourceOwner's - * dynamic shmem segment 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 -ResourceOwnerEnlargeDSMs(ResourceOwner owner) -{ - ResourceArrayEnlarge(&(owner->dsmarr)); -} - -/* - * Remember that a dynamic shmem segment is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeDSMs() - */ -void -ResourceOwnerRememberDSM(ResourceOwner owner, dsm_segment *seg) -{ - ResourceArrayAdd(&(owner->dsmarr), PointerGetDatum(seg)); -} - -/* - * Forget that a dynamic shmem segment is owned by a ResourceOwner - */ -void -ResourceOwnerForgetDSM(ResourceOwner owner, dsm_segment *seg) -{ - if (!ResourceArrayRemove(&(owner->dsmarr), PointerGetDatum(seg))) - elog(ERROR, "dynamic shared memory segment %u is not owned by resource owner %s", - dsm_segment_handle(seg), owner->name); -} - -/* - * Debugging subroutine - */ -static void -PrintDSMLeakWarning(dsm_segment *seg) -{ - elog(WARNING, "dynamic shared memory leak: segment %u still referenced", - dsm_segment_handle(seg)); -} - -/* - * Make sure there is room for at least one more entry in a ResourceOwner's - * JIT context 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 -ResourceOwnerEnlargeJIT(ResourceOwner owner) -{ - ResourceArrayEnlarge(&(owner->jitarr)); -} - -/* - * Remember that a JIT context is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeJIT() - */ -void -ResourceOwnerRememberJIT(ResourceOwner owner, Datum handle) -{ - ResourceArrayAdd(&(owner->jitarr), handle); -} - -/* - * Forget that a JIT context is owned by a ResourceOwner - */ -void -ResourceOwnerForgetJIT(ResourceOwner owner, Datum handle) -{ - if (!ResourceArrayRemove(&(owner->jitarr), handle)) - elog(ERROR, "JIT context %p is not owned by resource owner %s", - DatumGetPointer(handle), owner->name); -} - -/* - * Make sure there is room for at least one more entry in a ResourceOwner's - * cryptohash context 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 -ResourceOwnerEnlargeCryptoHash(ResourceOwner owner) -{ - ResourceArrayEnlarge(&(owner->cryptohasharr)); -} - -/* - * Remember that a cryptohash context is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeCryptoHash() - */ -void -ResourceOwnerRememberCryptoHash(ResourceOwner owner, Datum handle) -{ - ResourceArrayAdd(&(owner->cryptohasharr), handle); -} - -/* - * Forget that a cryptohash context is owned by a ResourceOwner - */ -void -ResourceOwnerForgetCryptoHash(ResourceOwner owner, Datum handle) -{ - if (!ResourceArrayRemove(&(owner->cryptohasharr), handle)) - elog(ERROR, "cryptohash context %p is not owned by resource owner %s", - DatumGetPointer(handle), owner->name); -} - -/* - * Debugging subroutine - */ -static void -PrintCryptoHashLeakWarning(Datum handle) -{ - elog(WARNING, "cryptohash context reference leak: context %p still referenced", - DatumGetPointer(handle)); -} - -/* - * Make sure there is room for at least one more entry in a ResourceOwner's - * hmac context 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 -ResourceOwnerEnlargeHMAC(ResourceOwner owner) -{ - ResourceArrayEnlarge(&(owner->hmacarr)); -} - -/* - * Remember that a HMAC context is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeHMAC() - */ -void -ResourceOwnerRememberHMAC(ResourceOwner owner, Datum handle) -{ - ResourceArrayAdd(&(owner->hmacarr), handle); -} - -/* - * Forget that a HMAC context is owned by a ResourceOwner - */ -void -ResourceOwnerForgetHMAC(ResourceOwner owner, Datum handle) -{ - if (!ResourceArrayRemove(&(owner->hmacarr), handle)) - elog(ERROR, "HMAC context %p is not owned by resource owner %s", - DatumGetPointer(handle), owner->name); -} - -/* - * Debugging subroutine - */ -static void -PrintHMACLeakWarning(Datum handle) -{ - elog(WARNING, "HMAC context reference leak: context %p still referenced", - DatumGetPointer(handle)); -} diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c index 4a3613d15f..9198850ad2 100644 --- a/src/backend/utils/time/snapmgr.c +++ b/src/backend/utils/time/snapmgr.c @@ -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)); +} diff --git a/src/common/cryptohash_openssl.c b/src/common/cryptohash_openssl.c index d9ca5a1409..241582c48d 100644 --- a/src/common/cryptohash_openssl.c +++ b/src/common/cryptohash_openssl.c @@ -31,7 +31,6 @@ #ifndef FRONTEND #include "utils/memutils.h" #include "utils/resowner.h" -#include "utils/resowner_private.h" #endif /* @@ -74,6 +73,32 @@ struct pg_cryptohash_ctx #endif }; +/* ResourceOwner callbacks to hold cryptohash contexts */ +#ifndef FRONTEND +static void ResOwnerReleaseCryptoHash(Datum res); + +static const ResourceOwnerDesc cryptohash_resowner_desc = +{ + .name = "OpenSSL cryptohash context", + .release_phase = RESOURCE_RELEASE_BEFORE_LOCKS, + .release_priority = RELEASE_PRIO_CRYPTOHASH_CONTEXTS, + .ReleaseResource = ResOwnerReleaseCryptoHash, + .DebugPrint = NULL /* the default message is fine */ +}; + +/* Convenience wrappers over ResourceOwnerRemember/Forget */ +static inline void +ResourceOwnerRememberCryptoHash(ResourceOwner owner, pg_cryptohash_ctx *ctx) +{ + ResourceOwnerRemember(owner, PointerGetDatum(ctx), &cryptohash_resowner_desc); +} +static inline void +ResourceOwnerForgetCryptoHash(ResourceOwner owner, pg_cryptohash_ctx *ctx) +{ + ResourceOwnerForget(owner, PointerGetDatum(ctx), &cryptohash_resowner_desc); +} +#endif + static const char * SSLerrmessage(unsigned long ecode) { @@ -104,7 +129,7 @@ pg_cryptohash_create(pg_cryptohash_type type) * allocation to avoid leaking. */ #ifndef FRONTEND - ResourceOwnerEnlargeCryptoHash(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); #endif ctx = ALLOC(sizeof(pg_cryptohash_ctx)); @@ -138,8 +163,7 @@ pg_cryptohash_create(pg_cryptohash_type type) #ifndef FRONTEND ctx->resowner = CurrentResourceOwner; - ResourceOwnerRememberCryptoHash(CurrentResourceOwner, - PointerGetDatum(ctx)); + ResourceOwnerRememberCryptoHash(CurrentResourceOwner, ctx); #endif return ctx; @@ -307,8 +331,8 @@ pg_cryptohash_free(pg_cryptohash_ctx *ctx) EVP_MD_CTX_destroy(ctx->evpctx); #ifndef FRONTEND - ResourceOwnerForgetCryptoHash(ctx->resowner, - PointerGetDatum(ctx)); + if (ctx->resowner) + ResourceOwnerForgetCryptoHash(ctx->resowner, ctx); #endif explicit_bzero(ctx, sizeof(pg_cryptohash_ctx)); @@ -351,3 +375,16 @@ pg_cryptohash_error(pg_cryptohash_ctx *ctx) Assert(false); /* cannot be reached */ return _("success"); } + +/* ResourceOwner callbacks */ + +#ifndef FRONTEND +static void +ResOwnerReleaseCryptoHash(Datum res) +{ + pg_cryptohash_ctx *ctx = (pg_cryptohash_ctx *) DatumGetPointer(res); + + ctx->resowner = NULL; + pg_cryptohash_free(ctx); +} +#endif diff --git a/src/common/hmac_openssl.c b/src/common/hmac_openssl.c index 9164f4fdfe..b12e49ba66 100644 --- a/src/common/hmac_openssl.c +++ b/src/common/hmac_openssl.c @@ -31,7 +31,6 @@ #ifndef FRONTEND #include "utils/memutils.h" #include "utils/resowner.h" -#include "utils/resowner_private.h" #endif /* @@ -73,6 +72,32 @@ struct pg_hmac_ctx #endif }; +/* ResourceOwner callbacks to hold HMAC contexts */ +#ifndef FRONTEND +static void ResOwnerReleaseHMAC(Datum res); + +static const ResourceOwnerDesc hmac_resowner_desc = +{ + .name = "OpenSSL HMAC context", + .release_phase = RESOURCE_RELEASE_BEFORE_LOCKS, + .release_priority = RELEASE_PRIO_HMAC_CONTEXTS, + .ReleaseResource = ResOwnerReleaseHMAC, + .DebugPrint = NULL /* the default message is fine */ +}; + +/* Convenience wrappers over ResourceOwnerRemember/Forget */ +static inline void +ResourceOwnerRememberHMAC(ResourceOwner owner, pg_hmac_ctx *ctx) +{ + ResourceOwnerRemember(owner, PointerGetDatum(ctx), &hmac_resowner_desc); +} +static inline void +ResourceOwnerForgetHMAC(ResourceOwner owner, pg_hmac_ctx *ctx) +{ + ResourceOwnerForget(owner, PointerGetDatum(ctx), &hmac_resowner_desc); +} +#endif + static const char * SSLerrmessage(unsigned long ecode) { @@ -115,7 +140,7 @@ pg_hmac_create(pg_cryptohash_type type) ERR_clear_error(); #ifdef HAVE_HMAC_CTX_NEW #ifndef FRONTEND - ResourceOwnerEnlargeHMAC(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); #endif ctx->hmacctx = HMAC_CTX_new(); #else @@ -137,7 +162,7 @@ pg_hmac_create(pg_cryptohash_type type) #ifdef HAVE_HMAC_CTX_NEW #ifndef FRONTEND ctx->resowner = CurrentResourceOwner; - ResourceOwnerRememberHMAC(CurrentResourceOwner, PointerGetDatum(ctx)); + ResourceOwnerRememberHMAC(CurrentResourceOwner, ctx); #endif #else memset(ctx->hmacctx, 0, sizeof(HMAC_CTX)); @@ -303,7 +328,8 @@ pg_hmac_free(pg_hmac_ctx *ctx) #ifdef HAVE_HMAC_CTX_FREE HMAC_CTX_free(ctx->hmacctx); #ifndef FRONTEND - ResourceOwnerForgetHMAC(ctx->resowner, PointerGetDatum(ctx)); + if (ctx->resowner) + ResourceOwnerForgetHMAC(ctx->resowner, ctx); #endif #else explicit_bzero(ctx->hmacctx, sizeof(HMAC_CTX)); @@ -346,3 +372,16 @@ pg_hmac_error(pg_hmac_ctx *ctx) Assert(false); /* cannot be reached */ return _("success"); } + +/* ResourceOwner callbacks */ + +#ifndef FRONTEND +static void +ResOwnerReleaseHMAC(Datum res) +{ + pg_hmac_ctx *ctx = (pg_hmac_ctx *) DatumGetPointer(res); + + ctx->resowner = NULL; + pg_hmac_free(ctx); +} +#endif diff --git a/src/include/storage/buf_internals.h b/src/include/storage/buf_internals.h index bc79a329a1..e15a86eff9 100644 --- a/src/include/storage/buf_internals.h +++ b/src/include/storage/buf_internals.h @@ -26,6 +26,7 @@ #include "storage/smgr.h" #include "storage/spin.h" #include "utils/relcache.h" +#include "utils/resowner.h" /* * Buffer state is a single 32-bit variable where following data is combined. @@ -383,6 +384,32 @@ typedef struct CkptSortItem extern PGDLLIMPORT CkptSortItem *CkptBufferIds; +/* ResourceOwner callbacks to hold buffer I/Os and pins */ +extern const ResourceOwnerDesc buffer_io_resowner_desc; +extern const ResourceOwnerDesc buffer_pin_resowner_desc; + +/* Convenience wrappers over ResourceOwnerRemember/Forget */ +static inline void +ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer) +{ + ResourceOwnerRemember(owner, Int32GetDatum(buffer), &buffer_pin_resowner_desc); +} +static inline void +ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer) +{ + ResourceOwnerForget(owner, Int32GetDatum(buffer), &buffer_pin_resowner_desc); +} +static inline void +ResourceOwnerRememberBufferIO(ResourceOwner owner, Buffer buffer) +{ + ResourceOwnerRemember(owner, Int32GetDatum(buffer), &buffer_io_resowner_desc); +} +static inline void +ResourceOwnerForgetBufferIO(ResourceOwner owner, Buffer buffer) +{ + ResourceOwnerForget(owner, Int32GetDatum(buffer), &buffer_io_resowner_desc); +} + /* * Internal buffer management routines */ @@ -418,6 +445,7 @@ extern void BufTableDelete(BufferTag *tagPtr, uint32 hashcode); /* localbuf.c */ extern bool PinLocalBuffer(BufferDesc *buf_hdr, bool adjust_usagecount); extern void UnpinLocalBuffer(Buffer buffer); +extern void UnpinLocalBufferNoOwner(Buffer buffer); extern PrefetchBufferResult PrefetchLocalBuffer(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum); diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h index 2eda466d44..41e26d3e20 100644 --- a/src/include/storage/bufmgr.h +++ b/src/include/storage/bufmgr.h @@ -207,7 +207,7 @@ extern Buffer ExtendBufferedRelTo(BufferManagerRelation bmr, extern void InitBufferPoolAccess(void); extern void AtEOXact_Buffers(bool isCommit); -extern void PrintBufferLeakWarning(Buffer buffer); +extern char *DebugPrintBufferRefcount(Buffer buffer); extern void CheckPointBuffers(int flags); extern BlockNumber BufferGetBlockNumber(Buffer buffer); extern BlockNumber RelationGetNumberOfBlocksInFork(Relation relation, @@ -248,8 +248,6 @@ extern bool ConditionalLockBufferForCleanup(Buffer buffer); extern bool IsBufferCleanupOK(Buffer buffer); extern bool HoldingBufferPinThatDelaysRecovery(void); -extern void AbortBufferIO(Buffer buffer); - extern bool BgBufferSync(struct WritebackContext *wb_context); /* in buf_init.c */ diff --git a/src/include/utils/catcache.h b/src/include/utils/catcache.h index af0b341873..df40538218 100644 --- a/src/include/utils/catcache.h +++ b/src/include/utils/catcache.h @@ -225,7 +225,4 @@ extern void PrepareToInvalidateCacheTuple(Relation relation, HeapTuple newtuple, void (*function) (int, uint32, Oid)); -extern void PrintCatCacheLeakWarning(HeapTuple tuple); -extern void PrintCatCacheListLeakWarning(CatCList *list); - #endif /* CATCACHE_H */ diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h index 97e58157b7..110e649fce 100644 --- a/src/include/utils/plancache.h +++ b/src/include/utils/plancache.h @@ -188,6 +188,8 @@ typedef struct CachedExpression extern void InitPlanCache(void); extern void ResetPlanCache(void); +extern void ReleaseAllPlanCacheRefsInOwner(ResourceOwner owner); + extern CachedPlanSource *CreateCachedPlan(struct RawStmt *raw_parse_tree, const char *query_string, CommandTag commandTag); diff --git a/src/include/utils/resowner.h b/src/include/utils/resowner.h index cb35e9e090..0735480214 100644 --- a/src/include/utils/resowner.h +++ b/src/include/utils/resowner.h @@ -37,19 +37,87 @@ extern PGDLLIMPORT ResourceOwner AuxProcessResourceOwner; /* * 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. + * 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. + * + * Within each phase, resources are released in priority order. Priority is + * just an integer specified in ResourceOwnerDesc. The priorities of built-in + * resource types are given below, extensions may use any priority relative to + * those or RELEASE_PRIO_FIRST/LAST. RELEASE_PRIO_FIRST is a fine choice if + * your resource doesn't depend on any other resources. */ typedef enum { - RESOURCE_RELEASE_BEFORE_LOCKS, + RESOURCE_RELEASE_BEFORE_LOCKS = 1, RESOURCE_RELEASE_LOCKS, RESOURCE_RELEASE_AFTER_LOCKS, } ResourceReleasePhase; +typedef uint32 ResourceReleasePriority; + +/* priorities of built-in BEFORE_LOCKS resources */ +#define RELEASE_PRIO_BUFFER_IOS 100 +#define RELEASE_PRIO_BUFFER_PINS 200 +#define RELEASE_PRIO_RELCACHE_REFS 300 +#define RELEASE_PRIO_DSMS 400 +#define RELEASE_PRIO_JIT_CONTEXTS 500 +#define RELEASE_PRIO_CRYPTOHASH_CONTEXTS 600 +#define RELEASE_PRIO_HMAC_CONTEXTS 700 + +/* priorities of built-in AFTER_LOCKS resources */ +#define RELEASE_PRIO_CATCACHE_REFS 100 +#define RELEASE_PRIO_CATCACHE_LIST_REFS 200 +#define RELEASE_PRIO_PLANCACHE_REFS 300 +#define RELEASE_PRIO_TUPDESC_REFS 400 +#define RELEASE_PRIO_SNAPSHOT_REFS 500 +#define RELEASE_PRIO_FILES 600 + +/* 0 is considered invalid */ +#define RELEASE_PRIO_FIRST 1 +#define RELEASE_PRIO_LAST UINT32_MAX + +/* + * In order to track an object, resowner.c needs a few callbacks for it. + * The callbacks for resources of a specific kind are encapsulated in + * ResourceOwnerDesc. + * + * Note that the callbacks occur post-commit or post-abort, so the callback + * functions can only do noncritical cleanup and must not fail. + */ +typedef struct ResourceOwnerDesc +{ + const char *name; /* name for the object kind, for debugging */ + + /* when are these objects released? */ + ResourceReleasePhase release_phase; + ResourceReleasePriority release_priority; + + /* + * Release resource. + * + * This is called for each resource in the resource owner, in the order + * specified by 'release_phase' and 'release_priority' when the whole + * resource owner is been released or when ResourceOwnerReleaseAllOfKind() + * is called. The resource is implicitly removed from the owner, the + * callback function doesn't need to call ResourceOwnerForget. + */ + void (*ReleaseResource) (Datum res); + + /* + * Format a string describing the resource, for debugging purposes. If a + * resource has not been properly released before commit, this is used to + * print a WARNING. + * + * This can be left to NULL, in which case a generic "[resource name]: %p" + * format is used. + */ + char *(*DebugPrint) (Datum res); + +} ResourceOwnerDesc; + /* * Dynamically loaded modules can get control during ResourceOwnerRelease * by providing a callback of this form. @@ -71,16 +139,28 @@ extern void ResourceOwnerRelease(ResourceOwner owner, ResourceReleasePhase phase, bool isCommit, bool isTopLevel); -extern void ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner); extern void ResourceOwnerDelete(ResourceOwner owner); extern ResourceOwner ResourceOwnerGetParent(ResourceOwner owner); extern void ResourceOwnerNewParent(ResourceOwner owner, ResourceOwner newparent); + +extern void ResourceOwnerEnlarge(ResourceOwner owner); +extern void ResourceOwnerRemember(ResourceOwner owner, Datum res, const ResourceOwnerDesc *kind); +extern void ResourceOwnerForget(ResourceOwner owner, Datum res, const ResourceOwnerDesc *kind); + +extern void ResourceOwnerReleaseAllOfKind(ResourceOwner owner, const ResourceOwnerDesc *kind); + extern void RegisterResourceReleaseCallback(ResourceReleaseCallback callback, void *arg); extern void UnregisterResourceReleaseCallback(ResourceReleaseCallback callback, void *arg); + extern void CreateAuxProcessResourceOwner(void); extern void ReleaseAuxProcessResources(bool isCommit); +/* special support for local lock management */ +struct LOCALLOCK; +extern void ResourceOwnerRememberLock(ResourceOwner owner, struct LOCALLOCK *locallock); +extern void ResourceOwnerForgetLock(ResourceOwner owner, struct LOCALLOCK *locallock); + #endif /* RESOWNER_H */ diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 4b76f7699a..f8c7f48747 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -8470,7 +8470,7 @@ plpgsql_xact_cb(XactEvent event, void *arg) FreeExecutorState(shared_simple_eval_estate); shared_simple_eval_estate = NULL; if (shared_simple_eval_resowner) - ResourceOwnerReleaseAllPlanCacheRefs(shared_simple_eval_resowner); + ReleaseAllPlanCacheRefsInOwner(shared_simple_eval_resowner); shared_simple_eval_resowner = NULL; } else if (event == XACT_EVENT_ABORT || diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c index d8994538b7..462ba438d6 100644 --- a/src/pl/plpgsql/src/pl_handler.c +++ b/src/pl/plpgsql/src/pl_handler.c @@ -288,7 +288,7 @@ plpgsql_call_handler(PG_FUNCTION_ARGS) /* Be sure to release the procedure resowner if any */ if (procedure_resowner) { - ResourceOwnerReleaseAllPlanCacheRefs(procedure_resowner); + ReleaseAllPlanCacheRefsInOwner(procedure_resowner); ResourceOwnerDelete(procedure_resowner); } } @@ -393,7 +393,7 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS) /* Clean up the private EState and resowner */ FreeExecutorState(simple_eval_estate); - ResourceOwnerReleaseAllPlanCacheRefs(simple_eval_resowner); + ReleaseAllPlanCacheRefsInOwner(simple_eval_resowner); ResourceOwnerDelete(simple_eval_resowner); /* Function should now have no remaining use-counts ... */ @@ -410,7 +410,7 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS) /* Clean up the private EState and resowner */ FreeExecutorState(simple_eval_estate); - ResourceOwnerReleaseAllPlanCacheRefs(simple_eval_resowner); + ReleaseAllPlanCacheRefsInOwner(simple_eval_resowner); ResourceOwnerDelete(simple_eval_resowner); /* Function should now have no remaining use-counts ... */ diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile index e81873cb5a..738b715e79 100644 --- a/src/test/modules/Makefile +++ b/src/test/modules/Makefile @@ -28,6 +28,7 @@ SUBDIRS = \ test_predtest \ test_rbtree \ test_regex \ + test_resowner \ test_rls_hooks \ test_shm_mq \ test_slru \ diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build index fcd643f6f1..d4828dc44d 100644 --- a/src/test/modules/meson.build +++ b/src/test/modules/meson.build @@ -25,6 +25,7 @@ subdir('test_pg_dump') subdir('test_predtest') subdir('test_rbtree') subdir('test_regex') +subdir('test_resowner') subdir('test_rls_hooks') subdir('test_shm_mq') subdir('test_slru') diff --git a/src/test/modules/test_resowner/.gitignore b/src/test/modules/test_resowner/.gitignore new file mode 100644 index 0000000000..5dcb3ff972 --- /dev/null +++ b/src/test/modules/test_resowner/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/src/test/modules/test_resowner/Makefile b/src/test/modules/test_resowner/Makefile new file mode 100644 index 0000000000..d28da3c286 --- /dev/null +++ b/src/test/modules/test_resowner/Makefile @@ -0,0 +1,24 @@ +# src/test/modules/test_resowner/Makefile + +MODULE_big = test_resowner +OBJS = \ + $(WIN32RES) \ + test_resowner_basic.o \ + test_resowner_many.o +PGFILEDESC = "test_resowner - test code for ResourceOwners" + +EXTENSION = test_resowner +DATA = test_resowner--1.0.sql + +REGRESS = test_resowner + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = src/test/modules/test_resowner +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/src/test/modules/test_resowner/expected/test_resowner.out b/src/test/modules/test_resowner/expected/test_resowner.out new file mode 100644 index 0000000000..527b678fc1 --- /dev/null +++ b/src/test/modules/test_resowner/expected/test_resowner.out @@ -0,0 +1,197 @@ +CREATE EXTENSION test_resowner; +-- This is small enough that everything fits in the small array +SELECT test_resowner_priorities(2, 3); +NOTICE: releasing resources before locks +NOTICE: releasing string: child before locks priority 1 +NOTICE: releasing string: child before locks priority 1 +NOTICE: releasing string: child before locks priority 2 +NOTICE: releasing string: parent before locks priority 1 +NOTICE: releasing string: parent before locks priority 1 +NOTICE: releasing string: parent before locks priority 2 +NOTICE: releasing locks +NOTICE: releasing resources after locks +NOTICE: releasing string: child after locks priority 1 +NOTICE: releasing string: child after locks priority 1 +NOTICE: releasing string: child after locks priority 2 +NOTICE: releasing string: parent after locks priority 1 +NOTICE: releasing string: parent after locks priority 1 +NOTICE: releasing string: parent after locks priority 2 + test_resowner_priorities +-------------------------- + +(1 row) + +-- Same test with more resources, to exercise the hash table +SELECT test_resowner_priorities(2, 32); +NOTICE: releasing resources before locks +NOTICE: releasing string: child before locks priority 1 +NOTICE: releasing string: child before locks priority 1 +NOTICE: releasing string: child before locks priority 1 +NOTICE: releasing string: child before locks priority 1 +NOTICE: releasing string: child before locks priority 1 +NOTICE: releasing string: child before locks priority 1 +NOTICE: releasing string: child before locks priority 1 +NOTICE: releasing string: child before locks priority 1 +NOTICE: releasing string: child before locks priority 1 +NOTICE: releasing string: child before locks priority 1 +NOTICE: releasing string: child before locks priority 1 +NOTICE: releasing string: child before locks priority 1 +NOTICE: releasing string: child before locks priority 1 +NOTICE: releasing string: child before locks priority 1 +NOTICE: releasing string: child before locks priority 1 +NOTICE: releasing string: child before locks priority 1 +NOTICE: releasing string: child before locks priority 2 +NOTICE: releasing string: child before locks priority 2 +NOTICE: releasing string: child before locks priority 2 +NOTICE: releasing string: child before locks priority 2 +NOTICE: releasing string: child before locks priority 2 +NOTICE: releasing string: child before locks priority 2 +NOTICE: releasing string: child before locks priority 2 +NOTICE: releasing string: child before locks priority 2 +NOTICE: releasing string: child before locks priority 2 +NOTICE: releasing string: child before locks priority 2 +NOTICE: releasing string: child before locks priority 2 +NOTICE: releasing string: child before locks priority 2 +NOTICE: releasing string: child before locks priority 2 +NOTICE: releasing string: child before locks priority 2 +NOTICE: releasing string: child before locks priority 2 +NOTICE: releasing string: child before locks priority 2 +NOTICE: releasing string: parent before locks priority 1 +NOTICE: releasing string: parent before locks priority 1 +NOTICE: releasing string: parent before locks priority 1 +NOTICE: releasing string: parent before locks priority 1 +NOTICE: releasing string: parent before locks priority 1 +NOTICE: releasing string: parent before locks priority 1 +NOTICE: releasing string: parent before locks priority 1 +NOTICE: releasing string: parent before locks priority 1 +NOTICE: releasing string: parent before locks priority 1 +NOTICE: releasing string: parent before locks priority 1 +NOTICE: releasing string: parent before locks priority 1 +NOTICE: releasing string: parent before locks priority 1 +NOTICE: releasing string: parent before locks priority 1 +NOTICE: releasing string: parent before locks priority 1 +NOTICE: releasing string: parent before locks priority 1 +NOTICE: releasing string: parent before locks priority 1 +NOTICE: releasing string: parent before locks priority 2 +NOTICE: releasing string: parent before locks priority 2 +NOTICE: releasing string: parent before locks priority 2 +NOTICE: releasing string: parent before locks priority 2 +NOTICE: releasing string: parent before locks priority 2 +NOTICE: releasing string: parent before locks priority 2 +NOTICE: releasing string: parent before locks priority 2 +NOTICE: releasing string: parent before locks priority 2 +NOTICE: releasing string: parent before locks priority 2 +NOTICE: releasing string: parent before locks priority 2 +NOTICE: releasing string: parent before locks priority 2 +NOTICE: releasing string: parent before locks priority 2 +NOTICE: releasing string: parent before locks priority 2 +NOTICE: releasing string: parent before locks priority 2 +NOTICE: releasing string: parent before locks priority 2 +NOTICE: releasing string: parent before locks priority 2 +NOTICE: releasing locks +NOTICE: releasing resources after locks +NOTICE: releasing string: child after locks priority 1 +NOTICE: releasing string: child after locks priority 1 +NOTICE: releasing string: child after locks priority 1 +NOTICE: releasing string: child after locks priority 1 +NOTICE: releasing string: child after locks priority 1 +NOTICE: releasing string: child after locks priority 1 +NOTICE: releasing string: child after locks priority 1 +NOTICE: releasing string: child after locks priority 1 +NOTICE: releasing string: child after locks priority 1 +NOTICE: releasing string: child after locks priority 1 +NOTICE: releasing string: child after locks priority 1 +NOTICE: releasing string: child after locks priority 1 +NOTICE: releasing string: child after locks priority 1 +NOTICE: releasing string: child after locks priority 1 +NOTICE: releasing string: child after locks priority 1 +NOTICE: releasing string: child after locks priority 1 +NOTICE: releasing string: child after locks priority 2 +NOTICE: releasing string: child after locks priority 2 +NOTICE: releasing string: child after locks priority 2 +NOTICE: releasing string: child after locks priority 2 +NOTICE: releasing string: child after locks priority 2 +NOTICE: releasing string: child after locks priority 2 +NOTICE: releasing string: child after locks priority 2 +NOTICE: releasing string: child after locks priority 2 +NOTICE: releasing string: child after locks priority 2 +NOTICE: releasing string: child after locks priority 2 +NOTICE: releasing string: child after locks priority 2 +NOTICE: releasing string: child after locks priority 2 +NOTICE: releasing string: child after locks priority 2 +NOTICE: releasing string: child after locks priority 2 +NOTICE: releasing string: child after locks priority 2 +NOTICE: releasing string: child after locks priority 2 +NOTICE: releasing string: parent after locks priority 1 +NOTICE: releasing string: parent after locks priority 1 +NOTICE: releasing string: parent after locks priority 1 +NOTICE: releasing string: parent after locks priority 1 +NOTICE: releasing string: parent after locks priority 1 +NOTICE: releasing string: parent after locks priority 1 +NOTICE: releasing string: parent after locks priority 1 +NOTICE: releasing string: parent after locks priority 1 +NOTICE: releasing string: parent after locks priority 1 +NOTICE: releasing string: parent after locks priority 1 +NOTICE: releasing string: parent after locks priority 1 +NOTICE: releasing string: parent after locks priority 1 +NOTICE: releasing string: parent after locks priority 1 +NOTICE: releasing string: parent after locks priority 1 +NOTICE: releasing string: parent after locks priority 1 +NOTICE: releasing string: parent after locks priority 1 +NOTICE: releasing string: parent after locks priority 2 +NOTICE: releasing string: parent after locks priority 2 +NOTICE: releasing string: parent after locks priority 2 +NOTICE: releasing string: parent after locks priority 2 +NOTICE: releasing string: parent after locks priority 2 +NOTICE: releasing string: parent after locks priority 2 +NOTICE: releasing string: parent after locks priority 2 +NOTICE: releasing string: parent after locks priority 2 +NOTICE: releasing string: parent after locks priority 2 +NOTICE: releasing string: parent after locks priority 2 +NOTICE: releasing string: parent after locks priority 2 +NOTICE: releasing string: parent after locks priority 2 +NOTICE: releasing string: parent after locks priority 2 +NOTICE: releasing string: parent after locks priority 2 +NOTICE: releasing string: parent after locks priority 2 +NOTICE: releasing string: parent after locks priority 2 + test_resowner_priorities +-------------------------- + +(1 row) + +-- Basic test with lots more resources, to test extending the hash table +SELECT test_resowner_many( + 3, -- # of different resource kinds + 100000, -- before-locks resources to remember + 500, -- before-locks resources to forget + 100000, -- after-locks resources to remember + 500 -- after-locks resources to forget +); +NOTICE: remembering 100000 before-locks resources +NOTICE: remembering 100000 after-locks resources +NOTICE: forgetting 500 before-locks resources +NOTICE: forgetting 500 after-locks resources +NOTICE: releasing resources before locks +NOTICE: releasing locks +NOTICE: releasing resources after locks + test_resowner_many +-------------------- + +(1 row) + +-- Test resource leak warning +SELECT test_resowner_leak(); +WARNING: resource was not closed: test string "my string" +NOTICE: releasing string: my string + test_resowner_leak +-------------------- + +(1 row) + +-- Negative tests, using a resource owner after release-phase has started. +set client_min_messages='warning'; -- order between ERROR and NOTICE varies +SELECT test_resowner_remember_between_phases(); +ERROR: ResourceOwnerEnlarge called after release started +SELECT test_resowner_forget_between_phases(); +ERROR: ResourceOwnerForget called for test resource after release started +reset client_min_messages; diff --git a/src/test/modules/test_resowner/meson.build b/src/test/modules/test_resowner/meson.build new file mode 100644 index 0000000000..5f669505a6 --- /dev/null +++ b/src/test/modules/test_resowner/meson.build @@ -0,0 +1,34 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +test_resowner_sources = files( + 'test_resowner_basic.c', + 'test_resowner_many.c', +) + +if host_system == 'windows' + test_resowner_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'test_resowner', + '--FILEDESC', 'test_resowner - test code for ResourceOwners',]) +endif + +test_resowner = shared_module('test_resowner', + test_resowner_sources, + kwargs: pg_test_mod_args, +) +test_install_libs += test_resowner + +test_install_data += files( + 'test_resowner.control', + 'test_resowner--1.0.sql', +) + +tests += { + 'name': 'test_resowner', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'test_resowner', + ], + }, +} diff --git a/src/test/modules/test_resowner/sql/test_resowner.sql b/src/test/modules/test_resowner/sql/test_resowner.sql new file mode 100644 index 0000000000..23284b7c8b --- /dev/null +++ b/src/test/modules/test_resowner/sql/test_resowner.sql @@ -0,0 +1,25 @@ +CREATE EXTENSION test_resowner; + +-- This is small enough that everything fits in the small array +SELECT test_resowner_priorities(2, 3); + +-- Same test with more resources, to exercise the hash table +SELECT test_resowner_priorities(2, 32); + +-- Basic test with lots more resources, to test extending the hash table +SELECT test_resowner_many( + 3, -- # of different resource kinds + 100000, -- before-locks resources to remember + 500, -- before-locks resources to forget + 100000, -- after-locks resources to remember + 500 -- after-locks resources to forget +); + +-- Test resource leak warning +SELECT test_resowner_leak(); + +-- Negative tests, using a resource owner after release-phase has started. +set client_min_messages='warning'; -- order between ERROR and NOTICE varies +SELECT test_resowner_remember_between_phases(); +SELECT test_resowner_forget_between_phases(); +reset client_min_messages; diff --git a/src/test/modules/test_resowner/test_resowner--1.0.sql b/src/test/modules/test_resowner/test_resowner--1.0.sql new file mode 100644 index 0000000000..26fed7a010 --- /dev/null +++ b/src/test/modules/test_resowner/test_resowner--1.0.sql @@ -0,0 +1,30 @@ +/* src/test/modules/test_resowner/test_resowner--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION test_resowner" to load this file. \quit + +CREATE FUNCTION test_resowner_priorities(nkinds pg_catalog.int4, nresources pg_catalog.int4) + RETURNS pg_catalog.void + AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_resowner_leak() + RETURNS pg_catalog.void + AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_resowner_remember_between_phases() + RETURNS pg_catalog.void + AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_resowner_forget_between_phases() + RETURNS pg_catalog.void + AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_resowner_many( + nkinds pg_catalog.int4, + nremember_bl pg_catalog.int4, + nforget_bl pg_catalog.int4, + nremember_al pg_catalog.int4, + nforget_al pg_catalog.int4 +) + RETURNS pg_catalog.void + AS 'MODULE_PATHNAME' LANGUAGE C; diff --git a/src/test/modules/test_resowner/test_resowner.control b/src/test/modules/test_resowner/test_resowner.control new file mode 100644 index 0000000000..b56c4ee92b --- /dev/null +++ b/src/test/modules/test_resowner/test_resowner.control @@ -0,0 +1,4 @@ +comment = 'Test code for ResourceOwners' +default_version = '1.0' +module_pathname = '$libdir/test_resowner' +relocatable = true diff --git a/src/test/modules/test_resowner/test_resowner_basic.c b/src/test/modules/test_resowner/test_resowner_basic.c new file mode 100644 index 0000000000..4be46e9ddf --- /dev/null +++ b/src/test/modules/test_resowner/test_resowner_basic.c @@ -0,0 +1,211 @@ +/*-------------------------------------------------------------------------- + * + * test_resowner_basic.c + * Test basic ResourceOwner functionality + * + * Copyright (c) 2022-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/test/modules/test_resowner/test_resowner_basic.c + * + * ------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "fmgr.h" +#include "lib/ilist.h" +#include "utils/memutils.h" +#include "utils/resowner.h" + +PG_MODULE_MAGIC; + +static void ReleaseString(Datum res); +static char *PrintString(Datum res); + +/* + * A resource that tracks strings and prints the string when it's released. + * This makes the order that the resources are released visible. + */ +static const ResourceOwnerDesc string_desc = { + .name = "test resource", + .release_phase = RESOURCE_RELEASE_AFTER_LOCKS, + .release_priority = RELEASE_PRIO_FIRST, + .ReleaseResource = ReleaseString, + .DebugPrint = PrintString +}; + +static void +ReleaseString(Datum res) +{ + elog(NOTICE, "releasing string: %s", DatumGetPointer(res)); +} + +static char * +PrintString(Datum res) +{ + return psprintf("test string \"%s\"", DatumGetPointer(res)); +} + +/* demonstrates phases and priorities between a parent and child context */ +PG_FUNCTION_INFO_V1(test_resowner_priorities); +Datum +test_resowner_priorities(PG_FUNCTION_ARGS) +{ + int32 nkinds = PG_GETARG_INT32(0); + int32 nresources = PG_GETARG_INT32(1); + ResourceOwner parent, + child; + ResourceOwnerDesc *before_desc; + ResourceOwnerDesc *after_desc; + + if (nkinds <= 0) + elog(ERROR, "nkinds must be greater than zero"); + if (nresources <= 0) + elog(ERROR, "nresources must be greater than zero"); + + parent = ResourceOwnerCreate(CurrentResourceOwner, "test parent"); + child = ResourceOwnerCreate(parent, "test child"); + + before_desc = palloc(nkinds * sizeof(ResourceOwnerDesc)); + for (int i = 0; i < nkinds; i++) + { + before_desc[i].name = psprintf("test resource before locks %d", i); + before_desc[i].release_phase = RESOURCE_RELEASE_BEFORE_LOCKS; + before_desc[i].release_priority = RELEASE_PRIO_FIRST + i; + before_desc[i].ReleaseResource = ReleaseString; + before_desc[i].DebugPrint = PrintString; + } + after_desc = palloc(nkinds * sizeof(ResourceOwnerDesc)); + for (int i = 0; i < nkinds; i++) + { + after_desc[i].name = psprintf("test resource after locks %d", i); + after_desc[i].release_phase = RESOURCE_RELEASE_AFTER_LOCKS; + after_desc[i].release_priority = RELEASE_PRIO_FIRST + i; + after_desc[i].ReleaseResource = ReleaseString; + after_desc[i].DebugPrint = PrintString; + } + + /* Add a bunch of resources to child, with different priorities */ + for (int i = 0; i < nresources; i++) + { + ResourceOwnerDesc *kind = &before_desc[i % nkinds]; + + ResourceOwnerEnlarge(child); + ResourceOwnerRemember(child, + CStringGetDatum(psprintf("child before locks priority %d", kind->release_priority)), + kind); + } + for (int i = 0; i < nresources; i++) + { + ResourceOwnerDesc *kind = &after_desc[i % nkinds]; + + ResourceOwnerEnlarge(child); + ResourceOwnerRemember(child, + CStringGetDatum(psprintf("child after locks priority %d", kind->release_priority)), + kind); + } + + /* And also to the parent */ + for (int i = 0; i < nresources; i++) + { + ResourceOwnerDesc *kind = &after_desc[i % nkinds]; + + ResourceOwnerEnlarge(parent); + ResourceOwnerRemember(parent, + CStringGetDatum(psprintf("parent after locks priority %d", kind->release_priority)), + kind); + } + for (int i = 0; i < nresources; i++) + { + ResourceOwnerDesc *kind = &before_desc[i % nkinds]; + + ResourceOwnerEnlarge(parent); + ResourceOwnerRemember(parent, + CStringGetDatum(psprintf("parent before locks priority %d", kind->release_priority)), + kind); + } + + elog(NOTICE, "releasing resources before locks"); + ResourceOwnerRelease(parent, RESOURCE_RELEASE_BEFORE_LOCKS, false, false); + elog(NOTICE, "releasing locks"); + ResourceOwnerRelease(parent, RESOURCE_RELEASE_LOCKS, false, false); + elog(NOTICE, "releasing resources after locks"); + ResourceOwnerRelease(parent, RESOURCE_RELEASE_AFTER_LOCKS, false, false); + + ResourceOwnerDelete(parent); + + PG_RETURN_VOID(); +} + +PG_FUNCTION_INFO_V1(test_resowner_leak); +Datum +test_resowner_leak(PG_FUNCTION_ARGS) +{ + ResourceOwner resowner; + + resowner = ResourceOwnerCreate(CurrentResourceOwner, "TestOwner"); + + ResourceOwnerEnlarge(resowner); + + ResourceOwnerRemember(resowner, CStringGetDatum("my string"), &string_desc); + + /* don't call ResourceOwnerForget, so that it is leaked */ + + ResourceOwnerRelease(resowner, RESOURCE_RELEASE_BEFORE_LOCKS, true, false); + ResourceOwnerRelease(resowner, RESOURCE_RELEASE_LOCKS, true, false); + ResourceOwnerRelease(resowner, RESOURCE_RELEASE_AFTER_LOCKS, true, false); + + ResourceOwnerDelete(resowner); + + PG_RETURN_VOID(); +} + +PG_FUNCTION_INFO_V1(test_resowner_remember_between_phases); +Datum +test_resowner_remember_between_phases(PG_FUNCTION_ARGS) +{ + ResourceOwner resowner; + + resowner = ResourceOwnerCreate(CurrentResourceOwner, "TestOwner"); + + ResourceOwnerRelease(resowner, RESOURCE_RELEASE_BEFORE_LOCKS, true, false); + + /* + * Try to remember a new resource. Fails because we already called + * ResourceOwnerRelease. + */ + ResourceOwnerEnlarge(resowner); + ResourceOwnerRemember(resowner, CStringGetDatum("my string"), &string_desc); + + /* unreachable */ + elog(ERROR, "ResourceOwnerEnlarge should have errored out"); + + PG_RETURN_VOID(); +} + +PG_FUNCTION_INFO_V1(test_resowner_forget_between_phases); +Datum +test_resowner_forget_between_phases(PG_FUNCTION_ARGS) +{ + ResourceOwner resowner; + Datum str_resource; + + resowner = ResourceOwnerCreate(CurrentResourceOwner, "TestOwner"); + + ResourceOwnerEnlarge(resowner); + str_resource = CStringGetDatum("my string"); + ResourceOwnerRemember(resowner, str_resource, &string_desc); + + ResourceOwnerRelease(resowner, RESOURCE_RELEASE_BEFORE_LOCKS, true, false); + + /* + * Try to forget the resource that was remembered earlier. Fails because + * we already called ResourceOwnerRelease. + */ + ResourceOwnerForget(resowner, str_resource, &string_desc); + + /* unreachable */ + elog(ERROR, "ResourceOwnerForget should have errored out"); + + PG_RETURN_VOID(); +} diff --git a/src/test/modules/test_resowner/test_resowner_many.c b/src/test/modules/test_resowner/test_resowner_many.c new file mode 100644 index 0000000000..bb820793a1 --- /dev/null +++ b/src/test/modules/test_resowner/test_resowner_many.c @@ -0,0 +1,296 @@ +/*-------------------------------------------------------------------------- + * + * test_resowner_many.c + * Test ResourceOwner functionality with lots of resources + * + * Copyright (c) 2022-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/test/modules/test_resowner/test_resowner_many.c + * + * ------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "fmgr.h" +#include "lib/ilist.h" +#include "utils/memutils.h" +#include "utils/resowner.h" + +/* + * Define a custom resource type to use in the test. The resource being + * tracked is a palloc'd ManyTestResource struct. + * + * To cross-check that the ResourceOwner calls the callback functions + * correctly, we keep track of the remembered resources ourselves in a linked + * list, and also keep counters of how many times the callback functions have + * been called. + */ +typedef struct +{ + ResourceOwnerDesc desc; + int nremembered; + int nforgotten; + int nreleased; + int nleaked; + + dlist_head current_resources; +} ManyTestResourceKind; + +typedef struct +{ + ManyTestResourceKind *kind; + dlist_node node; +} ManyTestResource; + +/* + * Current release phase, and priority of last call to the release callback. + * This is used to check that the resources are released in correct order. + */ +static ResourceReleasePhase current_release_phase; +static uint32 last_release_priority = 0; + +/* prototypes for local functions */ +static void ReleaseManyTestResource(Datum res); +static char *PrintManyTest(Datum res); +static void InitManyTestResourceKind(ManyTestResourceKind *kind, char *name, + ResourceReleasePhase phase, uint32 priority); +static void RememberManyTestResources(ResourceOwner owner, + ManyTestResourceKind *kinds, int nkinds, + int nresources); +static void ForgetManyTestResources(ResourceOwner owner, + ManyTestResourceKind *kinds, int nkinds, + int nresources); +static int GetTotalResourceCount(ManyTestResourceKind *kinds, int nkinds); + +/* ResourceOwner callback */ +static void +ReleaseManyTestResource(Datum res) +{ + ManyTestResource *mres = (ManyTestResource *) DatumGetPointer(res); + + elog(DEBUG1, "releasing resource %p from %s", mres, mres->kind->desc.name); + Assert(last_release_priority <= mres->kind->desc.release_priority); + + dlist_delete(&mres->node); + mres->kind->nreleased++; + last_release_priority = mres->kind->desc.release_priority; + pfree(mres); +} + +/* ResourceOwner callback */ +static char * +PrintManyTest(Datum res) +{ + ManyTestResource *mres = (ManyTestResource *) DatumGetPointer(res); + + /* + * XXX: we assume that the DebugPrint function is called once for each + * leaked resource, and that there are no other callers. + */ + mres->kind->nleaked++; + + return psprintf("many-test resource from %s", mres->kind->desc.name); +} + +static void +InitManyTestResourceKind(ManyTestResourceKind *kind, char *name, + ResourceReleasePhase phase, uint32 priority) +{ + kind->desc.name = name; + kind->desc.release_phase = phase; + kind->desc.release_priority = priority; + kind->desc.ReleaseResource = ReleaseManyTestResource; + kind->desc.DebugPrint = PrintManyTest; + kind->nremembered = 0; + kind->nforgotten = 0; + kind->nreleased = 0; + kind->nleaked = 0; + dlist_init(&kind->current_resources); +} + +/* + * Remember 'nresources' resources. The resources are remembered in round + * robin fashion with the kinds from 'kinds' array. + */ +static void +RememberManyTestResources(ResourceOwner owner, + ManyTestResourceKind *kinds, int nkinds, + int nresources) +{ + int kind_idx = 0; + + for (int i = 0; i < nresources; i++) + { + ManyTestResource *mres = palloc(sizeof(ManyTestResource)); + + mres->kind = &kinds[kind_idx]; + dlist_node_init(&mres->node); + + ResourceOwnerEnlarge(owner); + ResourceOwnerRemember(owner, PointerGetDatum(mres), &kinds[kind_idx].desc); + kinds[kind_idx].nremembered++; + dlist_push_tail(&kinds[kind_idx].current_resources, &mres->node); + + elog(DEBUG1, "remembered resource %p from %s", mres, mres->kind->desc.name); + + kind_idx = (kind_idx + 1) % nkinds; + } +} + +/* + * Forget 'nresources' resources, in round robin fashion from 'kinds'. + */ +static void +ForgetManyTestResources(ResourceOwner owner, + ManyTestResourceKind *kinds, int nkinds, + int nresources) +{ + int kind_idx = 0; + int ntotal; + + ntotal = GetTotalResourceCount(kinds, nkinds); + if (ntotal < nresources) + elog(PANIC, "cannot free %d resources, only %d remembered", nresources, ntotal); + + for (int i = 0; i < nresources; i++) + { + bool found = false; + + for (int j = 0; j < nkinds; j++) + { + kind_idx = (kind_idx + 1) % nkinds; + if (!dlist_is_empty(&kinds[kind_idx].current_resources)) + { + ManyTestResource *mres = dlist_head_element(ManyTestResource, node, &kinds[kind_idx].current_resources); + + ResourceOwnerForget(owner, PointerGetDatum(mres), &kinds[kind_idx].desc); + kinds[kind_idx].nforgotten++; + dlist_delete(&mres->node); + pfree(mres); + + found = true; + break; + } + } + if (!found) + elog(ERROR, "could not find a test resource to forget"); + } +} + +/* + * Get total number of currently active resources among 'kinds'. + */ +static int +GetTotalResourceCount(ManyTestResourceKind *kinds, int nkinds) +{ + int ntotal = 0; + + for (int i = 0; i < nkinds; i++) + ntotal += kinds[i].nremembered - kinds[i].nforgotten - kinds[i].nreleased; + + return ntotal; +} + +/* + * Remember lots of resources, belonging to 'nkinds' different resource types + * with different priorities. Then forget some of them, and finally, release + * the resource owner. We use a custom resource type that performs various + * sanity checks to verify that all the the resources are released, and in the + * correct order. + */ +PG_FUNCTION_INFO_V1(test_resowner_many); +Datum +test_resowner_many(PG_FUNCTION_ARGS) +{ + int32 nkinds = PG_GETARG_INT32(0); + int32 nremember_bl = PG_GETARG_INT32(1); + int32 nforget_bl = PG_GETARG_INT32(2); + int32 nremember_al = PG_GETARG_INT32(3); + int32 nforget_al = PG_GETARG_INT32(4); + + ResourceOwner resowner; + + ManyTestResourceKind *before_kinds; + ManyTestResourceKind *after_kinds; + + /* Sanity check the arguments */ + if (nkinds < 0) + elog(ERROR, "nkinds must be >= 0"); + if (nremember_bl < 0) + elog(ERROR, "nremember_bl must be >= 0"); + if (nforget_bl < 0 || nforget_bl > nremember_bl) + elog(ERROR, "nforget_bl must between 0 and 'nremember_bl'"); + if (nremember_al < 0) + elog(ERROR, "nremember_al must be greater than zero"); + if (nforget_al < 0 || nforget_al > nremember_al) + elog(ERROR, "nforget_al must between 0 and 'nremember_al'"); + + /* Initialize all the different resource kinds to use */ + before_kinds = palloc(nkinds * sizeof(ManyTestResourceKind)); + for (int i = 0; i < nkinds; i++) + { + InitManyTestResourceKind(&before_kinds[i], + psprintf("resource before locks %d", i), + RESOURCE_RELEASE_BEFORE_LOCKS, + RELEASE_PRIO_FIRST + i); + } + after_kinds = palloc(nkinds * sizeof(ManyTestResourceKind)); + for (int i = 0; i < nkinds; i++) + { + InitManyTestResourceKind(&after_kinds[i], + psprintf("resource after locks %d", i), + RESOURCE_RELEASE_AFTER_LOCKS, + RELEASE_PRIO_FIRST + i); + } + + resowner = ResourceOwnerCreate(CurrentResourceOwner, "TestOwner"); + + /* Remember a bunch of resources */ + if (nremember_bl > 0) + { + elog(NOTICE, "remembering %d before-locks resources", nremember_bl); + RememberManyTestResources(resowner, before_kinds, nkinds, nremember_bl); + } + if (nremember_al > 0) + { + elog(NOTICE, "remembering %d after-locks resources", nremember_al); + RememberManyTestResources(resowner, after_kinds, nkinds, nremember_al); + } + + /* Forget what was remembered */ + if (nforget_bl > 0) + { + elog(NOTICE, "forgetting %d before-locks resources", nforget_bl); + ForgetManyTestResources(resowner, before_kinds, nkinds, nforget_bl); + } + + if (nforget_al > 0) + { + elog(NOTICE, "forgetting %d after-locks resources", nforget_al); + ForgetManyTestResources(resowner, after_kinds, nkinds, nforget_al); + } + + /* Start releasing */ + elog(NOTICE, "releasing resources before locks"); + current_release_phase = RESOURCE_RELEASE_BEFORE_LOCKS; + last_release_priority = 0; + ResourceOwnerRelease(resowner, RESOURCE_RELEASE_BEFORE_LOCKS, false, false); + Assert(GetTotalResourceCount(before_kinds, nkinds) == 0); + + elog(NOTICE, "releasing locks"); + current_release_phase = RESOURCE_RELEASE_LOCKS; + last_release_priority = 0; + ResourceOwnerRelease(resowner, RESOURCE_RELEASE_LOCKS, false, false); + + elog(NOTICE, "releasing resources after locks"); + current_release_phase = RESOURCE_RELEASE_AFTER_LOCKS; + last_release_priority = 0; + ResourceOwnerRelease(resowner, RESOURCE_RELEASE_AFTER_LOCKS, false, false); + Assert(GetTotalResourceCount(before_kinds, nkinds) == 0); + Assert(GetTotalResourceCount(after_kinds, nkinds) == 0); + + ResourceOwnerDelete(resowner); + + PG_RETURN_VOID(); +} diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 87c1aee379..bf50a32119 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1527,6 +1527,8 @@ MVDependencies MVDependency MVNDistinct MVNDistinctItem +ManyTestResource +ManyTestResourceKind Material MaterialPath MaterialState @@ -2359,8 +2361,10 @@ ReplicationStateOnDisk ResTarget ReservoirState ReservoirStateData -ResourceArray +ResourceElem ResourceOwner +ResourceOwnerData +ResourceOwnerDesc ResourceReleaseCallback ResourceReleaseCallbackItem ResourceReleasePhase