From d48b69c187a802f8ac84d8ec9e6e2a5a54b7fde7 Mon Sep 17 00:00:00 2001 From: Lukas Fittl Date: Thu, 17 Jul 2025 16:50:12 -0700 Subject: [PATCH v0] Introduce pg_exclusive_locks view --- src/backend/catalog/system_views.sql | 5 ++ src/backend/storage/ipc/standby.c | 4 +- src/backend/storage/lmgr/lock.c | 13 +++-- src/backend/utils/adt/lockfuncs.c | 87 ++++++++++++++++++++++++++++ src/include/catalog/pg_proc.dat | 7 +++ src/include/storage/lock.h | 2 +- src/test/regress/expected/rules.out | 6 ++ 7 files changed, 116 insertions(+), 8 deletions(-) diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index b2d5332effc..860a0616851 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,11 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_exclusive_locks AS + SELECT L.database, L.relation, L.transactionid, A.pid + FROM pg_exclusive_lock_status() L + LEFT JOIN pg_stat_activity A ON (L.transactionid = A.backend_xid); + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c index 4222bdab078..4a31a620c52 100644 --- a/src/backend/storage/ipc/standby.c +++ b/src/backend/storage/ipc/standby.c @@ -1302,7 +1302,7 @@ LogStandbySnapshot(void) /* * Get details of any AccessExclusiveLocks being held at the moment. */ - locks = GetRunningTransactionLocks(&nlocks); + locks = GetCurrentAccessExclusiveLocks(&nlocks, true); if (nlocks > 0) LogAccessExclusiveLocks(nlocks, locks); pfree(locks); @@ -1456,7 +1456,7 @@ LogAccessExclusiveLockPrepare(void) * hack, but for a corner case not worth adding code for into the main * commit path. Second, we must assign an xid before the lock is recorded * in shared memory, otherwise a concurrently executing - * GetRunningTransactionLocks() might see a lock associated with an + * GetCurrentAccessExclusiveLocks() might see a lock associated with an * InvalidTransactionId which we later assert cannot happen. */ (void) GetCurrentTransactionId(); diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index 62f3471448e..076de9a5296 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -962,7 +962,7 @@ LockAcquireExtended(const LOCKTAG *locktag, * * Only AccessExclusiveLocks can conflict with lock types that read-only * transactions can acquire in a standby server. Make sure this definition - * matches the one in GetRunningTransactionLocks(). + * matches the one in GetCurrentAccessExclusiveLocks(). */ if (lockmode >= AccessExclusiveLock && locktag->locktag_type == LOCKTAG_RELATION && @@ -4127,8 +4127,11 @@ GetSingleProcBlockerStatusData(PGPROC *blocked_proc, BlockedProcsData *data) /* * Returns a list of currently held AccessExclusiveLocks, for use by - * LogStandbySnapshot(). The result is a palloc'd array, - * with the number of elements returned into *nlocks. + * LogStandbySnapshot(), or user level reporting. The skip_committed argument + * allows skipping locks by committed transactions that are not yet released. + * + * The result is a palloc'd array, with the number of elements returned into + * *nlocks. * * XXX This currently takes a lock on all partitions of the lock table, * but it's possible to do better. By reference counting locks and storing @@ -4138,7 +4141,7 @@ GetSingleProcBlockerStatusData(PGPROC *blocked_proc, BlockedProcsData *data) * is pretty dubious though. */ xl_standby_lock * -GetRunningTransactionLocks(int *nlocks) +GetCurrentAccessExclusiveLocks(int *nlocks, bool skip_committed) { xl_standby_lock *accessExclusiveLocks; PROCLOCK *proclock; @@ -4191,7 +4194,7 @@ GetRunningTransactionLocks(int *nlocks) * lock. It is still possible that we see locks held by already * complete transactions, if they haven't yet zeroed their xids. */ - if (!TransactionIdIsValid(xid)) + if (skip_committed && !TransactionIdIsValid(xid)) continue; accessExclusiveLocks[index].xid = xid; diff --git a/src/backend/utils/adt/lockfuncs.c b/src/backend/utils/adt/lockfuncs.c index 00e67fb46d0..388b1dbe4a4 100644 --- a/src/backend/utils/adt/lockfuncs.c +++ b/src/backend/utils/adt/lockfuncs.c @@ -65,6 +65,17 @@ typedef struct /* Number of columns in pg_locks output */ #define NUM_LOCK_STATUS_COLUMNS 16 +/* Working status for pg_exclusive_lock_status */ +typedef struct +{ + xl_standby_lock *locks; /* exclusive lock data from lmgr */ + int nlocks; /* number of exclusive locks */ + int currIdx; /* current lock data index */ +} PG_Exclusive_Lock_Status; + +/* Number of columns in pg_exclusive_lock_status output */ +#define NUM_EXCLUSIVE_LOCK_STATUS_COLUMNS 3 + /* * VXIDGetDatum - Construct a text representation of a VXID * @@ -442,6 +453,82 @@ pg_lock_status(PG_FUNCTION_ARGS) SRF_RETURN_DONE(funcctx); } +/* + * pg_exclusive_lock_status - produce a view with one row per AccessExclusiveLock currently held + */ +Datum +pg_exclusive_lock_status(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + PG_Exclusive_Lock_Status *mystatus; + + if (SRF_IS_FIRSTCALL()) + { + TupleDesc tupdesc; + MemoryContext oldcontext; + + /* create a function context for cross-call persistence */ + funcctx = SRF_FIRSTCALL_INIT(); + + /* + * switch to memory context appropriate for multiple function calls + */ + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + /* build tupdesc for result tuples */ + /* this had better match function's declaration in pg_proc.h */ + tupdesc = CreateTemplateTupleDesc(NUM_EXCLUSIVE_LOCK_STATUS_COLUMNS); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "database", + OIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "relation", + OIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "transactionid", + XIDOID, -1, 0); + + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + + /* + * Collect all the locking information that we will format and send + * out as a result set. + */ + mystatus = (PG_Exclusive_Lock_Status *) palloc(sizeof(PG_Exclusive_Lock_Status)); + funcctx->user_fctx = mystatus; + + mystatus->locks = GetCurrentAccessExclusiveLocks(&mystatus->nlocks, false); + mystatus->currIdx = 0; + + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + mystatus = (PG_Exclusive_Lock_Status *) funcctx->user_fctx; + + while (mystatus->currIdx < mystatus->nlocks) + { + Datum values[NUM_EXCLUSIVE_LOCK_STATUS_COLUMNS] = {0}; + bool nulls[NUM_EXCLUSIVE_LOCK_STATUS_COLUMNS] = {0}; + HeapTuple tuple; + Datum result; + xl_standby_lock *lock; + + lock = &(mystatus->locks[mystatus->currIdx]); + mystatus->currIdx++; + + /* + * Form tuple with appropriate data. + */ + values[0] = ObjectIdGetDatum(lock->dbOid); + values[1] = ObjectIdGetDatum(lock->relOid); + values[2] = ObjectIdGetDatum(lock->xid); + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + result = HeapTupleGetDatum(tuple); + SRF_RETURN_NEXT(funcctx, result); + } + + SRF_RETURN_DONE(funcctx); +} + /* * pg_blocking_pids - produce an array of the PIDs blocking given PID diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 1fc19146f46..16d6ed4130c 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -6543,6 +6543,13 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{locktype,database,relation,page,tuple,virtualxid,transactionid,classid,objid,objsubid,virtualtransaction,pid,mode,granted,fastpath,waitstart}', prosrc => 'pg_lock_status' }, +{ oid => '8089', descr => 'view exclusive system lock information', + proname => 'pg_exclusive_lock_status', prorows => '1000', proretset => 't', + provolatile => 'v', prorettype => 'record', proargtypes => '', + proallargtypes => '{oid,oid,xid}', + proargmodes => '{o,o,o}', + proargnames => '{database,relation,transactionid}', + prosrc => 'pg_exclusive_lock_status' }, { oid => '2561', descr => 'get array of PIDs of sessions blocking specified backend PID from acquiring a heavyweight lock', proname => 'pg_blocking_pids', provolatile => 'v', prorettype => '_int4', diff --git a/src/include/storage/lock.h b/src/include/storage/lock.h index 826cf28fdbd..c04f2b45a12 100644 --- a/src/include/storage/lock.h +++ b/src/include/storage/lock.h @@ -595,7 +595,7 @@ extern void RemoveFromWaitQueue(PGPROC *proc, uint32 hashcode); extern LockData *GetLockStatusData(void); extern BlockedProcsData *GetBlockerStatusData(int blocked_pid); -extern xl_standby_lock *GetRunningTransactionLocks(int *nlocks); +extern xl_standby_lock *GetCurrentAccessExclusiveLocks(int *nlocks, bool skip_committed); extern const char *GetLockmodeName(LOCKMETHODID lockmethodid, LOCKMODE mode); extern void lock_twophase_recover(FullTransactionId fxid, uint16 info, diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index dce8c672b40..f4c6f1a9c8b 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1344,6 +1344,12 @@ pg_dsm_registry_allocations| SELECT name, type, size FROM pg_get_dsm_registry_allocations() pg_get_dsm_registry_allocations(name, type, size); +pg_exclusive_locks| SELECT l.database, + l.relation, + l.transactionid, + a.pid + FROM (pg_exclusive_lock_status() l(database, relation, transactionid) + LEFT JOIN pg_stat_activity a ON ((l.transactionid = a.backend_xid))); pg_file_settings| SELECT sourcefile, sourceline, seqno, -- 2.47.1