public inbox for [email protected]help / color / mirror / Atom feed
[PATCH v6 2/3] Make resowners more easily extensible. 2+ messages / 2 participants [nested] [flat]
* [PATCH v6 2/3] Make resowners more easily extensible. @ 2021-01-20 21:16 Heikki Linnakangas <[email protected]> 0 siblings, 0 replies; 2+ messages in thread From: Heikki Linnakangas @ 2021-01-20 21:16 UTC (permalink / raw) Use a single array and hash, instead of one for each object kind. --- src/backend/access/common/tupdesc.c | 42 +- src/backend/jit/jit.c | 2 - src/backend/jit/llvm/llvmjit.c | 46 +- src/backend/storage/buffer/bufmgr.c | 45 +- src/backend/storage/buffer/localbuf.c | 4 +- src/backend/storage/file/fd.c | 44 +- src/backend/storage/ipc/dsm.c | 44 +- src/backend/storage/lmgr/lock.c | 2 +- src/backend/utils/cache/catcache.c | 74 +- src/backend/utils/cache/plancache.c | 45 +- src/backend/utils/cache/relcache.c | 41 +- src/backend/utils/resowner/README | 19 +- src/backend/utils/resowner/resowner.c | 1236 +++++++------------------ src/backend/utils/time/snapmgr.c | 38 +- src/common/cryptohash_openssl.c | 49 +- src/include/storage/buf_internals.h | 9 + src/include/utils/catcache.h | 3 - src/include/utils/plancache.h | 2 + src/include/utils/resowner.h | 45 +- src/include/utils/resowner_private.h | 105 --- src/tools/pgindent/typedefs.list | 4 +- 21 files changed, 844 insertions(+), 1055 deletions(-) delete mode 100644 src/include/utils/resowner_private.h diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c index 902f59440cd..2bf0c121fda 100644 --- a/src/backend/access/common/tupdesc.c +++ b/src/backend/access/common/tupdesc.c @@ -29,9 +29,27 @@ #include "utils/acl.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 void ResOwnerPrintTupleDescLeakWarning(Datum res); + +static ResourceOwnerFuncs tupdesc_resowner_funcs = +{ + /* relcache references */ + .name = "tupdesc reference", + .phase = RESOURCE_RELEASE_AFTER_LOCKS, + .ReleaseResource = ResOwnerReleaseTupleDesc, + .PrintLeakWarning = ResOwnerPrintTupleDescLeakWarning +}; + +/* Convenience wrappers over ResourceOwnerRemember/Forget */ +#define ResourceOwnerRememberTupleDesc(owner, tupdesc) \ + ResourceOwnerRemember(owner, PointerGetDatum(tupdesc), &tupdesc_resowner_funcs) +#define ResourceOwnerForgetTupleDesc(owner, tupdesc) \ + ResourceOwnerForget(owner, PointerGetDatum(tupdesc), &tupdesc_resowner_funcs) /* * CreateTemplateTupleDesc @@ -376,7 +394,7 @@ IncrTupleDescRefCount(TupleDesc tupdesc) { Assert(tupdesc->tdrefcount >= 0); - ResourceOwnerEnlargeTupleDescs(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); tupdesc->tdrefcount++; ResourceOwnerRememberTupleDesc(CurrentResourceOwner, tupdesc); } @@ -925,3 +943,23 @@ BuildDescFromLists(List *names, List *types, List *typmods, List *collations) return desc; } + + +/* + * ResourceOwner callbacks + */ +static void +ResOwnerReleaseTupleDesc(Datum res) +{ + DecrTupleDescRefCount((TupleDesc) DatumGetPointer(res)); +} + +static void +ResOwnerPrintTupleDescLeakWarning(Datum res) +{ + TupleDesc tupdesc = (TupleDesc) DatumGetPointer(res); + + elog(WARNING, + "TupleDesc reference leak: TupleDesc %p (%u,%d) still referenced", + tupdesc, tupdesc->tdtypeid, tupdesc->tdtypmod); +} diff --git a/src/backend/jit/jit.c b/src/backend/jit/jit.c index 2da300e000d..1aa04d173b4 100644 --- a/src/backend/jit/jit.c +++ b/src/backend/jit/jit.c @@ -26,7 +26,6 @@ #include "jit/jit.h" #include "miscadmin.h" #include "utils/fmgrprotos.h" -#include "utils/resowner_private.h" /* GUCs */ bool jit_enabled = true; @@ -140,7 +139,6 @@ jit_release_context(JitContext *context) if (provider_successfully_loaded) provider.release_context(context); - ResourceOwnerForgetJIT(context->resowner, PointerGetDatum(context)); pfree(context); } diff --git a/src/backend/jit/llvm/llvmjit.c b/src/backend/jit/llvm/llvmjit.c index b0789a5fb80..8e5b86a0e5b 100644 --- a/src/backend/jit/llvm/llvmjit.c +++ b/src/backend/jit/llvm/llvmjit.c @@ -40,7 +40,7 @@ #include "portability/instr_time.h" #include "storage/ipc.h" #include "utils/memutils.h" -#include "utils/resowner_private.h" +#include "utils/resowner.h" /* Handle of a module emitted via ORC JIT */ typedef struct LLVMJitHandle @@ -121,8 +121,26 @@ static LLVMOrcLLJITRef llvm_create_jit_instance(LLVMTargetMachineRef tm); static char *llvm_error_message(LLVMErrorRef error); #endif /* LLVM_VERSION_MAJOR > 11 */ -PG_MODULE_MAGIC; +/* ResourceOwner callbacks to hold JitContexts */ +static void ResOwnerReleaseJitContext(Datum res); +static void ResOwnerPrintJitContextLeakWarning(Datum res); + +static ResourceOwnerFuncs jit_resowner_funcs = +{ + /* relcache references */ + .name = "LLVM JIT context", + .phase = RESOURCE_RELEASE_BEFORE_LOCKS, + .ReleaseResource = ResOwnerReleaseJitContext, + .PrintLeakWarning = ResOwnerPrintJitContextLeakWarning +}; + +/* Convenience wrappers over ResourceOwnerRemember/Forget */ +#define ResourceOwnerRememberJIT(owner, handle) \ + ResourceOwnerRemember(owner, PointerGetDatum(handle), &jit_resowner_funcs) +#define ResourceOwnerForgetJIT(owner, handle) \ + ResourceOwnerForget(owner, PointerGetDatum(handle), &jit_resowner_funcs) +PG_MODULE_MAGIC; /* * Initialize LLVM JIT provider. @@ -151,7 +169,7 @@ llvm_create_context(int jitFlags) llvm_session_initialize(); - ResourceOwnerEnlargeJIT(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); context = MemoryContextAllocZero(TopMemoryContext, sizeof(LLVMJitContext)); @@ -159,7 +177,7 @@ llvm_create_context(int jitFlags) /* ensure cleanup */ context->base.resowner = CurrentResourceOwner; - ResourceOwnerRememberJIT(CurrentResourceOwner, PointerGetDatum(context)); + ResourceOwnerRememberJIT(CurrentResourceOwner, context); return context; } @@ -221,6 +239,8 @@ llvm_release_context(JitContext *context) pfree(jit_handle); } + + ResourceOwnerForgetJIT(context->resowner, context); } /* @@ -1231,3 +1251,21 @@ llvm_error_message(LLVMErrorRef error) } #endif /* LLVM_VERSION_MAJOR > 11 */ + +/* + * ResourceOwner callbacks + */ +static void +ResOwnerReleaseJitContext(Datum res) +{ + jit_release_context((JitContext *) PointerGetDatum(res)); +} + +static void +ResOwnerPrintJitContextLeakWarning(Datum res) +{ + /* XXX: We used to not print these. Was that intentional? */ + JitContext *context = (JitContext *) PointerGetDatum(res); + + elog(WARNING, "JIT context leak: context %p still referenced", context); +} diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index cde869e7d64..82e39de19f9 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -52,7 +52,7 @@ #include "utils/memdebug.h" #include "utils/ps_status.h" #include "utils/rel.h" -#include "utils/resowner_private.h" +#include "utils/resowner.h" #include "utils/timestamp.h" @@ -206,6 +206,18 @@ static PrivateRefCountEntry *GetPrivateRefCountEntry(Buffer buffer, bool do_move static inline int32 GetPrivateRefCount(Buffer buffer); static void ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref); +/* ResourceOwner callbacks to hold buffer pins */ +static void ResOwnerReleaseBuffer(Datum res); +static void ResOwnerPrintBufferLeakWarning(Datum res); + +ResourceOwnerFuncs buffer_resowner_funcs = +{ + .name = "buffer", + .phase = RESOURCE_RELEASE_BEFORE_LOCKS, + .ReleaseResource = ResOwnerReleaseBuffer, + .PrintLeakWarning = ResOwnerPrintBufferLeakWarning +}; + /* * Ensure that the PrivateRefCountArray has sufficient space to store one more * entry. This has to be called before using NewPrivateRefCountEntry() to fill @@ -1094,7 +1106,7 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum, * pin. */ ReservePrivateRefCountEntry(); - ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); /* * Select a victim buffer. The buffer is returned with its header @@ -1604,7 +1616,7 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy) bool result; PrivateRefCountEntry *ref; - ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); ref = GetPrivateRefCountEntry(b, true); @@ -1687,7 +1699,7 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy) * * As this function is called with the spinlock held, the caller has to * previously call ReservePrivateRefCountEntry() and - * ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + * ResourceOwnerEnlarge(CurrentResourceOwner); * * Currently, no callers of this function want to modify the buffer's * usage_count at all, so there's no need for a strategy parameter. @@ -2423,7 +2435,7 @@ SyncOneBuffer(int buf_id, bool skip_recently_used, WritebackContext *wb_context) /* Make sure we can handle the pin */ ReservePrivateRefCountEntry(); - ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); /* * Check whether buffer needs writing. @@ -3496,7 +3508,7 @@ FlushRelationBuffers(Relation rel) /* Make sure we can handle the pin */ ReservePrivateRefCountEntry(); - ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); buf_state = LockBufHdr(bufHdr); if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) && @@ -3591,7 +3603,7 @@ FlushRelationsAllBuffers(SMgrRelation *smgrs, int nrels) /* Make sure we can handle the pin */ ReservePrivateRefCountEntry(); - ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); buf_state = LockBufHdr(bufHdr); if (RelFileNodeEquals(bufHdr->tag.rnode, srelent->rnode) && @@ -3646,7 +3658,7 @@ FlushDatabaseBuffers(Oid dbid) /* Make sure we can handle the pin */ ReservePrivateRefCountEntry(); - ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); buf_state = LockBufHdr(bufHdr); if (bufHdr->tag.rnode.dbNode == dbid && @@ -3729,7 +3741,7 @@ void IncrBufferRefCount(Buffer buffer) { Assert(BufferIsPinned(buffer)); - ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); if (BufferIsLocal(buffer)) LocalRefCount[-buffer - 1]++; else @@ -4803,3 +4815,18 @@ TestForOldSnapshot_impl(Snapshot snapshot, Relation relation) (errcode(ERRCODE_SNAPSHOT_TOO_OLD), errmsg("snapshot too old"))); } + +/* + * ResourceOwner callbacks + */ +static void +ResOwnerReleaseBuffer(Datum res) +{ + ReleaseBuffer(DatumGetInt32(res)); +} + +static void +ResOwnerPrintBufferLeakWarning(Datum res) +{ + PrintBufferLeakWarning(DatumGetInt32(res)); +} diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c index f7c15ea8a44..3f22d7127b9 100644 --- a/src/backend/storage/buffer/localbuf.c +++ b/src/backend/storage/buffer/localbuf.c @@ -22,7 +22,7 @@ #include "storage/bufmgr.h" #include "utils/guc.h" #include "utils/memutils.h" -#include "utils/resowner_private.h" +#include "utils/resowner.h" /*#define LBDEBUG*/ @@ -124,7 +124,7 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum, InitLocalBuffers(); /* Make sure we will have room to remember the buffer pin */ - ResourceOwnerEnlargeBuffers(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); /* See if the desired buffer already exists */ hresult = (LocalBufferLookupEnt *) diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c index b58502837aa..1172ea245e0 100644 --- a/src/backend/storage/file/fd.c +++ b/src/backend/storage/file/fd.c @@ -97,7 +97,7 @@ #include "storage/fd.h" #include "storage/ipc.h" #include "utils/guc.h" -#include "utils/resowner_private.h" +#include "utils/resowner.h" /* Define PG_FLUSH_DATA_WORKS if we have an implementation for pg_flush_data */ #if defined(HAVE_SYNC_FILE_RANGE) @@ -340,6 +340,24 @@ static void unlink_if_exists_fname(const char *fname, bool isdir, int elevel); static int fsync_parent_path(const char *fname, int elevel); +/* ResourceOwner callbacks to hold virtual file descriptors */ +static void ResOwnerReleaseFile(Datum res); +static void ResOwnerPrintFileLeakWarning(Datum res); + +static ResourceOwnerFuncs file_resowner_funcs = +{ + .name = "File", + .phase = RESOURCE_RELEASE_AFTER_LOCKS, + .ReleaseResource = ResOwnerReleaseFile, + .PrintLeakWarning = ResOwnerPrintFileLeakWarning +}; + +/* Convenience wrappers over ResourceOwnerRemember/Forget */ +#define ResourceOwnerRememberFile(owner, file) \ + ResourceOwnerRemember(owner, Int32GetDatum(file), &file_resowner_funcs) +#define ResourceOwnerForgetFile(owner, file) \ + ResourceOwnerForget(owner, Int32GetDatum(file), &file_resowner_funcs) + /* * pg_fsync --- do fsync with or without writethrough */ @@ -1430,7 +1448,7 @@ ReportTemporaryFileUsage(const char *path, off_t size) /* * Called to register a temporary file for automatic close. - * ResourceOwnerEnlargeFiles(CurrentResourceOwner) must have been called + * ResourceOwnerEnlarge(CurrentResourceOwner) must have been called * before the file was opened. */ static void @@ -1612,7 +1630,7 @@ OpenTemporaryFile(bool interXact) * open it, if we'll be registering it below. */ if (!interXact) - ResourceOwnerEnlargeFiles(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); /* * If some temp tablespace(s) have been given to us, try to use the next @@ -1742,7 +1760,7 @@ PathNameCreateTemporaryFile(const char *path, bool error_on_failure) { File file; - ResourceOwnerEnlargeFiles(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); /* * Open the file. Note: we don't use O_EXCL, in case there is an orphaned @@ -1780,7 +1798,7 @@ PathNameOpenTemporaryFile(const char *path, int mode) { File file; - ResourceOwnerEnlargeFiles(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); file = PathNameOpenFile(path, mode | PG_BINARY); @@ -3700,3 +3718,19 @@ pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset) return sum; } + +/* + * ResourceOwner callbacks + */ +static void +ResOwnerReleaseFile(Datum res) +{ + FileClose((File) DatumGetInt32(res)); +} + +static void +ResOwnerPrintFileLeakWarning(Datum res) +{ + elog(WARNING, "temporary file leak: File %d still referenced", + DatumGetInt32(res)); +} diff --git a/src/backend/storage/ipc/dsm.c b/src/backend/storage/ipc/dsm.c index ae82b4bdc0e..31e8860fb53 100644 --- a/src/backend/storage/ipc/dsm.c +++ b/src/backend/storage/ipc/dsm.c @@ -37,13 +37,15 @@ #include "miscadmin.h" #include "port/pg_bitutils.h" #include "storage/dsm.h" +#include "storage/fd.h" #include "storage/ipc.h" #include "storage/lwlock.h" #include "storage/pg_shmem.h" +#include "storage/shmem.h" #include "utils/freepage.h" #include "utils/guc.h" #include "utils/memutils.h" -#include "utils/resowner_private.h" +#include "utils/resowner.h" #define PG_DYNSHMEM_CONTROL_MAGIC 0x9a503d32 @@ -139,6 +141,25 @@ static dsm_control_header *dsm_control; static Size dsm_control_mapped_size = 0; static void *dsm_control_impl_private = NULL; + +/* ResourceOwner callbacks to hold DSM segments */ +static void ResOwnerReleaseDSM(Datum res); +static void ResOwnerPrintDSMLeakWarning(Datum res); + +static ResourceOwnerFuncs dsm_resowner_funcs = +{ + .name = "dynamic shared memory segment", + .phase = RESOURCE_RELEASE_BEFORE_LOCKS, + .ReleaseResource = ResOwnerReleaseDSM, + .PrintLeakWarning = ResOwnerPrintDSMLeakWarning +}; + +/* Convenience wrappers over ResourceOwnerRemember/Forget */ +#define ResourceOwnerRememberDSM(owner, seg) \ + ResourceOwnerRemember(owner, PointerGetDatum(seg), &dsm_resowner_funcs) +#define ResourceOwnerForgetDSM(owner, seg) \ + ResourceOwnerForget(owner, PointerGetDatum(seg), &dsm_resowner_funcs) + /* * Start up the dynamic shared memory system. * @@ -895,7 +916,7 @@ void dsm_unpin_mapping(dsm_segment *seg) { Assert(seg->resowner == NULL); - ResourceOwnerEnlargeDSMs(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); seg->resowner = CurrentResourceOwner; ResourceOwnerRememberDSM(seg->resowner, seg); } @@ -1162,7 +1183,7 @@ dsm_create_descriptor(void) dsm_segment *seg; if (CurrentResourceOwner) - ResourceOwnerEnlargeDSMs(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); seg = MemoryContextAlloc(TopMemoryContext, sizeof(dsm_segment)); dlist_push_head(&dsm_segment_list, &seg->node); @@ -1241,3 +1262,20 @@ is_main_region_dsm_handle(dsm_handle handle) { return handle & 1; } + +/* + * ResourceOwner callbacks + */ +static void +ResOwnerReleaseDSM(Datum res) +{ + dsm_detach((dsm_segment *) DatumGetPointer(res)); +} +static void +ResOwnerPrintDSMLeakWarning(Datum res) +{ + dsm_segment *seg = (dsm_segment *) res; + + elog(WARNING, "dynamic shared memory leak: segment %u still referenced", + dsm_segment_handle(seg)); +} diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index 20e50247ea4..de39c844d49 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -47,7 +47,7 @@ #include "storage/standby.h" #include "utils/memutils.h" #include "utils/ps_status.h" -#include "utils/resowner_private.h" +#include "utils/resowner.h" /* This configuration variable is used to set the lock table size */ diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c index 86fdfc454d2..9f9db426274 100644 --- a/src/backend/utils/cache/catcache.c +++ b/src/backend/utils/cache/catcache.c @@ -31,12 +31,13 @@ #endif #include "storage/lmgr.h" #include "utils/builtins.h" +#include "utils/catcache.h" #include "utils/datum.h" #include "utils/fmgroids.h" #include "utils/inval.h" #include "utils/memutils.h" #include "utils/rel.h" -#include "utils/resowner_private.h" +#include "utils/resowner.h" #include "utils/syscache.h" @@ -104,6 +105,42 @@ static void CatCacheCopyKeys(TupleDesc tupdesc, int nkeys, int *attnos, * internal support functions */ +/* ResourceOwner callbacks to hold catcache references */ + +static void ResOwnerReleaseCatCache(Datum res); +static void ResOwnerPrintCatCacheLeakWarning(Datum res); +static void ResOwnerReleaseCatCacheList(Datum res); +static void ResOwnerPrintCatCacheListLeakWarning(Datum res); + +static ResourceOwnerFuncs catcache_resowner_funcs = +{ + /* catcache references */ + .name = "catcache reference", + .phase = RESOURCE_RELEASE_AFTER_LOCKS, + .ReleaseResource = ResOwnerReleaseCatCache, + .PrintLeakWarning = ResOwnerPrintCatCacheLeakWarning +}; + +static ResourceOwnerFuncs catlistref_resowner_funcs = +{ + /* catcache-list pins */ + .name = "catcache list reference", + .phase = RESOURCE_RELEASE_AFTER_LOCKS, + .ReleaseResource = ResOwnerReleaseCatCacheList, + .PrintLeakWarning = ResOwnerPrintCatCacheListLeakWarning +}; + +/* Convenience wrappers over ResourceOwnerRemember/Forget */ +#define ResourceOwnerRememberCatCacheRef(owner, tuple) \ + ResourceOwnerRemember(owner, PointerGetDatum(tuple), &catcache_resowner_funcs) +#define ResourceOwnerForgetCatCacheRef(owner, tuple) \ + ResourceOwnerForget(owner, PointerGetDatum(tuple), &catcache_resowner_funcs) +#define ResourceOwnerRememberCatCacheListRef(owner, list) \ + ResourceOwnerRemember(owner, PointerGetDatum(list), &catlistref_resowner_funcs) +#define ResourceOwnerForgetCatCacheListRef(owner, list) \ + ResourceOwnerForget(owner, PointerGetDatum(list), &catlistref_resowner_funcs) + + /* * 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 @@ -1270,7 +1307,7 @@ SearchCatCacheInternal(CatCache *cache, */ if (!ct->negative) { - ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); ct->refcount++; ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple); @@ -1371,7 +1408,7 @@ SearchCatCacheMiss(CatCache *cache, hashValue, hashIndex, false); /* immediately set the refcount to 1 */ - ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); ct->refcount++; ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple); break; /* assume only one match */ @@ -1583,7 +1620,7 @@ SearchCatCacheList(CatCache *cache, dlist_move_head(&cache->cc_lists, &cl->cache_elem); /* Bump the list's refcount and return it */ - ResourceOwnerEnlargeCatCacheListRefs(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); cl->refcount++; ResourceOwnerRememberCatCacheListRef(CurrentResourceOwner, cl); @@ -1695,7 +1732,7 @@ SearchCatCacheList(CatCache *cache, table_close(relation, AccessShareLock); /* Make sure the resource owner has room to remember this entry. */ - ResourceOwnerEnlargeCatCacheListRefs(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); /* Now we can build the CatCList entry. */ oldcxt = MemoryContextSwitchTo(CacheMemoryContext); @@ -2069,14 +2106,19 @@ PrepareToInvalidateCacheTuple(Relation relation, } } - /* - * Subroutines for warning about reference leaks. These are exported so - * that resowner.c can call them. + * ResourceOwner callbacks */ -void -PrintCatCacheLeakWarning(HeapTuple tuple) +static void +ResOwnerReleaseCatCache(Datum res) +{ + ReleaseCatCache((HeapTuple) DatumGetPointer(res)); +} + +static void +ResOwnerPrintCatCacheLeakWarning(Datum res) { + HeapTuple tuple = (HeapTuple) DatumGetPointer(res); CatCTup *ct = (CatCTup *) (((char *) tuple) - offsetof(CatCTup, tuple)); @@ -2090,9 +2132,17 @@ PrintCatCacheLeakWarning(HeapTuple tuple) ct->refcount); } -void -PrintCatCacheListLeakWarning(CatCList *list) +static void +ResOwnerReleaseCatCacheList(Datum res) { + ReleaseCatCacheList((CatCList *) DatumGetPointer(res)); +} + +static void +ResOwnerPrintCatCacheListLeakWarning(Datum res) +{ + CatCList *list = (CatCList *) DatumGetPointer(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); diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c index cc04b5b4bef..ecc9296d2d3 100644 --- a/src/backend/utils/cache/plancache.c +++ b/src/backend/utils/cache/plancache.c @@ -69,7 +69,7 @@ #include "tcop/utility.h" #include "utils/inval.h" #include "utils/memutils.h" -#include "utils/resowner_private.h" +#include "utils/resowner.h" #include "utils/rls.h" #include "utils/snapmgr.h" #include "utils/syscache.h" @@ -115,6 +115,26 @@ static void PlanCacheRelCallback(Datum arg, Oid relid); static void PlanCacheObjectCallback(Datum arg, int cacheid, uint32 hashvalue); static void PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue); +/* ResourceOwner callbacks to track plancache references */ +static void ResOwnerReleaseCachedPlan(Datum res); +static void ResOwnerPrintPlanCacheLeakWarning(Datum res); + +/* this is exported for ResourceOwnerReleaseAllPlanCacheRefs() */ +ResourceOwnerFuncs planref_resowner_funcs = +{ + .name = "plancache reference", + .phase = RESOURCE_RELEASE_AFTER_LOCKS, + .ReleaseResource = ResOwnerReleaseCachedPlan, + .PrintLeakWarning = ResOwnerPrintPlanCacheLeakWarning +}; + +/* Convenience wrappers over ResourceOwnerRemember/Forget */ +#define ResourceOwnerRememberPlanCacheRef(owner, plan) \ + ResourceOwnerRemember(owner, PointerGetDatum(plan), &planref_resowner_funcs) +#define ResourceOwnerForgetPlanCacheRef(owner, plan) \ + ResourceOwnerForget(owner, PointerGetDatum(plan), &planref_resowner_funcs) + + /* GUC parameter */ int plan_cache_mode; @@ -1229,7 +1249,7 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams, /* Flag the plan as in use by caller */ if (useResOwner) - ResourceOwnerEnlargePlanCacheRefs(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); plan->refcount++; if (useResOwner) ResourceOwnerRememberPlanCacheRef(CurrentResourceOwner, plan); @@ -1392,7 +1412,7 @@ CachedPlanAllowsSimpleValidityCheck(CachedPlanSource *plansource, /* Bump refcount if requested. */ if (owner) { - ResourceOwnerEnlargePlanCacheRefs(owner); + ResourceOwnerEnlarge(owner); plan->refcount++; ResourceOwnerRememberPlanCacheRef(owner, plan); } @@ -1451,7 +1471,7 @@ CachedPlanIsSimplyValid(CachedPlanSource *plansource, CachedPlan *plan, /* It's still good. Bump refcount if requested. */ if (owner) { - ResourceOwnerEnlargePlanCacheRefs(owner); + ResourceOwnerEnlarge(owner); plan->refcount++; ResourceOwnerRememberPlanCacheRef(owner, plan); } @@ -2205,3 +2225,20 @@ ResetPlanCache(void) cexpr->is_valid = false; } } + +/* + * ResourceOwner callbacks + */ + +static void +ResOwnerReleaseCachedPlan(Datum res) +{ + ReleaseCachedPlan((CachedPlan *) DatumGetPointer(res), true); +} + +static void +ResOwnerPrintPlanCacheLeakWarning(Datum res) +{ + elog(WARNING, "plancache reference leak: plan %p not closed", + DatumGetPointer(res)); +} diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 7ef510cd01b..6ca5b50552b 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -78,13 +78,14 @@ #include "storage/smgr.h" #include "utils/array.h" #include "utils/builtins.h" +#include "utils/catcache.h" #include "utils/datum.h" #include "utils/fmgroids.h" #include "utils/inval.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/relmapper.h" -#include "utils/resowner_private.h" +#include "utils/resowner.h" #include "utils/snapmgr.h" #include "utils/syscache.h" @@ -2078,6 +2079,24 @@ RelationIdGetRelation(Oid relationId) * ---------------------------------------------------------------- */ +/* ResourceOwner callbacks to track relcache references */ +static void ResOwnerReleaseRelation(Datum res); +static void ResOwnerPrintRelCacheLeakWarning(Datum res); + +static ResourceOwnerFuncs relref_resowner_funcs = +{ + .name = "relcache reference", + .phase = RESOURCE_RELEASE_BEFORE_LOCKS, + .ReleaseResource = ResOwnerReleaseRelation, + .PrintLeakWarning = ResOwnerPrintRelCacheLeakWarning +}; + +/* Convenience wrappers over ResourceOwnerRemember/Forget */ +#define ResourceOwnerRememberRelationRef(owner, rel) \ + ResourceOwnerRemember(owner, PointerGetDatum(rel), &relref_resowner_funcs) +#define ResourceOwnerForgetRelationRef(owner, rel) \ + ResourceOwnerForget(owner, PointerGetDatum(rel), &relref_resowner_funcs) + /* * RelationIncrementReferenceCount * Increments relation reference count. @@ -2089,7 +2108,7 @@ RelationIdGetRelation(Oid relationId) void RelationIncrementReferenceCount(Relation rel) { - ResourceOwnerEnlargeRelationRefs(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); rel->rd_refcnt += 1; if (!IsBootstrapProcessingMode()) ResourceOwnerRememberRelationRef(CurrentResourceOwner, rel); @@ -6417,3 +6436,21 @@ unlink_initfile(const char *initfilename, int elevel) initfilename))); } } + +/* + * ResourceOwner callbacks + */ +static void +ResOwnerPrintRelCacheLeakWarning(Datum res) +{ + Relation rel = (Relation) res; + + elog(WARNING, "relcache reference leak: relation \"%s\" not closed", + RelationGetRelationName(rel)); +} + +static void +ResOwnerReleaseRelation(Datum res) +{ + RelationClose((Relation) res); +} diff --git a/src/backend/utils/resowner/README b/src/backend/utils/resowner/README index 2998f6bb362..4ed13afa101 100644 --- a/src/backend/utils/resowner/README +++ b/src/backend/utils/resowner/README @@ -60,13 +60,18 @@ subtransaction or portal. Therefore, the "release" operation on a child ResourceOwner transfers lock ownership to the parent instead of actually releasing the lock, if isCommit is true. -Currently, ResourceOwners contain direct support for recording ownership of -buffer pins, lmgr locks, and catcache, relcache, plancache, tupdesc, and -snapshot references. 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. +ResourceOwner can record ownership of many different kinds of resources. +As of this writing, it's used internally for buffer pins, lmgr locks, and +catcache, relcache, plancache, tupdesc, snapshot, DSM and JIT context +references. ResourceOwner treats all resources the same, and extensions +can define new kinds of resources by filling in a ResourceOwnerFuncs +struct with a suitable callback functions. + +There is also 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. This used to be the only way to register new kinds +of objects with a resource owner; nowadays it easier to write custom +ResourceOwnerFuncs callbacks. Whenever we are inside a transaction, the global variable CurrentResourceOwner shows which resource owner should be assigned diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c index 10f15f6a357..1db1e7cf14b 100644 --- a/src/backend/utils/resowner/resowner.c +++ b/src/backend/utils/resowner/resowner.c @@ -20,75 +20,45 @@ */ #include "postgres.h" -#include "common/cryptohash.h" #include "common/hashfn.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/plancache.h" +#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; + ResourceOwnerFuncs *kind; +} 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 small fixed-size array to hold most-recently remembered resources. */ -#define RESARRAY_INIT_SIZE 16 +#define RESOWNER_ARRAY_SIZE 8 /* - * When to switch to hashing vs. simple array logic in a ResourceArray. + * Initially allocated size of a ResourceOwner's hash. Must be power of two since + * we'll 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 32 /* - * How many items may be stored in a resource array of given capacity. + * How many items may be stored in a hash of given capacity. * When this number is reached, we must resize. */ -#define RESARRAY_MAX_ITEMS(capacity) \ - ((capacity) <= RESARRAY_MAX_ARRAY ? (capacity) : (capacity)/4 * 3) +#define RESOWNER_HASH_MAX_ITEMS(capacity) ((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 @@ -118,23 +88,33 @@ typedef struct ResourceOwnerData ResourceOwner nextchild; /* next child of same parent */ const char *name; /* name (just for debugging) */ - /* We have built-in support for remembering: */ - ResourceArray bufferarr; /* owned buffers */ - ResourceArray 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 */ + /* + * These structs keep track of the objects registered with this owner. + * + * We manage a small set of references by keeping them in a simple array. + * When the array gets full, all the elements in the array are moved to a + * 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. + */ + ResourceElem arr[RESOWNER_ARRAY_SIZE]; + uint32 narr; /* how many items are stored in the array */ + + /* + * 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. + */ + ResourceElem *hash; + uint32 nhash; /* how many items are stored in the hash */ + uint32 capacity; /* allocated length of hash[] */ + uint32 grow_at; /* grow hash when reach this */ /* We can remember up to MAX_RESOWNER_LOCKS references to local locks. */ int nlocks; /* number of owned locks */ LOCALLOCK *locks[MAX_RESOWNER_LOCKS]; /* list of owned locks */ -} ResourceOwnerData; +} ResourceOwnerData; /***************************************************************************** @@ -146,6 +126,18 @@ ResourceOwner CurTransactionResourceOwner = NULL; ResourceOwner TopTransactionResourceOwner = NULL; ResourceOwner AuxProcessResourceOwner = NULL; +/* #define RESOWNER_STATS */ +/* #define RESOWNER_TRACE */ + +#ifdef RESOWNER_STATS +static int narray_lookups = 0; +static int nhash_lookups = 0; +#endif + +#ifdef RESOWNER_TRACE +static int resowner_trace_counter = 0; +#endif + /* * List of add-on callbacks for resource releasing */ @@ -160,296 +152,315 @@ static ResourceReleaseCallbackItem *ResourceRelease_callbacks = NULL; /* Internal routines */ -static void ResourceArrayInit(ResourceArray *resarr, Datum invalidval); -static void ResourceArrayEnlarge(ResourceArray *resarr); -static void ResourceArrayAdd(ResourceArray *resarr, Datum value); -static bool ResourceArrayRemove(ResourceArray *resarr, Datum value); -static bool ResourceArrayGetAny(ResourceArray *resarr, Datum *value); -static void ResourceArrayFree(ResourceArray *resarr); static 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); /***************************************************************************** * INTERNAL ROUTINES * *****************************************************************************/ +static inline uint32 +hash_resource_elem(Datum value, ResourceOwnerFuncs *kind) +{ + Datum data[2]; + + data[0] = value; + data[1] = PointerGetDatum(kind); + + return hash_bytes((unsigned char *) &data, 2 * SIZEOF_DATUM); +} -/* - * Initialize a ResourceArray - */ static void -ResourceArrayInit(ResourceArray *resarr, Datum invalidval) +ResourceOwnerAddToHash(ResourceOwner owner, Datum value, ResourceOwnerFuncs *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 */ + /* Insert into first free slot at or after hash location. */ + uint32 mask = owner->capacity - 1; + uint32 idx; + + idx = hash_resource_elem(value, kind) & mask; + for (;;) + { + if (owner->hash[idx].kind == NULL) + break; + idx = (idx + 1) & mask; + } + owner->hash[idx].item = value; + owner->hash[idx].kind = kind; + owner->nhash++; } /* - * 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. + * Call the ReleaseResource callback on entries with given 'phase'. */ static void -ResourceArrayEnlarge(ResourceArray *resarr) +ResourceOwnerReleaseAll(ResourceOwner owner, ResourceReleasePhase phase, + bool printLeakWarnings) { - uint32 i, - oldcap, - newcap; - Datum *olditemsarr; - Datum *newitemsarr; - - if (resarr->nitems < resarr->maxitems) - return; /* no work needed */ + bool found; + int capacity; - 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; + /* First handle all the entries in the array. */ + do + { + found = false; + for (int i = 0; i < owner->narr; i++) + { + if (owner->arr[i].kind->phase == phase) + { + Datum value = owner->arr[i].item; + ResourceOwnerFuncs *kind = owner->arr[i].kind; - /* We assume we can't fail below this point, so OK to scribble on resarr */ - resarr->itemsarr = newitemsarr; - resarr->capacity = newcap; - resarr->maxitems = RESARRAY_MAX_ITEMS(newcap); - resarr->nitems = 0; + if (printLeakWarnings) + kind->PrintLeakWarning(value); + kind->ReleaseResource(value); + found = true; + } + } - if (olditemsarr != NULL) - { /* - * Transfer any pre-existing entries into the new array; they don't - * necessarily go where they were before, so this simple logic is the - * best way. Note that if we were managing the set as a simple array, - * the entries after nitems are garbage, but that shouldn't matter - * because we won't get here unless nitems was equal to oldcap. + * If any resources were released, check again because some of the + * elements might have been moved by the callbacks. We don't want to + * miss them. */ - for (i = 0; i < oldcap; i++) + } while (found && owner->narr > 0); + + /* Ok, the array has now been handled. Then the hash */ + do + { + capacity = owner->capacity; + for (int idx = 0; idx < capacity; idx++) { - if (olditemsarr[i] != resarr->invalidval) - ResourceArrayAdd(resarr, olditemsarr[i]); + while (owner->hash[idx].kind != NULL && + owner->hash[idx].kind->phase == phase) + { + Datum value = owner->hash[idx].item; + ResourceOwnerFuncs *kind = owner->hash[idx].kind; + + if (printLeakWarnings) + kind->PrintLeakWarning(value); + kind->ReleaseResource(value); + + /* + * If the resource is remembered more than once in this + * resource owner, the ReleaseResource callback might've + * released a different copy of it. Because of that, loop to + * check the same index again. + */ + } } - /* And release old array. */ - pfree(olditemsarr); - } - - Assert(resarr->nitems < resarr->maxitems); + /* + * It's possible that the callbacks acquired more resources, causing + * the hash table to grow and the existing entries to be moved around. + * If that happened, scan the hash table again, so that we don't miss + * entries that were moved. (XXX: I'm not sure if any of the callbacks + * actually do that, but this is cheap to check, and better safe than + * sorry.) + */ + Assert(owner->capacity >= capacity); + } while (capacity != owner->capacity); } + +/***************************************************************************** + * EXPORTED ROUTINES * + *****************************************************************************/ + + /* - * Add a resource to ResourceArray + * ResourceOwnerCreate + * Create an empty ResourceOwner. * - * Caller must have previously done ResourceArrayEnlarge() + * All ResourceOwner objects are kept in TopMemoryContext, since they should + * only be freed explicitly. */ -static void -ResourceArrayAdd(ResourceArray *resarr, Datum value) +ResourceOwner +ResourceOwnerCreate(ResourceOwner parent, const char *name) { - uint32 idx; + ResourceOwner owner; - Assert(value != resarr->invalidval); - Assert(resarr->nitems < resarr->maxitems); + owner = (ResourceOwner) MemoryContextAllocZero(TopMemoryContext, + sizeof(ResourceOwnerData)); + owner->name = name; - if (RESARRAY_IS_ARRAY(resarr)) + if (parent) { - /* Append to linear array. */ - idx = resarr->nitems; + owner->parent = parent; + owner->nextchild = parent->firstchild; + parent->firstchild = owner; } - 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++; +#ifdef RESOWNER_TRACE + elog(LOG, "CREATE %d: %p %s", + resowner_trace_counter++, owner, name); +#endif + + return owner; } /* - * Remove a resource from ResourceArray - * - * Returns true on success, false if resource was not found. + * Make sure there is room for at least one more resource in an array. * - * Note: if same resource ID appears more than once, one instance is removed. + * 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. */ -static bool -ResourceArrayRemove(ResourceArray *resarr, Datum value) +void +ResourceOwnerEnlarge(ResourceOwner owner) { - uint32 i, - idx, - lastidx = resarr->lastidx; - - Assert(value != resarr->invalidval); + if (owner->narr < RESOWNER_ARRAY_SIZE) + return; /* no work needed */ - /* Search through all items, but try lastidx first. */ - if (RESARRAY_IS_ARRAY(resarr)) + /* Is there space in the hash? If not, enlarge it. */ + if (owner->narr + owner->nhash >= owner->grow_at) { - 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++) + 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) { - if (resarr->itemsarr[i] == value) + /* + * 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++) { - resarr->itemsarr[i] = resarr->itemsarr[resarr->nitems - 1]; - resarr->nitems--; - /* Update lastidx to make reverse-order removals fast. */ - resarr->lastidx = resarr->nitems - 1; - return true; + if (oldhash[i].kind != NULL) + ResourceOwnerAddToHash(owner, oldhash[i].item, oldhash[i].kind); } + + /* And release old hash table. */ + pfree(oldhash); } } - else - { - uint32 mask = resarr->capacity - 1; - if (lastidx < resarr->capacity && - resarr->itemsarr[lastidx] == value) - { - resarr->itemsarr[lastidx] = resarr->invalidval; - resarr->nitems--; - return true; - } - idx = DatumGetUInt32(hash_any((void *) &value, sizeof(value))) & mask; - for (i = 0; i < resarr->capacity; i++) - { - if (resarr->itemsarr[idx] == value) - { - resarr->itemsarr[idx] = resarr->invalidval; - resarr->nitems--; - return true; - } - idx = (idx + 1) & mask; - } + /* Move items from the array to the hash */ + Assert(owner->narr == RESOWNER_ARRAY_SIZE); + for (int i = 0; i < owner->narr; i++) + { + ResourceOwnerAddToHash(owner, owner->arr[i].item, owner->arr[i].kind); } + owner->narr = 0; - return false; + Assert(owner->nhash < owner->grow_at); } /* - * Get any convenient entry in a ResourceArray. + * Remember that an object is owner by a ReourceOwner * - * "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. + * Caller must have previously done ResourceOwnerEnlarge() */ -static bool -ResourceArrayGetAny(ResourceArray *resarr, Datum *value) +void +ResourceOwnerRemember(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind) { - if (resarr->nitems == 0) - return false; + uint32 idx; - if (RESARRAY_IS_ARRAY(resarr)) - { - /* Linear array: just return the first element. */ - resarr->lastidx = 0; - } - else - { - /* Hash: search forward from wherever we were last. */ - uint32 mask = resarr->capacity - 1; +#ifdef RESOWNER_TRACE + elog(LOG, "REMEMBER %d: owner %p value " UINT64_FORMAT ", kind: %s", + resowner_trace_counter++, owner, DatumGetUInt64(value), kind->name); +#endif - for (;;) - { - resarr->lastidx &= mask; - if (resarr->itemsarr[resarr->lastidx] != resarr->invalidval) - break; - resarr->lastidx++; - } - } + Assert(owner->narr < RESOWNER_ARRAY_SIZE); - *value = resarr->itemsarr[resarr->lastidx]; - return true; + /* Append to linear array. */ + idx = owner->narr; + owner->arr[idx].item = value; + owner->arr[idx].kind = kind; + owner->narr++; } /* - * Trash a ResourceArray (we don't care about its state after this) + * Forget that an object is owned by a ResourceOwner + * + * Returns true on success. If the resource was not found, returns false, + * and calls kind->ForgetError callback. + * + * Note: if same resource ID is associated with the ResourceOwner more than once, + * one instance is removed. */ -static void -ResourceArrayFree(ResourceArray *resarr) +void +ResourceOwnerForget(ResourceOwner owner, Datum value, ResourceOwnerFuncs *kind) { - if (resarr->itemsarr) - pfree(resarr->itemsarr); -} - + uint32 i, + idx; -/***************************************************************************** - * EXPORTED ROUTINES * - *****************************************************************************/ +#ifdef RESOWNER_TRACE + elog(LOG, "FORGET %d: owner %p value " UINT64_FORMAT ", kind: %s", + resowner_trace_counter++, owner, DatumGetUInt64(value), kind->name); +#endif + /* Search through all items, but check the array first. */ + for (i = 0; i < owner->narr; i++) + { + if (owner->arr[i].item == value && + owner->arr[i].kind == kind) + { + owner->arr[i] = owner->arr[owner->narr - 1]; + owner->narr--; -/* - * ResourceOwnerCreate - * Create an empty ResourceOwner. - * - * All ResourceOwner objects are kept in TopMemoryContext, since they should - * only be freed explicitly. - */ -ResourceOwner -ResourceOwnerCreate(ResourceOwner parent, const char *name) -{ - ResourceOwner owner; +#ifdef RESOWNER_STATS + narray_lookups++; +#endif - owner = (ResourceOwner) MemoryContextAllocZero(TopMemoryContext, - sizeof(ResourceOwnerData)); - owner->name = name; + return; + } + } - if (parent) + /* Search hash */ + if (owner->nhash > 0) { - owner->parent = parent; - owner->nextchild = parent->firstchild; - parent->firstchild = owner; - } + uint32 mask = owner->capacity - 1; - ResourceArrayInit(&(owner->bufferarr), 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)); + idx = hash_resource_elem(value, kind) & mask; + for (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; + } + } - return owner; + /* + * 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); } /* @@ -486,6 +497,15 @@ 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 @@ -497,7 +517,6 @@ ResourceOwnerReleaseInternal(ResourceOwner owner, ResourceOwner child; ResourceOwner save; ResourceReleaseCallbackItem *item; - Datum foundres; /* Recurse to handle descendants */ for (child = owner->firstchild; child != NULL; child = child->nextchild) @@ -513,61 +532,13 @@ ResourceOwnerReleaseInternal(ResourceOwner 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 references 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 references --- + * 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 *) PointerGetDatum(foundres); - - jit_release_context(context); - } - - /* Ditto for cryptohash contexts */ - while (ResourceArrayGetAny(&(owner->cryptohasharr), &foundres)) - { - pg_cryptohash_ctx *context = - (pg_cryptohash_ctx *) PointerGetDatum(foundres); - - if (isCommit) - PrintCryptoHashLeakWarning(foundres); - pg_cryptohash_free(context); - } + ResourceOwnerReleaseAll(owner, phase, isCommit); } else if (phase == RESOURCE_RELEASE_LOCKS) { @@ -576,7 +547,7 @@ ResourceOwnerReleaseInternal(ResourceOwner owner, /* * For a top-level xact we are going to release all locks (or at * least all non-session locks), so just do a single lmgr call at - * the top of the recursion. + * the top of the recursion */ if (owner == TopTransactionResourceOwner) { @@ -620,70 +591,9 @@ ResourceOwnerReleaseInternal(ResourceOwner owner, else if (phase == RESOURCE_RELEASE_AFTER_LOCKS) { /* - * Release catcache references. Note that ReleaseCatCache will remove - * the catref entry from our array, so we just have to iterate till - * there are none. - * - * As with buffer pins, warn if any are left at commit time. + * Release all references that need to be released after the locks. */ - while (ResourceArrayGetAny(&(owner->catrefarr), &foundres)) - { - HeapTuple res = (HeapTuple) DatumGetPointer(foundres); - - if (isCommit) - PrintCatCacheLeakWarning(res); - ReleaseCatCache(res); - } - - /* Ditto for catcache lists */ - while (ResourceArrayGetAny(&(owner->catlistrefarr), &foundres)) - { - CatCList *res = (CatCList *) DatumGetPointer(foundres); - - if (isCommit) - PrintCatCacheListLeakWarning(res); - ReleaseCatCacheList(res); - } - - /* Ditto for plancache references */ - while (ResourceArrayGetAny(&(owner->planrefarr), &foundres)) - { - CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres); - - if (isCommit) - PrintPlanCacheLeakWarning(res); - ReleaseCachedPlan(res, true); - } - - /* Ditto for tupdesc references */ - while (ResourceArrayGetAny(&(owner->tupdescarr), &foundres)) - { - TupleDesc res = (TupleDesc) DatumGetPointer(foundres); - - if (isCommit) - PrintTupleDescLeakWarning(res); - DecrTupleDescRefCount(res); - } - - /* Ditto for snapshot references */ - while (ResourceArrayGetAny(&(owner->snapshotarr), &foundres)) - { - Snapshot res = (Snapshot) DatumGetPointer(foundres); - - if (isCommit) - PrintSnapshotLeakWarning(res); - UnregisterSnapshot(res); - } - - /* Ditto for temporary files */ - while (ResourceArrayGetAny(&(owner->filearr), &foundres)) - { - File res = DatumGetFile(foundres); - - if (isCommit) - PrintFileLeakWarning(res); - FileClose(res); - } + ResourceOwnerReleaseAll(owner, phase, isCommit); } /* Let add-on modules get a chance too */ @@ -704,16 +614,48 @@ void ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner) { ResourceOwner save; - Datum foundres; save = CurrentResourceOwner; CurrentResourceOwner = owner; - while (ResourceArrayGetAny(&(owner->planrefarr), &foundres)) + + /* array first */ + for (int i = 0; i < owner->narr; i++) + { + if (owner->arr[i].kind == &planref_resowner_funcs) + { + CachedPlan *planref = (CachedPlan *) DatumGetPointer(owner->arr[i].item); + + owner->arr[i] = owner->arr[owner->narr - 1]; + owner->narr--; + i--; + + /* + * pass 'false' because we already removed the entry from the + * resowner + */ + ReleaseCachedPlan(planref, false); + } + } + + /* Then hash */ + for (int i = 0; i < owner->capacity; i++) { - CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres); + if (owner->hash[i].kind == &planref_resowner_funcs) + { + CachedPlan *planref = (CachedPlan *) DatumGetPointer(owner->hash[i].item); - ReleaseCachedPlan(res, true); + owner->hash[i].item = (Datum) 0; + owner->hash[i].kind = NULL; + owner->nhash--; + + /* + * pass 'false' because we already removed the entry from the + * resowner + */ + ReleaseCachedPlan(planref, false); + } } + CurrentResourceOwner = save; } @@ -730,19 +672,15 @@ ResourceOwnerDelete(ResourceOwner owner) Assert(owner != CurrentResourceOwner); /* And it better not own any resources, either */ - Assert(owner->bufferarr.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->narr == 0); + Assert(owner->nhash == 0); Assert(owner->nlocks == 0 || owner->nlocks == MAX_RESOWNER_LOCKS + 1); +#ifdef RESOWNER_TRACE + elog(LOG, "DELETE %d: %p %s", + resowner_trace_counter++, owner, owner->name); +#endif + /* * Delete children. The recursive call will delink the child from me, so * just iterate as long as there is a child. @@ -758,18 +696,8 @@ ResourceOwnerDelete(ResourceOwner owner) ResourceOwnerNewParent(owner, NULL); /* And free the object. */ - ResourceArrayFree(&(owner->bufferarr)); - 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)); - + if (owner->hash) + pfree(owner->hash); pfree(owner); } @@ -827,11 +755,10 @@ ResourceOwnerNewParent(ResourceOwner owner, /* * Register or deregister callback functions for resource cleanup * - * These functions are intended for use by dynamically loaded modules. - * For built-in modules we generally just hardwire the appropriate calls. - * - * Note that the callback occurs post-commit or post-abort, so the callback - * functions can only do noncritical cleanup. + * These functions can be used by dynamically loaded modules. These used + * to be the only way for an extension to register custom resource types + * with a resource owner, but nowadays it is easier to define a new + * ResourceOwnerFuncs instance with custom callbacks. */ void RegisterResourceReleaseCallback(ResourceReleaseCallback callback, void *arg) @@ -922,44 +849,6 @@ ReleaseAuxProcessResourcesCallback(int code, Datum arg) ReleaseAuxProcessResources(isCommit); } - -/* - * Make sure there is room for at least one more entry in a ResourceOwner's - * buffer array. - * - * This is separate from actually inserting an entry because if we run out - * of memory, it's critical to do so *before* acquiring the resource. - */ -void -ResourceOwnerEnlargeBuffers(ResourceOwner owner) -{ - /* We used to allow pinning buffers without a resowner, but no more */ - Assert(owner != NULL); - ResourceArrayEnlarge(&(owner->bufferarr)); -} - -/* - * Remember that a buffer pin is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeBuffers() - */ -void -ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer) -{ - ResourceArrayAdd(&(owner->bufferarr), BufferGetDatum(buffer)); -} - -/* - * Forget that a buffer pin is owned by a ResourceOwner - */ -void -ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer) -{ - if (!ResourceArrayRemove(&(owner->bufferarr), BufferGetDatum(buffer))) - elog(ERROR, "buffer %d is not owned by resource owner %s", - buffer, owner->name); -} - /* * Remember that a Local Lock is owned by a ResourceOwner * @@ -1011,424 +900,3 @@ ResourceOwnerForgetLock(ResourceOwner owner, LOCALLOCK *locallock) elog(ERROR, "lock reference %p is not owned by resource owner %s", locallock, owner->name); } - -/* - * Make sure there is room for at least one more entry in a ResourceOwner's - * catcache reference array. - * - * This is separate from actually inserting an entry because if we run out - * of memory, it's critical to do so *before* acquiring the resource. - */ -void -ResourceOwnerEnlargeCatCacheRefs(ResourceOwner owner) -{ - ResourceArrayEnlarge(&(owner->catrefarr)); -} - -/* - * Remember that a catcache reference is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeCatCacheRefs() - */ -void -ResourceOwnerRememberCatCacheRef(ResourceOwner owner, HeapTuple tuple) -{ - ResourceArrayAdd(&(owner->catrefarr), PointerGetDatum(tuple)); -} - -/* - * Forget that a catcache reference is owned by a ResourceOwner - */ -void -ResourceOwnerForgetCatCacheRef(ResourceOwner owner, HeapTuple tuple) -{ - if (!ResourceArrayRemove(&(owner->catrefarr), PointerGetDatum(tuple))) - elog(ERROR, "catcache reference %p is not owned by resource owner %s", - tuple, owner->name); -} - -/* - * Make sure there is room for at least one more entry in a ResourceOwner's - * catcache-list reference array. - * - * This is separate from actually inserting an entry because if we run out - * of memory, it's critical to do so *before* acquiring the resource. - */ -void -ResourceOwnerEnlargeCatCacheListRefs(ResourceOwner owner) -{ - ResourceArrayEnlarge(&(owner->catlistrefarr)); -} - -/* - * Remember that a catcache-list reference is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeCatCacheListRefs() - */ -void -ResourceOwnerRememberCatCacheListRef(ResourceOwner owner, CatCList *list) -{ - ResourceArrayAdd(&(owner->catlistrefarr), PointerGetDatum(list)); -} - -/* - * Forget that a catcache-list reference is owned by a ResourceOwner - */ -void -ResourceOwnerForgetCatCacheListRef(ResourceOwner owner, CatCList *list) -{ - if (!ResourceArrayRemove(&(owner->catlistrefarr), PointerGetDatum(list))) - elog(ERROR, "catcache list reference %p is not owned by resource owner %s", - list, owner->name); -} - -/* - * Make sure there is room for at least one more entry in a ResourceOwner's - * relcache reference array. - * - * This is separate from actually inserting an entry because if we run out - * of memory, it's critical to do so *before* acquiring the resource. - */ -void -ResourceOwnerEnlargeRelationRefs(ResourceOwner owner) -{ - ResourceArrayEnlarge(&(owner->relrefarr)); -} - -/* - * Remember that a relcache reference is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeRelationRefs() - */ -void -ResourceOwnerRememberRelationRef(ResourceOwner owner, Relation rel) -{ - ResourceArrayAdd(&(owner->relrefarr), PointerGetDatum(rel)); -} - -/* - * Forget that a relcache reference is owned by a ResourceOwner - */ -void -ResourceOwnerForgetRelationRef(ResourceOwner owner, Relation rel) -{ - if (!ResourceArrayRemove(&(owner->relrefarr), PointerGetDatum(rel))) - elog(ERROR, "relcache reference %s is not owned by resource owner %s", - RelationGetRelationName(rel), owner->name); -} - -/* - * Debugging subroutine - */ -static void -PrintRelCacheLeakWarning(Relation rel) -{ - elog(WARNING, "relcache reference leak: relation \"%s\" not closed", - RelationGetRelationName(rel)); -} - -/* - * Make sure there is room for at least one more entry in a ResourceOwner's - * plancache reference array. - * - * This is separate from actually inserting an entry because if we run out - * of memory, it's critical to do so *before* acquiring the resource. - */ -void -ResourceOwnerEnlargePlanCacheRefs(ResourceOwner owner) -{ - ResourceArrayEnlarge(&(owner->planrefarr)); -} - -/* - * Remember that a plancache reference is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargePlanCacheRefs() - */ -void -ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, CachedPlan *plan) -{ - ResourceArrayAdd(&(owner->planrefarr), PointerGetDatum(plan)); -} - -/* - * Forget that a plancache reference is owned by a ResourceOwner - */ -void -ResourceOwnerForgetPlanCacheRef(ResourceOwner owner, CachedPlan *plan) -{ - if (!ResourceArrayRemove(&(owner->planrefarr), PointerGetDatum(plan))) - elog(ERROR, "plancache reference %p is not owned by resource owner %s", - plan, owner->name); -} - -/* - * Debugging subroutine - */ -static void -PrintPlanCacheLeakWarning(CachedPlan *plan) -{ - elog(WARNING, "plancache reference leak: plan %p not closed", plan); -} - -/* - * Make sure there is room for at least one more entry in a ResourceOwner's - * tupdesc reference array. - * - * This is separate from actually inserting an entry because if we run out - * of memory, it's critical to do so *before* acquiring the resource. - */ -void -ResourceOwnerEnlargeTupleDescs(ResourceOwner owner) -{ - ResourceArrayEnlarge(&(owner->tupdescarr)); -} - -/* - * Remember that a tupdesc reference is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeTupleDescs() - */ -void -ResourceOwnerRememberTupleDesc(ResourceOwner owner, TupleDesc tupdesc) -{ - ResourceArrayAdd(&(owner->tupdescarr), PointerGetDatum(tupdesc)); -} - -/* - * Forget that a tupdesc reference is owned by a ResourceOwner - */ -void -ResourceOwnerForgetTupleDesc(ResourceOwner owner, TupleDesc tupdesc) -{ - if (!ResourceArrayRemove(&(owner->tupdescarr), PointerGetDatum(tupdesc))) - elog(ERROR, "tupdesc reference %p is not owned by resource owner %s", - tupdesc, owner->name); -} - -/* - * Debugging subroutine - */ -static void -PrintTupleDescLeakWarning(TupleDesc tupdesc) -{ - elog(WARNING, - "TupleDesc reference leak: TupleDesc %p (%u,%d) still referenced", - tupdesc, tupdesc->tdtypeid, tupdesc->tdtypmod); -} - -/* - * Make sure there is room for at least one more entry in a ResourceOwner's - * snapshot reference array. - * - * This is separate from actually inserting an entry because if we run out - * of memory, it's critical to do so *before* acquiring the resource. - */ -void -ResourceOwnerEnlargeSnapshots(ResourceOwner owner) -{ - ResourceArrayEnlarge(&(owner->snapshotarr)); -} - -/* - * Remember that a snapshot reference is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeSnapshots() - */ -void -ResourceOwnerRememberSnapshot(ResourceOwner owner, Snapshot snapshot) -{ - ResourceArrayAdd(&(owner->snapshotarr), PointerGetDatum(snapshot)); -} - -/* - * Forget that a snapshot reference is owned by a ResourceOwner - */ -void -ResourceOwnerForgetSnapshot(ResourceOwner owner, Snapshot snapshot) -{ - if (!ResourceArrayRemove(&(owner->snapshotarr), PointerGetDatum(snapshot))) - elog(ERROR, "snapshot reference %p is not owned by resource owner %s", - snapshot, owner->name); -} - -/* - * Debugging subroutine - */ -static void -PrintSnapshotLeakWarning(Snapshot snapshot) -{ - elog(WARNING, "Snapshot reference leak: Snapshot %p still referenced", - snapshot); -} - - -/* - * Make sure there is room for at least one more entry in a ResourceOwner's - * files reference array. - * - * This is separate from actually inserting an entry because if we run out - * of memory, it's critical to do so *before* acquiring the resource. - */ -void -ResourceOwnerEnlargeFiles(ResourceOwner owner) -{ - ResourceArrayEnlarge(&(owner->filearr)); -} - -/* - * Remember that a temporary file is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeFiles() - */ -void -ResourceOwnerRememberFile(ResourceOwner owner, File file) -{ - ResourceArrayAdd(&(owner->filearr), FileGetDatum(file)); -} - -/* - * Forget that a temporary file is owned by a ResourceOwner - */ -void -ResourceOwnerForgetFile(ResourceOwner owner, File file) -{ - if (!ResourceArrayRemove(&(owner->filearr), FileGetDatum(file))) - elog(ERROR, "temporary file %d is not owned by resource owner %s", - file, owner->name); -} - -/* - * Debugging subroutine - */ -static void -PrintFileLeakWarning(File file) -{ - elog(WARNING, "temporary file leak: File %d still referenced", - file); -} - -/* - * Make sure there is room for at least one more entry in a ResourceOwner's - * dynamic shmem segment reference array. - * - * This is separate from actually inserting an entry because if we run out - * of memory, it's critical to do so *before* acquiring the resource. - */ -void -ResourceOwnerEnlargeDSMs(ResourceOwner owner) -{ - ResourceArrayEnlarge(&(owner->dsmarr)); -} - -/* - * Remember that a dynamic shmem segment is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeDSMs() - */ -void -ResourceOwnerRememberDSM(ResourceOwner owner, dsm_segment *seg) -{ - ResourceArrayAdd(&(owner->dsmarr), PointerGetDatum(seg)); -} - -/* - * Forget that a dynamic shmem segment is owned by a ResourceOwner - */ -void -ResourceOwnerForgetDSM(ResourceOwner owner, dsm_segment *seg) -{ - if (!ResourceArrayRemove(&(owner->dsmarr), PointerGetDatum(seg))) - elog(ERROR, "dynamic shared memory segment %u is not owned by resource owner %s", - dsm_segment_handle(seg), owner->name); -} - -/* - * Debugging subroutine - */ -static void -PrintDSMLeakWarning(dsm_segment *seg) -{ - elog(WARNING, "dynamic shared memory leak: segment %u still referenced", - dsm_segment_handle(seg)); -} - -/* - * Make sure there is room for at least one more entry in a ResourceOwner's - * JIT context reference array. - * - * This is separate from actually inserting an entry because if we run out of - * memory, it's critical to do so *before* acquiring the resource. - */ -void -ResourceOwnerEnlargeJIT(ResourceOwner owner) -{ - ResourceArrayEnlarge(&(owner->jitarr)); -} - -/* - * Remember that a JIT context is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeJIT() - */ -void -ResourceOwnerRememberJIT(ResourceOwner owner, Datum handle) -{ - ResourceArrayAdd(&(owner->jitarr), handle); -} - -/* - * Forget that a JIT context is owned by a ResourceOwner - */ -void -ResourceOwnerForgetJIT(ResourceOwner owner, Datum handle) -{ - if (!ResourceArrayRemove(&(owner->jitarr), handle)) - elog(ERROR, "JIT context %p is not owned by resource owner %s", - DatumGetPointer(handle), owner->name); -} - -/* - * Make sure there is room for at least one more entry in a ResourceOwner's - * cryptohash context reference array. - * - * This is separate from actually inserting an entry because if we run out of - * memory, it's critical to do so *before* acquiring the resource. - */ -void -ResourceOwnerEnlargeCryptoHash(ResourceOwner owner) -{ - ResourceArrayEnlarge(&(owner->cryptohasharr)); -} - -/* - * Remember that a cryptohash context is owned by a ResourceOwner - * - * Caller must have previously done ResourceOwnerEnlargeCryptoHash() - */ -void -ResourceOwnerRememberCryptoHash(ResourceOwner owner, Datum handle) -{ - ResourceArrayAdd(&(owner->cryptohasharr), handle); -} - -/* - * Forget that a cryptohash context is owned by a ResourceOwner - */ -void -ResourceOwnerForgetCryptoHash(ResourceOwner owner, Datum handle) -{ - if (!ResourceArrayRemove(&(owner->cryptohasharr), handle)) - elog(ERROR, "cryptohash context %p is not owned by resource owner %s", - DatumGetPointer(handle), owner->name); -} - -/* - * Debugging subroutine - */ -static void -PrintCryptoHashLeakWarning(Datum handle) -{ - elog(WARNING, "cryptohash context reference leak: context %p still referenced", - DatumGetPointer(handle)); -} diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c index ae16c3ed7d6..3ded16f6fc8 100644 --- a/src/backend/utils/time/snapmgr.c +++ b/src/backend/utils/time/snapmgr.c @@ -66,7 +66,7 @@ #include "utils/memutils.h" #include "utils/old_snapshot.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" @@ -174,6 +174,24 @@ static Snapshot CopySnapshot(Snapshot snapshot); static void FreeSnapshot(Snapshot snapshot); static void SnapshotResetXmin(void); +/* ResourceOwner callbacks to track snapshot references */ +static void ResOwnerReleaseSnapshot(Datum res); +static void ResOwnerPrintSnapshotLeakWarning(Datum res); + +static ResourceOwnerFuncs snapshot_resowner_funcs = +{ + .name = "snapshot reference", + .phase = RESOURCE_RELEASE_AFTER_LOCKS, + .ReleaseResource = ResOwnerReleaseSnapshot, + .PrintLeakWarning = ResOwnerPrintSnapshotLeakWarning +}; + +/* Convenience wrappers over ResourceOwnerRemember/Forget */ +#define ResourceOwnerRememberSnapshot(owner, snap) \ + ResourceOwnerRemember(owner, PointerGetDatum(snap), &snapshot_resowner_funcs) +#define ResourceOwnerForgetSnapshot(owner, snap) \ + ResourceOwnerForget(owner, PointerGetDatum(snap), &snapshot_resowner_funcs) + /* * Snapshot fields to be serialized. * @@ -831,7 +849,7 @@ RegisterSnapshotOnOwner(Snapshot snapshot, ResourceOwner owner) snap = snapshot->copied ? snapshot : CopySnapshot(snapshot); /* and tell resowner.c about it */ - ResourceOwnerEnlargeSnapshots(owner); + ResourceOwnerEnlarge(owner); snap->regd_count++; ResourceOwnerRememberSnapshot(owner, snap); @@ -2345,3 +2363,19 @@ XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot) return false; } + +/* + * ResourceOwner callbacks + */ +static void +ResOwnerReleaseSnapshot(Datum res) +{ + UnregisterSnapshot((Snapshot) DatumGetPointer(res)); +} + +static void +ResOwnerPrintSnapshotLeakWarning(Datum res) +{ + elog(WARNING, "Snapshot reference leak: Snapshot %p still referenced", + DatumGetPointer(res)); +} diff --git a/src/common/cryptohash_openssl.c b/src/common/cryptohash_openssl.c index 551ec392b60..6421f1de5cd 100644 --- a/src/common/cryptohash_openssl.c +++ b/src/common/cryptohash_openssl.c @@ -27,7 +27,6 @@ #ifndef FRONTEND #include "utils/memutils.h" #include "utils/resowner.h" -#include "utils/resowner_private.h" #endif /* @@ -60,6 +59,27 @@ struct pg_cryptohash_ctx #endif }; +/* ResourceOwner callbacks to hold cryptohash contexts */ +#ifndef FRONTEND +static void ResOwnerReleaseCryptoHash(Datum res); +static void ResOwnerPrintCryptoHashLeakWarning(Datum res); + +static ResourceOwnerFuncs cryptohash_resowner_funcs = +{ + /* relcache references */ + .name = "OpenSSL cryptohash context", + .phase = RESOURCE_RELEASE_BEFORE_LOCKS, + .ReleaseResource = ResOwnerReleaseCryptoHash, + .PrintLeakWarning = ResOwnerPrintCryptoHashLeakWarning, +}; + +/* Convenience wrappers over ResourceOwnerRemember/Forget */ +#define ResourceOwnerRememberCryptoHash(owner, ctx) \ + ResourceOwnerRemember(owner, PointerGetDatum(ctx), &cryptohash_resowner_funcs) +#define ResourceOwnerForgetCryptoHash(owner, ctx) \ + ResourceOwnerForget(owner, PointerGetDatum(ctx), &cryptohash_resowner_funcs) +#endif + /* * pg_cryptohash_create * @@ -77,7 +97,7 @@ pg_cryptohash_create(pg_cryptohash_type type) * allocation to avoid leaking. */ #ifndef FRONTEND - ResourceOwnerEnlargeCryptoHash(CurrentResourceOwner); + ResourceOwnerEnlarge(CurrentResourceOwner); #endif ctx = ALLOC(sizeof(pg_cryptohash_ctx)); @@ -106,8 +126,7 @@ pg_cryptohash_create(pg_cryptohash_type type) #ifndef FRONTEND ctx->resowner = CurrentResourceOwner; - ResourceOwnerRememberCryptoHash(CurrentResourceOwner, - PointerGetDatum(ctx)); + ResourceOwnerRememberCryptoHash(CurrentResourceOwner, ctx); #endif return ctx; @@ -207,10 +226,28 @@ pg_cryptohash_free(pg_cryptohash_ctx *ctx) EVP_MD_CTX_destroy(ctx->evpctx); #ifndef FRONTEND - ResourceOwnerForgetCryptoHash(ctx->resowner, - PointerGetDatum(ctx)); + ResourceOwnerForgetCryptoHash(ctx->resowner, ctx); #endif explicit_bzero(ctx, sizeof(pg_cryptohash_ctx)); FREE(ctx); } + + +/* + * ResourceOwner callbacks + */ +#ifndef FRONTEND +static void +ResOwnerReleaseCryptoHash(Datum res) +{ + pg_cryptohash_free((pg_cryptohash_ctx *) DatumGetPointer(res)); +} + +static void +ResOwnerPrintCryptoHashLeakWarning(Datum res) +{ + elog(WARNING, "cryptohash context reference leak: context %p still referenced", + DatumGetPointer(res)); +} +#endif diff --git a/src/include/storage/buf_internals.h b/src/include/storage/buf_internals.h index f6b57829653..6815dc38a16 100644 --- a/src/include/storage/buf_internals.h +++ b/src/include/storage/buf_internals.h @@ -296,6 +296,15 @@ typedef struct CkptSortItem extern CkptSortItem *CkptBufferIds; +/* ResourceOwner callbacks to hold buffer pins */ +extern ResourceOwnerFuncs buffer_resowner_funcs; + +/* Convenience wrappers over ResourceOwnerRemember/Forget */ +#define ResourceOwnerRememberBuffer(owner, buffer) \ + ResourceOwnerRemember(owner, Int32GetDatum(buffer), &buffer_resowner_funcs) +#define ResourceOwnerForgetBuffer(owner, buffer) \ + ResourceOwnerForget(owner, Int32GetDatum(buffer), &buffer_resowner_funcs) + /* * Internal buffer management routines */ diff --git a/src/include/utils/catcache.h b/src/include/utils/catcache.h index ddc2762eb3f..2bf95c22e84 100644 --- a/src/include/utils/catcache.h +++ b/src/include/utils/catcache.h @@ -225,7 +225,4 @@ extern void PrepareToInvalidateCacheTuple(Relation relation, HeapTuple newtuple, void (*function) (int, uint32, Oid)); -extern void PrintCatCacheLeakWarning(HeapTuple tuple); -extern void PrintCatCacheListLeakWarning(CatCList *list); - #endif /* CATCACHE_H */ diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h index 79d96e5ed03..964af79b30d 100644 --- a/src/include/utils/plancache.h +++ b/src/include/utils/plancache.h @@ -233,4 +233,6 @@ extern bool CachedPlanIsSimplyValid(CachedPlanSource *plansource, extern CachedExpression *GetCachedExpression(Node *expr); extern void FreeCachedExpression(CachedExpression *cexpr); +extern ResourceOwnerFuncs planref_resowner_funcs; + #endif /* PLANCACHE_H */ diff --git a/src/include/utils/resowner.h b/src/include/utils/resowner.h index 109ac31b248..d59d14c0d01 100644 --- a/src/include/utils/resowner.h +++ b/src/include/utils/resowner.h @@ -50,6 +50,36 @@ typedef enum RESOURCE_RELEASE_AFTER_LOCKS } ResourceReleasePhase; +/* + * In order to track an object, resowner.c needs a few callbacks for it. + * The callbacks for an resource of a specific kind are encapsulated in + * ResourceOwnerFuncs. + * + * Note that the callback occurs post-commit or post-abort, so these callback + * functions can only do noncritical cleanup. + */ +typedef struct ResourceOwnerFuncs +{ + const char *name; /* name for the object kind, for debugging */ + + ResourceReleasePhase phase; /* when are these objects released? */ + + /* + * Release resource. + * + * NOTE: this must call ResourceOwnerForget to disassociate it with the + * resource owner. + */ + void (*ReleaseResource)(Datum res); + + /* + * Print a warning, when a resource has not been properly released before + * commit. + */ + void (*PrintLeakWarning)(Datum res); + +} ResourceOwnerFuncs; + /* * Dynamically loaded modules can get control during ResourceOwnerRelease * by providing a callback of this form. @@ -71,16 +101,29 @@ extern void ResourceOwnerRelease(ResourceOwner owner, ResourceReleasePhase phase, bool isCommit, bool isTopLevel); -extern void ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner); extern void ResourceOwnerDelete(ResourceOwner owner); extern ResourceOwner ResourceOwnerGetParent(ResourceOwner owner); extern void ResourceOwnerNewParent(ResourceOwner owner, ResourceOwner newparent); + +extern void ResourceOwnerEnlarge(ResourceOwner owner); +extern void ResourceOwnerRemember(ResourceOwner owner, Datum res, ResourceOwnerFuncs *kind); +extern void ResourceOwnerForget(ResourceOwner owner, Datum res, ResourceOwnerFuncs *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); + +/* special function to relase all plancache references */ +extern void ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner); + #endif /* RESOWNER_H */ diff --git a/src/include/utils/resowner_private.h b/src/include/utils/resowner_private.h deleted file mode 100644 index c480a1a24be..00000000000 --- a/src/include/utils/resowner_private.h +++ /dev/null @@ -1,105 +0,0 @@ -/*------------------------------------------------------------------------- - * - * resowner_private.h - * POSTGRES resource owner private definitions. - * - * See utils/resowner/README for more info. - * - * - * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * src/include/utils/resowner_private.h - * - *------------------------------------------------------------------------- - */ -#ifndef RESOWNER_PRIVATE_H -#define RESOWNER_PRIVATE_H - -#include "storage/dsm.h" -#include "storage/fd.h" -#include "storage/lock.h" -#include "utils/catcache.h" -#include "utils/plancache.h" -#include "utils/resowner.h" -#include "utils/snapshot.h" - - -/* support for buffer refcount management */ -extern void ResourceOwnerEnlargeBuffers(ResourceOwner owner); -extern void ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer); -extern void ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer); - -/* support for local lock management */ -extern void ResourceOwnerRememberLock(ResourceOwner owner, LOCALLOCK *locallock); -extern void ResourceOwnerForgetLock(ResourceOwner owner, LOCALLOCK *locallock); - -/* support for catcache refcount management */ -extern void ResourceOwnerEnlargeCatCacheRefs(ResourceOwner owner); -extern void ResourceOwnerRememberCatCacheRef(ResourceOwner owner, - HeapTuple tuple); -extern void ResourceOwnerForgetCatCacheRef(ResourceOwner owner, - HeapTuple tuple); -extern void ResourceOwnerEnlargeCatCacheListRefs(ResourceOwner owner); -extern void ResourceOwnerRememberCatCacheListRef(ResourceOwner owner, - CatCList *list); -extern void ResourceOwnerForgetCatCacheListRef(ResourceOwner owner, - CatCList *list); - -/* support for relcache refcount management */ -extern void ResourceOwnerEnlargeRelationRefs(ResourceOwner owner); -extern void ResourceOwnerRememberRelationRef(ResourceOwner owner, - Relation rel); -extern void ResourceOwnerForgetRelationRef(ResourceOwner owner, - Relation rel); - -/* support for plancache refcount management */ -extern void ResourceOwnerEnlargePlanCacheRefs(ResourceOwner owner); -extern void ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, - CachedPlan *plan); -extern void ResourceOwnerForgetPlanCacheRef(ResourceOwner owner, - CachedPlan *plan); - -/* support for tupledesc refcount management */ -extern void ResourceOwnerEnlargeTupleDescs(ResourceOwner owner); -extern void ResourceOwnerRememberTupleDesc(ResourceOwner owner, - TupleDesc tupdesc); -extern void ResourceOwnerForgetTupleDesc(ResourceOwner owner, - TupleDesc tupdesc); - -/* support for snapshot refcount management */ -extern void ResourceOwnerEnlargeSnapshots(ResourceOwner owner); -extern void ResourceOwnerRememberSnapshot(ResourceOwner owner, - Snapshot snapshot); -extern void ResourceOwnerForgetSnapshot(ResourceOwner owner, - Snapshot snapshot); - -/* support for temporary file management */ -extern void ResourceOwnerEnlargeFiles(ResourceOwner owner); -extern void ResourceOwnerRememberFile(ResourceOwner owner, - File file); -extern void ResourceOwnerForgetFile(ResourceOwner owner, - File file); - -/* support for dynamic shared memory management */ -extern void ResourceOwnerEnlargeDSMs(ResourceOwner owner); -extern void ResourceOwnerRememberDSM(ResourceOwner owner, - dsm_segment *); -extern void ResourceOwnerForgetDSM(ResourceOwner owner, - dsm_segment *); - -/* support for JITContext management */ -extern void ResourceOwnerEnlargeJIT(ResourceOwner owner); -extern void ResourceOwnerRememberJIT(ResourceOwner owner, - Datum handle); -extern void ResourceOwnerForgetJIT(ResourceOwner owner, - Datum handle); - -/* support for cryptohash context management */ -extern void ResourceOwnerEnlargeCryptoHash(ResourceOwner owner); -extern void ResourceOwnerRememberCryptoHash(ResourceOwner owner, - Datum handle); -extern void ResourceOwnerForgetCryptoHash(ResourceOwner owner, - Datum handle); - -#endif /* RESOWNER_PRIVATE_H */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 943142ced8c..2df8d3cbefa 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2127,8 +2127,10 @@ ReplicationStateOnDisk ResTarget ReservoirState ReservoirStateData -ResourceArray +ResourceElem ResourceOwner +ResourceOwnerData +ResourceOwnerFuncs ResourceReleaseCallback ResourceReleaseCallbackItem ResourceReleasePhase -- 2.29.2 --------------F30A5443A5D524A5CBC51AE7 Content-Type: text/x-patch; charset=UTF-8; name="v6-0003-Optimize-hash-function.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="v6-0003-Optimize-hash-function.patch" ^ permalink raw reply [nested|flat] 2+ messages in thread
* Re: Using LibPq in TAP tests via FFI @ 2024-06-14 15:11 Andrew Dunstan <[email protected]> 0 siblings, 0 replies; 2+ messages in thread From: Andrew Dunstan @ 2024-06-14 15:11 UTC (permalink / raw) To: PostgreSQL Hackers <[email protected]> On 2024-06-14 Fr 11:09, Andrew Dunstan wrote: > Over at [1] Andres expressed enthusiasm for enabling TAP tests to call > LibPQ directly via FFI, and there was some support from others as > well. Attached is a very rough POC for just that.There are two perl > modules, one which wraps libpq (or almost all of it) in perl, and > another which uses that module to create a session object that can be > used to run SQL. Also in the patch is a modification of one TAP test > (arbitrarily chosen as src/bin/pg_amcheck/t/004_verify_heapam.p) to > use the new interface, so it doesn't use psql at all. > > There's a bunch of work to do here, but for a morning's work it's not > too bad :-) Luckily I had most of the first file already to hand. > > Next I plan to look at some of the recovery tests and other uses of > background_psql, which might be more challenging,a dn require > extension of the session API. Also there's a lot of error checking and > documentation that need to be added. > > > cheers > > > andrew > > > [1] > https://postgr.es/m/[email protected] > > And here's the patch cheers andrew -- Andrew Dunstan EDB: https://www.enterprisedb.com Attachments: [text/x-patch] pqffi-poc.patch (23.2K, ../../[email protected]/2-pqffi-poc.patch) download | inline diff: diff --git a/src/bin/pg_amcheck/t/004_verify_heapam.pl b/src/bin/pg_amcheck/t/004_verify_heapam.pl index f6d2c5f787..c47ff3f939 100644 --- a/src/bin/pg_amcheck/t/004_verify_heapam.pl +++ b/src/bin/pg_amcheck/t/004_verify_heapam.pl @@ -5,6 +5,7 @@ use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Test::More; @@ -190,16 +191,17 @@ $node->append_conf('postgresql.conf', 'max_prepared_transactions=10'); $node->start; my $port = $node->port; my $pgdata = $node->data_dir; -$node->safe_psql('postgres', "CREATE EXTENSION amcheck"); -$node->safe_psql('postgres', "CREATE EXTENSION pageinspect"); +my $session = PostgreSQL::Test::Session->new(node => $node); +$session->do("CREATE EXTENSION amcheck"); +$session->do("CREATE EXTENSION pageinspect"); # Get a non-zero datfrozenxid -$node->safe_psql('postgres', qq(VACUUM FREEZE)); +$session->do(qq(VACUUM FREEZE)); # Create the test table with precisely the schema that our corruption function # expects. -$node->safe_psql( - 'postgres', qq( +$session->do( + qq( CREATE TABLE public.test (a BIGINT, b TEXT, c TEXT); ALTER TABLE public.test SET (autovacuum_enabled=false); ALTER TABLE public.test ALTER COLUMN c SET STORAGE EXTERNAL; @@ -209,14 +211,15 @@ $node->safe_psql( # We want (0 < datfrozenxid < test.relfrozenxid). To achieve this, we freeze # an otherwise unused table, public.junk, prior to inserting data and freezing # public.test -$node->safe_psql( - 'postgres', qq( +$session->do( + qq( CREATE TABLE public.junk AS SELECT 'junk'::TEXT AS junk_column; ALTER TABLE public.junk SET (autovacuum_enabled=false); - VACUUM FREEZE public.junk - )); + ), + 'VACUUM FREEZE public.junk' +); -my $rel = $node->safe_psql('postgres', +my $rel = $session->query_oneval( qq(SELECT pg_relation_filepath('public.test'))); my $relpath = "$pgdata/$rel"; @@ -229,23 +232,24 @@ my $ROWCOUNT_BASIC = 16; # First insert data needed for tests unrelated to update chain validation. # Then freeze the page. These tuples are at offset numbers 1 to 16. -$node->safe_psql( - 'postgres', qq( +$session->do( + qq( INSERT INTO public.test (a, b, c) SELECT x'DEADF9F9DEADF9F9'::bigint, 'abcdefg', repeat('w', 10000) FROM generate_series(1, $ROWCOUNT_BASIC); - VACUUM FREEZE public.test;) + ), + 'VACUUM FREEZE public.test' ); # Create some simple HOT update chains for line pointer validation. After # the page is HOT pruned, we'll have two redirects line pointers each pointing # to a tuple. We'll then change the second redirect to point to the same # tuple as the first one and verify that we can detect corruption. -$node->safe_psql( - 'postgres', qq( +$session->do( + qq( INSERT INTO public.test (a, b, c) VALUES ( x'DEADF9F9DEADF9F9'::bigint, 'abcdefg', generate_series(1,2)); -- offset numbers 17 and 18 @@ -254,8 +258,8 @@ $node->safe_psql( )); # Create some more HOT update chains. -$node->safe_psql( - 'postgres', qq( +$session->do( + qq( INSERT INTO public.test (a, b, c) VALUES ( x'DEADF9F9DEADF9F9'::bigint, 'abcdefg', generate_series(3,6)); -- offset numbers 21 through 24 @@ -264,25 +268,29 @@ $node->safe_psql( )); # Negative test case of HOT-pruning with aborted tuple. -$node->safe_psql( - 'postgres', qq( +$session->do( + qq( BEGIN; UPDATE public.test SET c = 'a' WHERE c = '5'; -- offset number 27 ABORT; - VACUUM FREEZE public.test; - )); + ), + 'VACUUM FREEZE public.test;', + ); # Next update on any tuple will be stored at the same place of tuple inserted # by aborted transaction. This should not cause the table to appear corrupt. -$node->safe_psql( - 'postgres', qq( +$session->do( + qq( + BEGIN; UPDATE public.test SET c = 'a' WHERE c = '6'; -- offset number 27 again + COMMIT; VACUUM FREEZE public.test; )); # Data for HOT chain validation, so not calling VACUUM FREEZE. -$node->safe_psql( - 'postgres', qq( +$session->do( + qq( + BEGIN; INSERT INTO public.test (a, b, c) VALUES ( x'DEADF9F9DEADF9F9'::bigint, 'abcdefg', generate_series(7,15)); -- offset numbers 28 to 36 @@ -293,11 +301,12 @@ $node->safe_psql( UPDATE public.test SET c = 'a' WHERE c = '13'; -- offset number 41 UPDATE public.test SET c = 'a' WHERE c = '14'; -- offset number 42 UPDATE public.test SET c = 'a' WHERE c = '15'; -- offset number 43 + COMMIT; )); # Need one aborted transaction to test corruption in HOT chains. -$node->safe_psql( - 'postgres', qq( +$session->do( + qq( BEGIN; UPDATE public.test SET c = 'a' WHERE c = '9'; -- offset number 44 ABORT; @@ -306,19 +315,19 @@ $node->safe_psql( # Need one in-progress transaction to test few corruption in HOT chains. # We are creating PREPARE TRANSACTION here as these will not be aborted # even if we stop the node. -$node->safe_psql( - 'postgres', qq( +$session->do( + qq( BEGIN; PREPARE TRANSACTION 'in_progress_tx'; )); -my $in_progress_xid = $node->safe_psql( - 'postgres', qq( +my $in_progress_xid = $session->query_oneval( + qq( SELECT transaction FROM pg_prepared_xacts; )); -my $relfrozenxid = $node->safe_psql('postgres', +my $relfrozenxid = $session->query_oneval( q(select relfrozenxid from pg_class where relname = 'test')); -my $datfrozenxid = $node->safe_psql('postgres', +my $datfrozenxid = $session->query_oneval( q(select datfrozenxid from pg_database where datname = 'postgres')); # Sanity check that our 'test' table has a relfrozenxid newer than the @@ -326,6 +335,7 @@ my $datfrozenxid = $node->safe_psql('postgres', # first normal xid. We rely on these invariants in some of our tests. if ($datfrozenxid <= 3 || $datfrozenxid >= $relfrozenxid) { + $session->close; $node->clean_node; plan skip_all => "Xid thresholds not as expected: got datfrozenxid = $datfrozenxid, relfrozenxid = $relfrozenxid"; @@ -334,17 +344,21 @@ if ($datfrozenxid <= 3 || $datfrozenxid >= $relfrozenxid) # Find where each of the tuples is located on the page. If a particular # line pointer is a redirect rather than a tuple, we record the offset as -1. -my @lp_off = split '\n', $node->safe_psql( - 'postgres', qq( +my $lp_off_res = $session->query( + qq( SELECT CASE WHEN lp_flags = 2 THEN -1 ELSE lp_off END FROM heap_page_items(get_raw_page('test', 'main', 0)) ) -); + ); +my @lp_off; +push(@lp_off, $_->[0]) foreach @{$lp_off_res->{rows}}; + scalar @lp_off == $ROWCOUNT or BAIL_OUT("row offset counts mismatch"); # Sanity check that our 'test' table on disk layout matches expectations. If # this is not so, we will have to skip the test until somebody updates the test # to work on this platform. +$session->close; $node->stop; my $file; open($file, '+<', $relpath) @@ -750,17 +764,19 @@ for (my $tupidx = 0; $tupidx < $ROWCOUNT; $tupidx++) close($file) or BAIL_OUT("close failed: $!"); $node->start; +$session->reconnect; # Run pg_amcheck against the corrupt table with epoch=0, comparing actual # corruption messages against the expected messages $node->command_checks_all( [ 'pg_amcheck', '--no-dependent-indexes', '-p', $port, 'postgres' ], 2, [@expected], [], 'Expected corruption message output'); -$node->safe_psql( - 'postgres', qq( +$session->do( + qq( COMMIT PREPARED 'in_progress_tx'; )); +$session->close; $node->teardown_node; $node->clean_node; diff --git a/src/test/perl/PostgreSQL/PqFFI.pm b/src/test/perl/PostgreSQL/PqFFI.pm new file mode 100644 index 0000000000..fac544d32c --- /dev/null +++ b/src/test/perl/PostgreSQL/PqFFI.pm @@ -0,0 +1,564 @@ + +############################################ +# +# FFI wrapper for libpq +# +############################################ +package PostgreSQL::PqFFI; + +use strict; +use warnings FATAL => 'all'; + +use FFI::Platypus; +use FFI::CheckLib; + +use Exporter qw(import); + +our @EXPORT = qw ( + + CONNECTION_OK + CONNECTION_BAD + CONNECTION_STARTED + CONNECTION_MADE + CONNECTION_AWAITING_RESPONSE + CONNECTION_AUTH_OK + CONNECTION_SETENV + CONNECTION_SSL_STARTUP + CONNECTION_NEEDED + CONNECTION_CHECK_WRITABLE + CONNECTION_CONSUME + CONNECTION_GSS_STARTUP + CONNECTION_CHECK_TARGET + CONNECTION_CHECK_STANDBY + + PGRES_EMPTY_QUERY + PGRES_COMMAND_OK + PGRES_TUPLES_OK + PGRES_COPY_OUT + PGRES_COPY_IN + PGRES_BAD_RESPONSE + PGRES_NONFATAL_ERROR + PGRES_FATAL_ERROR + PGRES_COPY_BOTH + PGRES_SINGLE_TUPLE + PGRES_PIPELINE_SYNC + PGRES_PIPELINE_ABORTED + + PQPING_OK + PQPING_REJECT + PQPING_NO_RESPONSE + PQPING_NO_ATTEMPT + + PQTRANS_IDLE + PQTRANS_ACTIVE + PQTRANS_INTRANS + PQTRANS_INERROR + PQTRANS_UNKNOWN + + BOOLOID + BYTEAOID + CHAROID + NAMEOID + INT8OID + INT2OID + INT2VECTOROID + INT4OID + TEXTOID + OIDOID + TIDOID + XIDOID + CIDOID + OIDVECTOROID + JSONOID + XMLOID + XID8OID + POINTOID + LSEGOID + PATHOID + BOXOID + POLYGONOID + LINEOID + FLOAT4OID + FLOAT8OID + UNKNOWNOID + CIRCLEOID + MONEYOID + MACADDROID + INETOID + CIDROID + MACADDR8OID + ACLITEMOID + BPCHAROID + VARCHAROID + DATEOID + TIMEOID + TIMESTAMPOID + TIMESTAMPTZOID + INTERVALOID + TIMETZOID + BITOID + VARBITOID + NUMERICOID + REFCURSOROID + UUIDOID + TSVECTOROID + GTSVECTOROID + TSQUERYOID + JSONBOID + JSONPATHOID + TXID_SNAPSHOTOID + INT4RANGEOID + NUMRANGEOID + TSRANGEOID + TSTZRANGEOID + DATERANGEOID + INT8RANGEOID + INT4MULTIRANGEOID + NUMMULTIRANGEOID + TSMULTIRANGEOID + TSTZMULTIRANGEOID + DATEMULTIRANGEOID + INT8MULTIRANGEOID + RECORDOID + RECORDARRAYOID + CSTRINGOID + VOIDOID + TRIGGEROID + EVENT_TRIGGEROID + BOOLARRAYOID + BYTEAARRAYOID + CHARARRAYOID + NAMEARRAYOID + INT8ARRAYOID + INT2ARRAYOID + INT2VECTORARRAYOID + INT4ARRAYOID + TEXTARRAYOID + OIDARRAYOID + TIDARRAYOID + XIDARRAYOID + CIDARRAYOID + OIDVECTORARRAYOID + JSONARRAYOID + XMLARRAYOID + XID8ARRAYOID + POINTARRAYOID + LSEGARRAYOID + PATHARRAYOID + BOXARRAYOID + POLYGONARRAYOID + LINEARRAYOID + FLOAT4ARRAYOID + FLOAT8ARRAYOID + CIRCLEARRAYOID + MONEYARRAYOID + MACADDRARRAYOID + INETARRAYOID + CIDRARRAYOID + MACADDR8ARRAYOID + ACLITEMARRAYOID + BPCHARARRAYOID + VARCHARARRAYOID + DATEARRAYOID + TIMEARRAYOID + TIMESTAMPARRAYOID + TIMESTAMPTZARRAYOID + INTERVALARRAYOID + TIMETZARRAYOID + BITARRAYOID + VARBITARRAYOID + NUMERICARRAYOID + REFCURSORARRAYOID + UUIDARRAYOID + TSVECTORARRAYOID + GTSVECTORARRAYOID + TSQUERYARRAYOID + JSONBARRAYOID + JSONPATHARRAYOID + TXID_SNAPSHOTARRAYOID + INT4RANGEARRAYOID + NUMRANGEARRAYOID + TSRANGEARRAYOID + TSTZRANGEARRAYOID + DATERANGEARRAYOID + INT8RANGEARRAYOID + INT4MULTIRANGEARRAYOID + NUMMULTIRANGEARRAYOID + TSMULTIRANGEARRAYOID + TSTZMULTIRANGEARRAYOID + DATEMULTIRANGEARRAYOID + INT8MULTIRANGEARRAYOID + CSTRINGARRAYOID + +); + +# connection status + +use constant { + CONNECTION_OK => 0, + CONNECTION_BAD => 1, + # Non-blocking mode only below here + + CONNECTION_STARTED => 2, + CONNECTION_MADE => 3, + CONNECTION_AWAITING_RESPONSE => 4, + CONNECTION_AUTH_OK => 5, + CONNECTION_SETENV => 6, + CONNECTION_SSL_STARTUP => 7, + CONNECTION_NEEDED => 8, + CONNECTION_CHECK_WRITABLE => 9, + CONNECTION_CONSUME => 10, + CONNECTION_GSS_STARTUP => 11, + CONNECTION_CHECK_TARGET => 12, + CONNECTION_CHECK_STANDBY => 13, +}; + +# exec status + +use constant { + PGRES_EMPTY_QUERY => 0, + PGRES_COMMAND_OK => 1, + PGRES_TUPLES_OK => 2, + PGRES_COPY_OUT => 3, + PGRES_COPY_IN => 4, + PGRES_BAD_RESPONSE => 5, + PGRES_NONFATAL_ERROR => 6, + PGRES_FATAL_ERROR => 7, + PGRES_COPY_BOTH => 8, + PGRES_SINGLE_TUPLE => 9, + PGRES_PIPELINE_SYNC => 10, + PGRES_PIPELINE_ABORTED => 11, +}; + +# ping status + +use constant { + PQPING_OK => 0, + PQPING_REJECT => 1, + PQPING_NO_RESPONSE => 2, + PQPING_NO_ATTEMPT => 3, +}; + +# txn status +use constant { + PQTRANS_IDLE => 0, + PQTRANS_ACTIVE => 1, + PQTRANS_INTRANS => 2, + PQTRANS_INERROR => 3, + PQTRANS_UNKNOWN => 4, +}; + +# type oids +use constant { + BOOLOID => 16, + BYTEAOID => 17, + CHAROID => 18, + NAMEOID => 19, + INT8OID => 20, + INT2OID => 21, + INT2VECTOROID => 22, + INT4OID => 23, + TEXTOID => 25, + OIDOID => 26, + TIDOID => 27, + XIDOID => 28, + CIDOID => 29, + OIDVECTOROID => 30, + JSONOID => 114, + XMLOID => 142, + XID8OID => 5069, + POINTOID => 600, + LSEGOID => 601, + PATHOID => 602, + BOXOID => 603, + POLYGONOID => 604, + LINEOID => 628, + FLOAT4OID => 700, + FLOAT8OID => 701, + UNKNOWNOID => 705, + CIRCLEOID => 718, + MONEYOID => 790, + MACADDROID => 829, + INETOID => 869, + CIDROID => 650, + MACADDR8OID => 774, + ACLITEMOID => 1033, + BPCHAROID => 1042, + VARCHAROID => 1043, + DATEOID => 1082, + TIMEOID => 1083, + TIMESTAMPOID => 1114, + TIMESTAMPTZOID => 1184, + INTERVALOID => 1186, + TIMETZOID => 1266, + BITOID => 1560, + VARBITOID => 1562, + NUMERICOID => 1700, + REFCURSOROID => 1790, + UUIDOID => 2950, + TSVECTOROID => 3614, + GTSVECTOROID => 3642, + TSQUERYOID => 3615, + JSONBOID => 3802, + JSONPATHOID => 4072, + TXID_SNAPSHOTOID => 2970, + INT4RANGEOID => 3904, + NUMRANGEOID => 3906, + TSRANGEOID => 3908, + TSTZRANGEOID => 3910, + DATERANGEOID => 3912, + INT8RANGEOID => 3926, + INT4MULTIRANGEOID => 4451, + NUMMULTIRANGEOID => 4532, + TSMULTIRANGEOID => 4533, + TSTZMULTIRANGEOID => 4534, + DATEMULTIRANGEOID => 4535, + INT8MULTIRANGEOID => 4536, + RECORDOID => 2249, + RECORDARRAYOID => 2287, + CSTRINGOID => 2275, + VOIDOID => 2278, + TRIGGEROID => 2279, + EVENT_TRIGGEROID => 3838, + BOOLARRAYOID => 1000, + BYTEAARRAYOID => 1001, + CHARARRAYOID => 1002, + NAMEARRAYOID => 1003, + INT8ARRAYOID => 1016, + INT2ARRAYOID => 1005, + INT2VECTORARRAYOID => 1006, + INT4ARRAYOID => 1007, + TEXTARRAYOID => 1009, + OIDARRAYOID => 1028, + TIDARRAYOID => 1010, + XIDARRAYOID => 1011, + CIDARRAYOID => 1012, + OIDVECTORARRAYOID => 1013, + JSONARRAYOID => 199, + XMLARRAYOID => 143, + XID8ARRAYOID => 271, + POINTARRAYOID => 1017, + LSEGARRAYOID => 1018, + PATHARRAYOID => 1019, + BOXARRAYOID => 1020, + POLYGONARRAYOID => 1027, + LINEARRAYOID => 629, + FLOAT4ARRAYOID => 1021, + FLOAT8ARRAYOID => 1022, + CIRCLEARRAYOID => 719, + MONEYARRAYOID => 791, + MACADDRARRAYOID => 1040, + INETARRAYOID => 1041, + CIDRARRAYOID => 651, + MACADDR8ARRAYOID => 775, + ACLITEMARRAYOID => 1034, + BPCHARARRAYOID => 1014, + VARCHARARRAYOID => 1015, + DATEARRAYOID => 1182, + TIMEARRAYOID => 1183, + TIMESTAMPARRAYOID => 1115, + TIMESTAMPTZARRAYOID => 1185, + INTERVALARRAYOID => 1187, + TIMETZARRAYOID => 1270, + BITARRAYOID => 1561, + VARBITARRAYOID => 1563, + NUMERICARRAYOID => 1231, + REFCURSORARRAYOID => 2201, + UUIDARRAYOID => 2951, + TSVECTORARRAYOID => 3643, + GTSVECTORARRAYOID => 3644, + TSQUERYARRAYOID => 3645, + JSONBARRAYOID => 3807, + JSONPATHARRAYOID => 4073, + TXID_SNAPSHOTARRAYOID => 2949, + INT4RANGEARRAYOID => 3905, + NUMRANGEARRAYOID => 3907, + TSRANGEARRAYOID => 3909, + TSTZRANGEARRAYOID => 3911, + DATERANGEARRAYOID => 3913, + INT8RANGEARRAYOID => 3927, + INT4MULTIRANGEARRAYOID => 6150, + NUMMULTIRANGEARRAYOID => 6151, + TSMULTIRANGEARRAYOID => 6152, + TSTZMULTIRANGEARRAYOID => 6153, + DATEMULTIRANGEARRAYOID => 6155, + INT8MULTIRANGEARRAYOID => 6157, + CSTRINGARRAYOID => 1263, +}; + + + +my @procs = qw( + + PQconnectdb + PQconnectdbParams + PQsetdbLogin + PQfinish + PQreset + PQdb + PQuser + PQpass + PQhost + PQhostaddr + PQport + PQtty + PQoptions + PQstatus + PQtransactionStatus + PQparameterStatus + PQping + PQpingParams + + PQexec + PQexecParams + PQprepare + PQexecPrepared + + PQdescribePrepared + PQdescribePortal + + PQclosePrepared + PQclosePortal + PQclear + + PQprotocolVersion + PQserverVersion + PQerrorMessage + PQsocket + PQbackendPID + PQconnectionNeedsPassword + PQconnectionUsedPassword + PQconnectionUsedGSSAPI + PQclientEncoding + PQsetClientEncoding + + PQresultStatus + PQresStatus + PQresultErrorMessage + PQresultErrorField + PQntuples + PQnfields + PQbinaryTuples + PQfname + PQfnumber + PQftable + PQftablecol + PQfformat + PQftype + PQfsize + PQfmod + PQcmdStatus + PQoidValue + PQcmdTuples + PQgetvalue + PQgetlength + PQgetisnull + PQnparams + PQparamtype + +); + +push(@EXPORT, @procs); + +sub setup +{ + my $libdir = shift; + + my $ffi = FFI::Platypus->new(api => 1); + + $ffi->type('opaque' => 'PGconn'); + $ffi->type('opaque' => 'PGresult'); + $ffi->type('uint32' => 'Oid'); + $ffi->type('int' => 'ExecStatusType'); + + my $lib = find_lib_or_die( + lib => 'pq', + libpath => [$libdir], + systempath => [],); + $ffi->lib($lib); + + $ffi->attach('PQconnectdb' => ['string'] => 'PGconn'); + $ffi->attach( + 'PQconnectdbParams' => [ 'string[]', 'string[]', 'int' ] => 'PGconn'); + $ffi->attach( + 'PQsetdbLogin' => [ + 'string', 'string', 'string', 'string', + 'string', 'string', 'string', + ] => 'PGconn'); + $ffi->attach('PQfinish' => ['PGconn'] => 'void'); + $ffi->attach('PQreset' => ['PGconn'] => 'void'); + $ffi->attach('PQdb' => ['PGconn'] => 'string'); + $ffi->attach('PQuser' => ['PGconn'] => 'string'); + $ffi->attach('PQpass' => ['PGconn'] => 'string'); + $ffi->attach('PQhost' => ['PGconn'] => 'string'); + $ffi->attach('PQhostaddr' => ['PGconn'] => 'string'); + $ffi->attach('PQport' => ['PGconn'] => 'string'); + $ffi->attach('PQtty' => ['PGconn'] => 'string'); + $ffi->attach('PQoptions' => ['PGconn'] => 'string'); + $ffi->attach('PQstatus' => ['PGconn'] => 'int'); + $ffi->attach('PQtransactionStatus' => ['PGconn'] => 'int'); + $ffi->attach('PQparameterStatus' => [ 'PGconn', 'string' ] => 'string'); + $ffi->attach('PQping' => ['string'] => 'int'); + $ffi->attach( + 'PQpingParams' => [ 'string[]', 'string[]', 'int' ] => 'int'); + + $ffi->attach('PQprotocolVersion' => ['PGconn'] => 'int'); + $ffi->attach('PQserverVersion' => ['PGconn'] => 'int'); + $ffi->attach('PQerrorMessage' => ['PGconn'] => 'string'); + $ffi->attach('PQsocket' => ['PGconn'] => 'int'); + $ffi->attach('PQbackendPID' => ['PGconn'] => 'int'); + $ffi->attach('PQconnectionNeedsPassword' => ['PGconn'] => 'int'); + $ffi->attach('PQconnectionUsedPassword' => ['PGconn'] => 'int'); + $ffi->attach('PQconnectionUsedGSSAPI' => ['PGconn'] => 'int'); + $ffi->attach('PQclientEncoding' => ['PGconn'] => 'int'); + $ffi->attach('PQsetClientEncoding' => [ 'PGconn', 'string' ] => 'int'); + + $ffi->attach('PQexec' => [ 'PGconn', 'string' ] => 'PGresult'); + $ffi->attach( + 'PQexecParams' => [ + 'PGconn', 'string', 'int', 'int[]', + 'string[]', 'int[]', 'int[]', 'int' + ] => 'PGresult'); + $ffi->attach( + 'PQprepare' => [ 'PGconn', 'string', 'string', 'int', 'int[]' ] => + 'PGresult'); + $ffi->attach( + 'PQexecPrepared' => [ 'PGconn', 'string', 'int', + 'string[]', 'int[]', 'int[]', 'int' ] => 'PGresult'); + + $ffi->attach('PQresultStatus' => ['PGresult'] => 'ExecStatusType'); + $ffi->attach('PQresStatus' => ['ExecStatusType'] => 'string'); + $ffi->attach('PQresultErrorMessage' => ['PGresult'] => 'string'); + $ffi->attach('PQresultErrorField' => [ 'PGresult', 'int' ] => 'string'); + $ffi->attach('PQntuples' => ['PGresult'] => 'int'); + $ffi->attach('PQnfields' => ['PGresult'] => 'int'); + $ffi->attach('PQbinaryTuples' => ['PGresult'] => 'int'); + $ffi->attach('PQfname' => [ 'PGresult', 'int' ] => 'string'); + $ffi->attach('PQfnumber' => [ 'PGresult', 'string' ] => 'int'); + $ffi->attach('PQftable' => [ 'PGresult', 'int' ] => 'Oid'); + $ffi->attach('PQftablecol' => [ 'PGresult', 'int' ] => 'int'); + $ffi->attach('PQfformat' => [ 'PGresult', 'int' ] => 'int'); + $ffi->attach('PQftype' => [ 'PGresult', 'int' ] => 'Oid'); + $ffi->attach('PQfsize' => [ 'PGresult', 'int' ] => 'int'); + $ffi->attach('PQfmod' => [ 'PGresult', 'int' ] => 'int'); + $ffi->attach('PQcmdStatus' => ['PGresult'] => 'string'); + $ffi->attach('PQoidValue' => ['PGresult'] => 'Oid'); + $ffi->attach('PQcmdTuples' => ['PGresult'] => 'string'); + $ffi->attach('PQgetvalue' => [ 'PGresult', 'int', 'int' ] => 'string'); + $ffi->attach('PQgetlength' => [ 'PGresult', 'int', 'int' ] => 'int'); + $ffi->attach('PQgetisnull' => [ 'PGresult', 'int', 'int' ] => 'int'); + $ffi->attach('PQnparams' => ['PGresult'] => 'int'); + $ffi->attach('PQparamtype' => [ 'PGresult', 'int' ] => 'Oid'); + + + $ffi->attach( + 'PQdescribePrepared' => [ 'PGconn', 'string' ] => 'PGresult'); + $ffi->attach('PQdescribePortal' => [ 'PGconn', 'string' ] => 'PGresult'); + + $ffi->attach('PQclosePrepared' => [ 'PGconn', 'string' ] => 'PGresult'); + $ffi->attach('PQclosePortal' => [ 'PGconn', 'string' ] => 'PGresult'); + $ffi->attach('PQclear' => ['PGresult'] => 'void'); +} + + +1; diff --git a/src/test/perl/PostgreSQL/Test/Session.pm b/src/test/perl/PostgreSQL/Test/Session.pm new file mode 100644 index 0000000000..74443a9ee5 --- /dev/null +++ b/src/test/perl/PostgreSQL/Test/Session.pm @@ -0,0 +1,143 @@ +package PostgreSQL::Test::Session; + +use strict; +use warnings FATAL => 'all'; + + +use PostgreSQL::PqFFI; + +my $setup_ok; + +sub setup +{ + return if $setup_ok; + my $libdir = shift; + PostgreSQL::PqFFI::setup($libdir); + $setup_ok = 1; +} + +sub new +{ + my $class = shift; + my $self = {}; + bless $self, $class; + my %args = @_; + my $node = $args{node}; + my $dbname = $args{dbname} || 'postgres'; + die "bad node" unless $node->isa("PostgreSQL::Test::Cluster"); + unless ($setup_ok) + { + my $libdir = $node->config_data('--libdir'); + setup($libdir); + } + $self->{connstr} = $node->connstr($dbname); + $self->{conn} = PQconnectdb($self->{connstr}); + return $self; +} + +sub close +{ + my $self = shift; + PQfinish($self->{conn}); + delete $self->{conn}; +} + +sub DESTROY +{ + my $self = shift; + $self->close if $self->{conn}; +} + +sub reconnect +{ + my $self = shift; + $self->close if $self->{conn}; + $self->{conn} = PQconnectdb($self->{connstr}); +} + +# run some sql which doesn't return tuples + +sub do +{ + my $self = shift; + my $conn = $self->{conn}; + foreach my $sql (@_) + { + my $result = PQexec($conn, $sql); + my $ok = $result && (PQresultStatus($result) == PGRES_COMMAND_OK); + PQclear($result); + return undef unless $ok; + } + return 1; +} + +# run some sql that does return tuples + +sub query +{ + my $self = shift; + my $sql = shift; + my $conn = $self->{conn}; + my $result = PQexec($conn, $sql); + my $ok = $result && (PQresultStatus($result) == PGRES_TUPLES_OK); + unless ($ok) + { + PQclear($result); + return undef; + } + my $ntuples = PQntuples($result); + my $nfields = PQnfields($result); + my $res = { names => [], types => [], rows => [], }; + # assuming here that the strings returned by PQfname and PQgetvalue + # are mapped into perl space using setsvpv or similar and thus won't + # be affect by us calling PQclear on the result object. + foreach my $field (0 .. $nfields-1) + { + push(@{$res->{names}}, PQfname($result, $field)); + push(@{$res->{types}}, PQftype($result, $field)); + } + foreach my $nrow (0.. $ntuples - 1) + { + my $row = []; + foreach my $field ( 0 .. $nfields) + { + my $val = PQgetvalue($result, $nrow, $field); + if (($val // "") eq "") + { + $val = undef if PQgetisnull($result, $nrow, $field); + } + push(@$row, $val); + } + push(@{$res->{rows}}, $row); + } + PQclear($result); + return $res; +} + +sub query_oneval +{ + my $self = shift; + my $sql = shift; + my $conn = $self->{conn}; + my $result = PQexec($conn, $sql); + my $ok = $result && (PQresultStatus($result) == PGRES_TUPLES_OK); + unless ($ok) + { + PQclear($result); + return undef; + } + my $ntuples = PQntuples($result); + my $nfields = PQnfields($result); + die "$ntuples tuples != 1 or $nfields fields != 1" + if $ntuples != 1 || $nfields != 1; + my $val = PQgetvalue($result, 0, 0); + if ($val eq "") + { + $val = undef if PGgetisnull($result, 0, 0); + } + PQclear($result); + return $val; + +} + +1; ^ permalink raw reply [nested|flat] 2+ messages in thread
end of thread, other threads:[~2024-06-14 15:11 UTC | newest] Thread overview: 2+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2021-01-20 21:16 [PATCH v6 2/3] Make resowners more easily extensible. Heikki Linnakangas <[email protected]> 2024-06-14 15:11 Re: Using LibPq in TAP tests via FFI Andrew Dunstan <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox