#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
{
Assert(tupdesc->tdrefcount >= 0);
- ResourceOwnerEnlargeTupleDescs(CurrentResourceOwner);
+ ResourceOwnerEnlarge(CurrentResourceOwner);
tupdesc->tdrefcount++;
ResourceOwnerRememberTupleDesc(CurrentResourceOwner, tupdesc);
}
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);
+}
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);
#include "jit/jit.h"
#include "miscadmin.h"
#include "utils/fmgrprotos.h"
-#include "utils/resowner_private.h"
/* GUCs */
bool jit_enabled = true;
if (provider_successfully_loaded)
provider.release_context(context);
- ResourceOwnerForgetJIT(context->resowner, PointerGetDatum(context));
pfree(context);
}
#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
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;
llvm_recreate_llvm_context();
- ResourceOwnerEnlargeJIT(CurrentResourceOwner);
+ ResourceOwnerEnlarge(CurrentResourceOwner);
context = MemoryContextAllocZero(TopMemoryContext,
sizeof(LLVMJitContext));
/* ensure cleanup */
context->base.resowner = CurrentResourceOwner;
- ResourceOwnerRememberJIT(CurrentResourceOwner, PointerGetDatum(context));
+ ResourceOwnerRememberJIT(CurrentResourceOwner, context);
llvm_jit_context_in_use_count++;
llvm_jit_context->handles = NIL;
llvm_leave_fatal_on_oom();
+
+ if (context->resowner)
+ ResourceOwnerForgetJIT(context->resowner, llvm_jit_context);
}
/*
}
#endif /* LLVM_VERSION_MAJOR > 11 */
+
+/*
+ * ResourceOwner callbacks
+ */
+static void
+ResOwnerReleaseJitContext(Datum res)
+{
+ JitContext *context = (JitContext *) DatumGetPointer(res);
+
+ context->resowner = NULL;
+ jit_release_context(context);
+}
#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"
#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"
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
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,
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,
Assert(BufferIsValid(recent_buffer));
- ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+ ResourceOwnerEnlarge(CurrentResourceOwner);
ReservePrivateRefCountEntry();
InitBufferTag(&tag, &rlocator, forkNum, blockNum);
else
{
/* Set BM_VALID, terminate IO, and wake up any waiters */
- TerminateBufferIO(bufHdr, false, BM_VALID);
+ TerminateBufferIO(bufHdr, false, BM_VALID, true);
}
VacuumPageMiss++;
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 */
* 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);
* 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:
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);
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;
* 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
*
* 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.
*/
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);
/* 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)
/* Make sure we can handle the pin */
ReservePrivateRefCountEntry();
- ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+ ResourceOwnerEnlarge(CurrentResourceOwner);
/*
* Check whether buffer needs writing.
int RefCountErrors = 0;
PrivateRefCountEntry *res;
int i;
+ char *s;
/* check the array */
for (i = 0; i < REFCOUNT_ARRAY_ENTRIES; i++)
if (res->buffer != InvalidBuffer)
{
- PrintBufferLeakWarning(res->buffer);
+ s = DebugPrintBufferRefcount(res->buffer);
+ elog(WARNING, "buffer refcount leak: %s", s);
+ pfree(s);
+
RefCountErrors++;
}
}
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++;
}
}
/*
* 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;
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;
}
/*
* 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,
/* Make sure we can handle the pin */
ReservePrivateRefCountEntry();
- ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+ ResourceOwnerEnlarge(CurrentResourceOwner);
buf_state = LockBufHdr(bufHdr);
if (BufTagMatchesRelFileLocator(&bufHdr->tag, &rel->rd_locator) &&
/* Make sure we can handle the pin */
ReservePrivateRefCountEntry();
- ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+ ResourceOwnerEnlarge(CurrentResourceOwner);
buf_state = LockBufHdr(bufHdr);
if (BufTagMatchesRelFileLocator(&bufHdr->tag, &srelent->rlocator) &&
/* Make sure we can handle the pin */
ReservePrivateRefCountEntry();
- ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+ ResourceOwnerEnlarge(CurrentResourceOwner);
buf_state = LockBufHdr(bufHdr);
if (bufHdr->tag.dbOid == dbid &&
IncrBufferRefCount(Buffer buffer)
{
Assert(BufferIsPinned(buffer));
- ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+ ResourceOwnerEnlarge(CurrentResourceOwner);
if (BufferIsLocal(buffer))
LocalRefCount[-buffer - 1]++;
else
{
uint32 buf_state;
- ResourceOwnerEnlargeBufferIOs(CurrentResourceOwner);
+ ResourceOwnerEnlarge(CurrentResourceOwner);
for (;;)
{
* 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;
buf_state |= set_flag_bits;
UnlockBufHdr(buf, buf_state);
- ResourceOwnerForgetBufferIO(CurrentResourceOwner,
- BufferDescriptorGetBuffer(buf));
+ if (forget_owner)
+ ResourceOwnerForgetBufferIO(CurrentResourceOwner,
+ BufferDescriptorGetBuffer(buf));
ConditionVariableBroadcast(BufferDescriptorGetIOCV(buf));
}
*
* 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);
}
}
- TerminateBufferIO(buf_hdr, false, BM_IO_ERROR);
+ TerminateBufferIO(buf_hdr, false, BM_IO_ERROR, false);
}
/*
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));
+}
#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*/
if (LocalBufHash == NULL)
InitLocalBuffers();
- ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+ ResourceOwnerEnlarge(CurrentResourceOwner);
/* See if the desired buffer already exists */
hresult = (LocalBufferLookupEnt *)
uint32 buf_state;
BufferDesc *bufHdr;
- ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
+ ResourceOwnerEnlarge(CurrentResourceOwner);
/*
* Need to get a new buffer. We use a clock sweep algorithm (essentially
void
UnpinLocalBuffer(Buffer buffer)
+{
+ UnpinLocalBufferNoOwner(buffer);
+ ResourceOwnerForgetBuffer(CurrentResourceOwner, buffer);
+}
+
+void
+UnpinLocalBufferNoOwner(Buffer buffer)
{
int buffid = -buffer - 1;
Assert(LocalRefCount[buffid] > 0);
Assert(NLocalPinnedBuffers > 0);
- ResourceOwnerForgetBuffer(CurrentResourceOwner, buffer);
if (--LocalRefCount[buffid] == 0)
NLocalPinnedBuffers--;
}
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++;
}
}
#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 */
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
*/
/*
* 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
* 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
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
Assert(temporary_files_allowed); /* check temp file access is up */
- ResourceOwnerEnlargeFiles(CurrentResourceOwner);
+ ResourceOwnerEnlarge(CurrentResourceOwner);
file = PathNameOpenFile(path, mode | PG_BINARY);
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));
+}
#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
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.
*
dsm_unpin_mapping(dsm_segment *seg)
{
Assert(seg->resowner == NULL);
- ResourceOwnerEnlargeDSMs(CurrentResourceOwner);
+ ResourceOwnerEnlarge(CurrentResourceOwner);
seg->resowner = CurrentResourceOwner;
ResourceOwnerRememberDSM(seg->resowner, seg);
}
dsm_segment *seg;
if (CurrentResourceOwner)
- ResourceOwnerEnlargeDSMs(CurrentResourceOwner);
+ ResourceOwnerEnlarge(CurrentResourceOwner);
seg = MemoryContextAlloc(TopMemoryContext, sizeof(dsm_segment));
dlist_push_head(&dsm_segment_list, &seg->node);
{
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));
+}
#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 */
#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"
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,
* 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
*/
if (!ct->negative)
{
- ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner);
+ ResourceOwnerEnlarge(CurrentResourceOwner);
ct->refcount++;
ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple);
hashValue, hashIndex,
false);
/* immediately set the refcount to 1 */
- ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner);
+ ResourceOwnerEnlarge(CurrentResourceOwner);
ct->refcount++;
ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple);
break; /* assume only one match */
*/
void
ReleaseCatCache(HeapTuple tuple)
+{
+ ReleaseCatCacheWithOwner(tuple, CurrentResourceOwner);
+}
+
+static void
+ReleaseCatCacheWithOwner(HeapTuple tuple, ResourceOwner resowner)
{
CatCTup *ct = (CatCTup *) (((char *) tuple) -
offsetof(CatCTup, tuple));
Assert(ct->refcount > 0);
ct->refcount--;
- ResourceOwnerForgetCatCacheRef(CurrentResourceOwner, &ct->tuple);
+ if (resowner)
+ ResourceOwnerForgetCatCacheRef(CurrentResourceOwner, &ct->tuple);
if (
#ifndef CATCACHE_FORCE_RELEASE
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);
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);
*/
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
}
}
+/* 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);
}
#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"
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;
/* Flag the plan as in use by caller */
if (owner)
- ResourceOwnerEnlargePlanCacheRefs(owner);
+ ResourceOwnerEnlarge(owner);
plan->refcount++;
if (owner)
ResourceOwnerRememberPlanCacheRef(owner, plan);
/* Bump refcount if requested. */
if (owner)
{
- ResourceOwnerEnlargePlanCacheRefs(owner);
+ ResourceOwnerEnlarge(owner);
plan->refcount++;
ResourceOwnerRememberPlanCacheRef(owner, plan);
}
/* It's still good. Bump refcount if requested. */
if (owner)
{
- ResourceOwnerEnlargePlanCacheRefs(owner);
+ ResourceOwnerEnlarge(owner);
plan->refcount++;
ResourceOwnerRememberPlanCacheRef(owner, plan);
}
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);
+}
#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"
/* non-export function prototypes */
+static void RelationCloseCleanup(Relation relation);
static void RelationDestroyRelation(Relation relation, bool remember_tupdesc);
static void RelationClearRelation(Relation relation, bool rebuild);
* ----------------------------------------------------------------
*/
+/* 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.
void
RelationIncrementReferenceCount(Relation rel)
{
- ResourceOwnerEnlargeRelationRefs(CurrentResourceOwner);
+ ResourceOwnerEnlarge(CurrentResourceOwner);
rel->rd_refcnt += 1;
if (!IsBootstrapProcessingMode())
ResourceOwnerRememberRelationRef(CurrentResourceOwner, rel);
/* 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
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);
+}
as query parsing) when no associated Portal exists yet.
-API Overview
-------------
+Usage
+-----
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
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.
* 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
*/
#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"
-
-
-/*
- * All resource IDs 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))
+#include "utils/resowner.h"
/*
- * 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.
+ * ResourceElem represents a reference associated with a resource owner.
*
- * In either mode, lastidx remembers the location of the last item inserted
- * or returned by GetAny; this speeds up searches in ResourceArrayRemove.
+ * All objects managed by this code are required to fit into a Datum,
+ * which is fine since they are generally pointers or integers.
*/
-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.
+ * How many items may be stored in a hash table of given capacity. When this
+ * number is reached, we must resize.
+ *
+ * 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 RESARRAY_MAX_ITEMS(capacity) \
- ((capacity) <= RESARRAY_MAX_ARRAY ? (capacity) : (capacity)/4 * 3)
+#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");
/*
- * 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.
- *
- * MAX_RESOWNER_LOCKS is the size of the per-resource owner cache. It's
+ * 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
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 */
-
- /* We can remember up to MAX_RESOWNER_LOCKS references to local locks. */
- int nlocks; /* number of owned locks */
+ /*
+ * 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? */
+
+ /*
+ * 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;
/*****************************************************************************
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
*/
/* 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)
+ResourceOwnerAddToHash(ResourceOwner owner, Datum value, const ResourceOwnerDesc *kind)
{
- 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;
+ uint32 mask = owner->capacity - 1;
+ uint32 idx;
- /* 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;
+ Assert(kind != NULL);
- if (olditemsarr != NULL)
+ /* Insert into first free slot at or after hash location. */
+ idx = hash_resource_elem(value, kind) & mask;
+ for (;;)
{
- /*
- * 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);
+ if (owner->hash[idx].kind == NULL)
+ break; /* found a free slot */
+ idx = (idx + 1) & mask;
}
-
- Assert(resarr->nitems < resarr->maxitems);
+ owner->hash[idx].item = value;
+ owner->hash[idx].kind = kind;
+ owner->nhash++;
}
/*
- * Add a resource to ResourceArray
- *
- * Caller must have previously done ResourceArrayEnlarge()
+ * Comparison function to sort by release phase and priority
*/
-static void
-ResourceArrayAdd(ResourceArray *resarr, Datum value)
+static int
+resource_priority_cmp(const void *a, const void *b)
{
- uint32 idx;
+ const ResourceElem *ra = (const ResourceElem *) a;
+ const ResourceElem *rb = (const ResourceElem *) b;
- Assert(value != resarr->invalidval);
- Assert(resarr->nitems < resarr->maxitems);
-
- if (RESARRAY_IS_ARRAY(resarr))
+ /* Note: reverse order */
+ if (ra->kind->release_phase == rb->kind->release_phase)
{
- /* Append to linear array. */
- idx = resarr->nitems;
+ 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
- {
- /* 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++;
+ return 1;
}
/*
- * Remove a resource from ResourceArray
+ * Sort resources in reverse release priority.
*
- * Returns true on success, false if resource was not found.
- *
- * Note: if same resource ID appears more than once, one instance is removed.
+ * 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 bool
-ResourceArrayRemove(ResourceArray *resarr, Datum value)
+static void
+ResourceOwnerSort(ResourceOwner owner)
{
- uint32 i,
- idx,
- lastidx = resarr->lastidx;
+ ResourceElem *items;
+ uint32 nitems;
- Assert(value != resarr->invalidval);
-
- /* Search through all items, but try lastidx first. */
- if (RESARRAY_IS_ARRAY(resarr))
+ if (owner->nhash == 0)
{
- 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;
- }
- }
+ items = owner->arr;
+ nitems = owner->narr;
}
else
{
- uint32 mask = resarr->capacity - 1;
+ /*
+ * Compact the hash table, so that all the elements are in the
+ * beginning of the 'hash' array, with no empty elements.
+ */
+ uint32 dst = 0;
- 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++)
+ for (int idx = 0; idx < owner->capacity; idx++)
{
- if (resarr->itemsarr[idx] == value)
+ if (owner->hash[idx].kind != NULL)
{
- resarr->itemsarr[idx] = resarr->invalidval;
- resarr->nitems--;
- return true;
+ if (dst != idx)
+ owner->hash[dst] = owner->hash[idx];
+ dst++;
}
- idx = (idx + 1) & mask;
}
+
+ /*
+ * 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;
}
- return false;
+ qsort(items, nitems, sizeof(ResourceElem), resource_priority_cmp);
}
/*
- * Get any convenient entry in a ResourceArray.
- *
- * "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.
+ * Call the ReleaseResource callback on entries with given 'phase'.
*/
-static bool
-ResourceArrayGetAny(ResourceArray *resarr, Datum *value)
+static void
+ResourceOwnerReleaseAll(ResourceOwner owner, ResourceReleasePhase phase,
+ bool printLeakWarnings)
{
- if (resarr->nitems == 0)
- return false;
+ ResourceElem *items;
+ uint32 nitems;
- if (RESARRAY_IS_ARRAY(resarr))
+ /* ResourceOwnerSort must've been called already */
+ Assert(owner->releasing);
+ Assert(owner->sorted);
+ if (!owner->hash)
{
- /* Linear array: just return the first element. */
- resarr->lastidx = 0;
+ items = owner->arr;
+ nitems = owner->narr;
}
else
{
- /* Hash: search forward from wherever we were last. */
- uint32 mask = resarr->capacity - 1;
+ 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);
- for (;;)
+ if (printLeakWarnings)
{
- resarr->lastidx &= mask;
- if (resarr->itemsarr[resarr->lastidx] != resarr->invalidval)
- break;
- resarr->lastidx++;
+ 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--;
}
-
- *value = resarr->itemsarr[resarr->lastidx];
- return true;
-}
-
-/*
- * Trash a ResourceArray (we don't care about its state after this)
- */
-static void
-ResourceArrayFree(ResourceArray *resarr)
-{
- if (resarr->itemsarr)
- pfree(resarr->itemsarr);
+ if (!owner->hash)
+ owner->narr = nitems;
+ else
+ owner->nhash = nitems;
}
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,
* 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,
{
/* 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
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.
*/
- save = CurrentResourceOwner;
- CurrentResourceOwner = owner;
-
- if (phase == RESOURCE_RELEASE_BEFORE_LOCKS)
+ if (!owner->releasing)
+ {
+ Assert(phase == RESOURCE_RELEASE_BEFORE_LOCKS);
+ Assert(!owner->sorted);
+ owner->releasing = true;
+ }
+ else
{
/*
- * Abort failed buffer IO. AbortBufferIO()->TerminateBufferIO() calls
- * ResourceOwnerForgetBufferIO(), so we just have to iterate till
- * there are none.
- *
- * Needs to be before we release buffer pins.
- *
- * During a commit, there shouldn't be any in-progress IO.
+ * 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.
*/
- while (ResourceArrayGetAny(&(owner->bufferioarr), &foundres))
- {
- Buffer res = DatumGetBuffer(foundres);
+ }
+ if (!owner->sorted)
+ {
+ ResourceOwnerSort(owner);
+ owner->sorted = true;
+ }
- if (isCommit)
- elog(PANIC, "lost track of buffer IO on buffer %d", res);
- AbortBufferIO(res);
- }
+ /*
+ * Make CurrentResourceOwner point to me, so that the release callback
+ * functions know which resource owner is been released.
+ */
+ save = CurrentResourceOwner;
+ CurrentResourceOwner = owner;
+ if (phase == RESOURCE_RELEASE_BEFORE_LOCKS)
+ {
/*
- * Release buffer pins. Note that ReleaseBuffer will remove the
- * buffer entry from our array, so we just have to iterate till there
- * are none.
+ * Release all resources that need to be released before the locks.
*
- * 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.
+ * 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->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)
{
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);
+ ResourceOwnerReleaseAll(owner, phase, isCommit);
+ }
- if (isCommit)
- PrintCatCacheLeakWarning(res);
- ReleaseCatCache(res);
- }
+ /* Let add-on modules get a chance too */
+ for (item = ResourceRelease_callbacks; item; item = next)
+ {
+ /* allow callbacks to unregister themselves when called */
+ next = item->next;
+ item->callback(phase, isCommit, isTopLevel, item->arg);
+ }
- /* Ditto for catcache lists */
- while (ResourceArrayGetAny(&(owner->catlistrefarr), &foundres))
- {
- CatCList *res = (CatCList *) DatumGetPointer(foundres);
+ CurrentResourceOwner = save;
+}
- if (isCommit)
- PrintCatCacheListLeakWarning(res);
- ReleaseCatCacheList(res);
- }
+/*
+ * ResourceOwnerReleaseAllOfKind
+ * Release all resources of a certain type held by this owner.
+ */
+void
+ResourceOwnerReleaseAllOfKind(ResourceOwner owner, const ResourceOwnerDesc *kind)
+{
+ /* 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);
- /* Ditto for plancache references */
- while (ResourceArrayGetAny(&(owner->planrefarr), &foundres))
- {
- CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres);
-
- if (isCommit)
- PrintPlanCacheLeakWarning(res);
- ReleaseCachedPlan(res, owner);
- }
+ /*
+ * 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;
- /* Ditto for tupdesc references */
- while (ResourceArrayGetAny(&(owner->tupdescarr), &foundres))
+ /* Array first */
+ for (int i = 0; i < owner->narr; i++)
+ {
+ if (owner->arr[i].kind == kind)
{
- TupleDesc res = (TupleDesc) DatumGetPointer(foundres);
-
- if (isCommit)
- PrintTupleDescLeakWarning(res);
- DecrTupleDescRefCount(res);
- }
+ Datum value = owner->arr[i].item;
- /* Ditto for snapshot references */
- while (ResourceArrayGetAny(&(owner->snapshotarr), &foundres))
- {
- Snapshot res = (Snapshot) DatumGetPointer(foundres);
+ owner->arr[i] = owner->arr[owner->narr - 1];
+ owner->narr--;
+ i--;
- 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);
+ kind->ReleaseResource(value);
}
}
- /* Let add-on modules get a chance too */
- for (item = ResourceRelease_callbacks; item; item = next)
+ /* Then hash */
+ for (int i = 0; i < owner->capacity; i++)
{
- /* allow callbacks to unregister themselves when called */
- next = item->next;
- item->callback(phase, isCommit, isTopLevel, item->arg);
- }
-
- CurrentResourceOwner = save;
-}
-
-/*
- * 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.
- */
-void
-ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner)
-{
- Datum foundres;
+ if (owner->hash[i].kind == kind)
+ {
+ Datum value = owner->hash[i].item;
- while (ResourceArrayGetAny(&(owner->planrefarr), &foundres))
- {
- CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres);
+ owner->hash[i].item = (Datum) 0;
+ owner->hash[i].kind = NULL;
+ owner->nhash--;
- ReleaseCachedPlan(res, owner);
+ kind->ReleaseResource(value);
+ }
}
+ owner->releasing = false;
}
/*
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);
/*
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);
}
/*
* 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)
ResourceOwnerRelease(AuxProcessResourceOwner,
RESOURCE_RELEASE_AFTER_LOCKS,
isCommit, true);
+ /* allow it to be reused */
+ AuxProcessResourceOwner->releasing = false;
+ AuxProcessResourceOwner->sorted = false;
}
/*
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.
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));
-}
#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"
#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"
/* 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.
*
snap = snapshot->copied ? snapshot : CopySnapshot(snapshot);
/* and tell resowner.c about it */
- ResourceOwnerEnlargeSnapshots(owner);
+ ResourceOwnerEnlarge(owner);
snap->regd_count++;
ResourceOwnerRememberSnapshot(owner, snap);
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);
return false;
}
+
+/* ResourceOwner callbacks */
+
+static void
+ResOwnerReleaseSnapshot(Datum res)
+{
+ UnregisterSnapshotNoOwner((Snapshot) DatumGetPointer(res));
+}
#ifndef FRONTEND
#include "utils/memutils.h"
#include "utils/resowner.h"
-#include "utils/resowner_private.h"
#endif
/*
#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)
{
* allocation to avoid leaking.
*/
#ifndef FRONTEND
- ResourceOwnerEnlargeCryptoHash(CurrentResourceOwner);
+ ResourceOwnerEnlarge(CurrentResourceOwner);
#endif
ctx = ALLOC(sizeof(pg_cryptohash_ctx));
#ifndef FRONTEND
ctx->resowner = CurrentResourceOwner;
- ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
- PointerGetDatum(ctx));
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner, ctx);
#endif
return 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));
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
#ifndef FRONTEND
#include "utils/memutils.h"
#include "utils/resowner.h"
-#include "utils/resowner_private.h"
#endif
/*
#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)
{
ERR_clear_error();
#ifdef HAVE_HMAC_CTX_NEW
#ifndef FRONTEND
- ResourceOwnerEnlargeHMAC(CurrentResourceOwner);
+ ResourceOwnerEnlarge(CurrentResourceOwner);
#endif
ctx->hmacctx = HMAC_CTX_new();
#else
#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));
#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));
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
#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.
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
*/
/* 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);
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,
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 */
HeapTuple newtuple,
void (*function) (int, uint32, Oid));
-extern void PrintCatCacheLeakWarning(HeapTuple tuple);
-extern void PrintCatCacheListLeakWarning(CatCList *list);
-
#endif /* CATCACHE_H */
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);
/*
* 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.
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 */
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 ||
/* Be sure to release the procedure resowner if any */
if (procedure_resowner)
{
- ResourceOwnerReleaseAllPlanCacheRefs(procedure_resowner);
+ ReleaseAllPlanCacheRefsInOwner(procedure_resowner);
ResourceOwnerDelete(procedure_resowner);
}
}
/* 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 ... */
/* 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 ... */
test_predtest \
test_rbtree \
test_regex \
+ test_resowner \
test_rls_hooks \
test_shm_mq \
test_slru \
subdir('test_predtest')
subdir('test_rbtree')
subdir('test_regex')
+subdir('test_resowner')
subdir('test_rls_hooks')
subdir('test_shm_mq')
subdir('test_slru')
--- /dev/null
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
--- /dev/null
+# 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
--- /dev/null
+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;
--- /dev/null
+# 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',
+ ],
+ },
+}
--- /dev/null
+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;
--- /dev/null
+/* 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;
--- /dev/null
+comment = 'Test code for ResourceOwners'
+default_version = '1.0'
+module_pathname = '$libdir/test_resowner'
+relocatable = true
--- /dev/null
+/*--------------------------------------------------------------------------
+ *
+ * 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();
+}
--- /dev/null
+/*--------------------------------------------------------------------------
+ *
+ * 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();
+}
MVDependency
MVNDistinct
MVNDistinctItem
+ManyTestResource
+ManyTestResourceKind
Material
MaterialPath
MaterialState
ResTarget
ReservoirState
ReservoirStateData
-ResourceArray
+ResourceElem
ResourceOwner
+ResourceOwnerData
+ResourceOwnerDesc
ResourceReleaseCallback
ResourceReleaseCallbackItem
ResourceReleasePhase