mirror of
https://github.com/postgres/postgres.git
synced 2025-10-29 22:49:41 +03:00
Add GUC option to log lock acquisition failures.
This commit introduces a new GUC, log_lock_failure, which controls whether a detailed log message is produced when a lock acquisition fails. Currently, it only supports logging lock failures caused by SELECT ... NOWAIT. The log message includes information about all processes holding or waiting for the lock that couldn't be acquired, helping users analyze and diagnose the causes of lock failures. Currently, this option does not log failures from SELECT ... SKIP LOCKED, as that could generate excessive log messages if many locks are skipped, causing unnecessary noise. This mechanism can be extended in the future to support for logging lock failures from other commands, such as LOCK TABLE ... NOWAIT. Author: Yuki Seino <seinoyu@oss.nttdata.com> Co-authored-by: Fujii Masao <masao.fujii@gmail.com> Reviewed-by: Jelte Fennema-Nio <postgres@jeltef.nl> Discussion: https://postgr.es/m/411280a186cc26ef7034e0f2dfe54131@oss.nttdata.com
This commit is contained in:
@@ -112,7 +112,8 @@ LockRelationOid(Oid relid, LOCKMODE lockmode)
|
||||
|
||||
SetLocktagRelationOid(&tag, relid);
|
||||
|
||||
res = LockAcquireExtended(&tag, lockmode, false, false, true, &locallock);
|
||||
res = LockAcquireExtended(&tag, lockmode, false, false, true, &locallock,
|
||||
false);
|
||||
|
||||
/*
|
||||
* Now that we have the lock, check for invalidation messages, so that we
|
||||
@@ -155,7 +156,8 @@ ConditionalLockRelationOid(Oid relid, LOCKMODE lockmode)
|
||||
|
||||
SetLocktagRelationOid(&tag, relid);
|
||||
|
||||
res = LockAcquireExtended(&tag, lockmode, false, true, true, &locallock);
|
||||
res = LockAcquireExtended(&tag, lockmode, false, true, true, &locallock,
|
||||
false);
|
||||
|
||||
if (res == LOCKACQUIRE_NOT_AVAIL)
|
||||
return false;
|
||||
@@ -188,7 +190,8 @@ LockRelationId(LockRelId *relid, LOCKMODE lockmode)
|
||||
|
||||
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
|
||||
|
||||
res = LockAcquireExtended(&tag, lockmode, false, false, true, &locallock);
|
||||
res = LockAcquireExtended(&tag, lockmode, false, false, true, &locallock,
|
||||
false);
|
||||
|
||||
/*
|
||||
* Now that we have the lock, check for invalidation messages; see notes
|
||||
@@ -250,7 +253,8 @@ LockRelation(Relation relation, LOCKMODE lockmode)
|
||||
relation->rd_lockInfo.lockRelId.dbId,
|
||||
relation->rd_lockInfo.lockRelId.relId);
|
||||
|
||||
res = LockAcquireExtended(&tag, lockmode, false, false, true, &locallock);
|
||||
res = LockAcquireExtended(&tag, lockmode, false, false, true, &locallock,
|
||||
false);
|
||||
|
||||
/*
|
||||
* Now that we have the lock, check for invalidation messages; see notes
|
||||
@@ -281,7 +285,8 @@ ConditionalLockRelation(Relation relation, LOCKMODE lockmode)
|
||||
relation->rd_lockInfo.lockRelId.dbId,
|
||||
relation->rd_lockInfo.lockRelId.relId);
|
||||
|
||||
res = LockAcquireExtended(&tag, lockmode, false, true, true, &locallock);
|
||||
res = LockAcquireExtended(&tag, lockmode, false, true, true, &locallock,
|
||||
false);
|
||||
|
||||
if (res == LOCKACQUIRE_NOT_AVAIL)
|
||||
return false;
|
||||
@@ -574,7 +579,8 @@ LockTuple(Relation relation, ItemPointer tid, LOCKMODE lockmode)
|
||||
* Returns true iff the lock was acquired.
|
||||
*/
|
||||
bool
|
||||
ConditionalLockTuple(Relation relation, ItemPointer tid, LOCKMODE lockmode)
|
||||
ConditionalLockTuple(Relation relation, ItemPointer tid, LOCKMODE lockmode,
|
||||
bool logLockFailure)
|
||||
{
|
||||
LOCKTAG tag;
|
||||
|
||||
@@ -584,7 +590,8 @@ ConditionalLockTuple(Relation relation, ItemPointer tid, LOCKMODE lockmode)
|
||||
ItemPointerGetBlockNumber(tid),
|
||||
ItemPointerGetOffsetNumber(tid));
|
||||
|
||||
return (LockAcquire(&tag, lockmode, false, true) != LOCKACQUIRE_NOT_AVAIL);
|
||||
return (LockAcquireExtended(&tag, lockmode, false, true, true, NULL,
|
||||
logLockFailure) != LOCKACQUIRE_NOT_AVAIL);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -726,7 +733,7 @@ XactLockTableWait(TransactionId xid, Relation rel, ItemPointer ctid,
|
||||
* Returns true if the lock was acquired.
|
||||
*/
|
||||
bool
|
||||
ConditionalXactLockTableWait(TransactionId xid)
|
||||
ConditionalXactLockTableWait(TransactionId xid, bool logLockFailure)
|
||||
{
|
||||
LOCKTAG tag;
|
||||
bool first = true;
|
||||
@@ -738,7 +745,9 @@ ConditionalXactLockTableWait(TransactionId xid)
|
||||
|
||||
SET_LOCKTAG_TRANSACTION(tag, xid);
|
||||
|
||||
if (LockAcquire(&tag, ShareLock, false, true) == LOCKACQUIRE_NOT_AVAIL)
|
||||
if (LockAcquireExtended(&tag, ShareLock, false, true, true, NULL,
|
||||
logLockFailure)
|
||||
== LOCKACQUIRE_NOT_AVAIL)
|
||||
return false;
|
||||
|
||||
LockRelease(&tag, ShareLock, false);
|
||||
@@ -1027,7 +1036,8 @@ ConditionalLockDatabaseObject(Oid classid, Oid objid, uint16 objsubid,
|
||||
objid,
|
||||
objsubid);
|
||||
|
||||
res = LockAcquireExtended(&tag, lockmode, false, true, true, &locallock);
|
||||
res = LockAcquireExtended(&tag, lockmode, false, true, true, &locallock,
|
||||
false);
|
||||
|
||||
if (res == LOCKACQUIRE_NOT_AVAIL)
|
||||
return false;
|
||||
@@ -1106,7 +1116,8 @@ ConditionalLockSharedObject(Oid classid, Oid objid, uint16 objsubid,
|
||||
objid,
|
||||
objsubid);
|
||||
|
||||
res = LockAcquireExtended(&tag, lockmode, false, true, true, &locallock);
|
||||
res = LockAcquireExtended(&tag, lockmode, false, true, true, &locallock,
|
||||
false);
|
||||
|
||||
if (res == LOCKACQUIRE_NOT_AVAIL)
|
||||
return false;
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#include "access/xlogutils.h"
|
||||
#include "miscadmin.h"
|
||||
#include "pg_trace.h"
|
||||
#include "storage/lmgr.h"
|
||||
#include "storage/proc.h"
|
||||
#include "storage/procarray.h"
|
||||
#include "storage/spin.h"
|
||||
@@ -48,8 +49,9 @@
|
||||
#include "utils/resowner.h"
|
||||
|
||||
|
||||
/* This configuration variable is used to set the lock table size */
|
||||
int max_locks_per_xact; /* set by guc.c */
|
||||
/* GUC variables */
|
||||
int max_locks_per_xact; /* used to set the lock table size */
|
||||
bool log_lock_failure = false;
|
||||
|
||||
#define NLOCKENTS() \
|
||||
mul_size(max_locks_per_xact, add_size(MaxBackends, max_prepared_xacts))
|
||||
@@ -806,7 +808,7 @@ LockAcquire(const LOCKTAG *locktag,
|
||||
bool dontWait)
|
||||
{
|
||||
return LockAcquireExtended(locktag, lockmode, sessionLock, dontWait,
|
||||
true, NULL);
|
||||
true, NULL, false);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -822,6 +824,9 @@ LockAcquire(const LOCKTAG *locktag,
|
||||
*
|
||||
* If locallockp isn't NULL, *locallockp receives a pointer to the LOCALLOCK
|
||||
* table entry if a lock is successfully acquired, or NULL if not.
|
||||
*
|
||||
* logLockFailure indicates whether to log details when a lock acquisition
|
||||
* fails with dontWait = true.
|
||||
*/
|
||||
LockAcquireResult
|
||||
LockAcquireExtended(const LOCKTAG *locktag,
|
||||
@@ -829,7 +834,8 @@ LockAcquireExtended(const LOCKTAG *locktag,
|
||||
bool sessionLock,
|
||||
bool dontWait,
|
||||
bool reportMemoryError,
|
||||
LOCALLOCK **locallockp)
|
||||
LOCALLOCK **locallockp,
|
||||
bool logLockFailure)
|
||||
{
|
||||
LOCKMETHODID lockmethodid = locktag->locktag_lockmethodid;
|
||||
LockMethod lockMethodTable;
|
||||
@@ -1145,6 +1151,47 @@ LockAcquireExtended(const LOCKTAG *locktag,
|
||||
|
||||
if (dontWait)
|
||||
{
|
||||
/*
|
||||
* Log lock holders and waiters as a detail log message if
|
||||
* logLockFailure = true and lock acquisition fails with dontWait
|
||||
* = true
|
||||
*/
|
||||
if (logLockFailure)
|
||||
{
|
||||
StringInfoData buf,
|
||||
lock_waiters_sbuf,
|
||||
lock_holders_sbuf;
|
||||
const char *modename;
|
||||
int lockHoldersNum = 0;
|
||||
|
||||
initStringInfo(&buf);
|
||||
initStringInfo(&lock_waiters_sbuf);
|
||||
initStringInfo(&lock_holders_sbuf);
|
||||
|
||||
DescribeLockTag(&buf, &locallock->tag.lock);
|
||||
modename = GetLockmodeName(locallock->tag.lock.locktag_lockmethodid,
|
||||
lockmode);
|
||||
|
||||
/* Gather a list of all lock holders and waiters */
|
||||
LWLockAcquire(partitionLock, LW_SHARED);
|
||||
GetLockHoldersAndWaiters(locallock, &lock_holders_sbuf,
|
||||
&lock_waiters_sbuf, &lockHoldersNum);
|
||||
LWLockRelease(partitionLock);
|
||||
|
||||
ereport(LOG,
|
||||
(errmsg("process %d could not obtain %s on %s",
|
||||
MyProcPid, modename, buf.data),
|
||||
errdetail_log_plural(
|
||||
"Process holding the lock: %s, Wait queue: %s.",
|
||||
"Processes holding the lock: %s, Wait queue: %s.",
|
||||
lockHoldersNum,
|
||||
lock_holders_sbuf.data,
|
||||
lock_waiters_sbuf.data)));
|
||||
|
||||
pfree(buf.data);
|
||||
pfree(lock_holders_sbuf.data);
|
||||
pfree(lock_waiters_sbuf.data);
|
||||
}
|
||||
if (locallockp)
|
||||
*locallockp = NULL;
|
||||
return LOCKACQUIRE_NOT_AVAIL;
|
||||
|
||||
@@ -1514,10 +1514,6 @@ ProcSleep(LOCALLOCK *locallock)
|
||||
long secs;
|
||||
int usecs;
|
||||
long msecs;
|
||||
dlist_iter proc_iter;
|
||||
PROCLOCK *curproclock;
|
||||
bool first_holder = true,
|
||||
first_waiter = true;
|
||||
int lockHoldersNum = 0;
|
||||
|
||||
initStringInfo(&buf);
|
||||
@@ -1533,54 +1529,10 @@ ProcSleep(LOCALLOCK *locallock)
|
||||
msecs = secs * 1000 + usecs / 1000;
|
||||
usecs = usecs % 1000;
|
||||
|
||||
/*
|
||||
* we loop over the lock's procLocks to gather a list of all
|
||||
* holders and waiters. Thus we will be able to provide more
|
||||
* detailed information for lock debugging purposes.
|
||||
*
|
||||
* lock->procLocks contains all processes which hold or wait for
|
||||
* this lock.
|
||||
*/
|
||||
|
||||
/* Gather a list of all lock holders and waiters */
|
||||
LWLockAcquire(partitionLock, LW_SHARED);
|
||||
|
||||
dlist_foreach(proc_iter, &lock->procLocks)
|
||||
{
|
||||
curproclock =
|
||||
dlist_container(PROCLOCK, lockLink, proc_iter.cur);
|
||||
|
||||
/*
|
||||
* we are a waiter if myProc->waitProcLock == curproclock; we
|
||||
* are a holder if it is NULL or something different
|
||||
*/
|
||||
if (curproclock->tag.myProc->waitProcLock == curproclock)
|
||||
{
|
||||
if (first_waiter)
|
||||
{
|
||||
appendStringInfo(&lock_waiters_sbuf, "%d",
|
||||
curproclock->tag.myProc->pid);
|
||||
first_waiter = false;
|
||||
}
|
||||
else
|
||||
appendStringInfo(&lock_waiters_sbuf, ", %d",
|
||||
curproclock->tag.myProc->pid);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (first_holder)
|
||||
{
|
||||
appendStringInfo(&lock_holders_sbuf, "%d",
|
||||
curproclock->tag.myProc->pid);
|
||||
first_holder = false;
|
||||
}
|
||||
else
|
||||
appendStringInfo(&lock_holders_sbuf, ", %d",
|
||||
curproclock->tag.myProc->pid);
|
||||
|
||||
lockHoldersNum++;
|
||||
}
|
||||
}
|
||||
|
||||
GetLockHoldersAndWaiters(locallock, &lock_holders_sbuf,
|
||||
&lock_waiters_sbuf, &lockHoldersNum);
|
||||
LWLockRelease(partitionLock);
|
||||
|
||||
if (deadlock_state == DS_SOFT_DEADLOCK)
|
||||
@@ -1885,6 +1837,81 @@ CheckDeadLockAlert(void)
|
||||
errno = save_errno;
|
||||
}
|
||||
|
||||
/*
|
||||
* GetLockHoldersAndWaiters - get lock holders and waiters for a lock
|
||||
*
|
||||
* Fill lock_holders_sbuf and lock_waiters_sbuf with the PIDs of processes holding
|
||||
* and waiting for the lock, and set lockHoldersNum to the number of lock holders.
|
||||
*
|
||||
* The lock table's partition lock must be held on entry and remains held on exit.
|
||||
*/
|
||||
void
|
||||
GetLockHoldersAndWaiters(LOCALLOCK *locallock, StringInfo lock_holders_sbuf,
|
||||
StringInfo lock_waiters_sbuf, int *lockHoldersNum)
|
||||
{
|
||||
dlist_iter proc_iter;
|
||||
PROCLOCK *curproclock;
|
||||
LOCK *lock = locallock->lock;
|
||||
bool first_holder = true,
|
||||
first_waiter = true;
|
||||
|
||||
#ifdef USE_ASSERT_CHECKING
|
||||
{
|
||||
uint32 hashcode = locallock->hashcode;
|
||||
LWLock *partitionLock = LockHashPartitionLock(hashcode);
|
||||
|
||||
Assert(LWLockHeldByMe(partitionLock));
|
||||
}
|
||||
#endif
|
||||
|
||||
*lockHoldersNum = 0;
|
||||
|
||||
/*
|
||||
* Loop over the lock's procLocks to gather a list of all holders and
|
||||
* waiters. Thus we will be able to provide more detailed information for
|
||||
* lock debugging purposes.
|
||||
*
|
||||
* lock->procLocks contains all processes which hold or wait for this
|
||||
* lock.
|
||||
*/
|
||||
dlist_foreach(proc_iter, &lock->procLocks)
|
||||
{
|
||||
curproclock =
|
||||
dlist_container(PROCLOCK, lockLink, proc_iter.cur);
|
||||
|
||||
/*
|
||||
* We are a waiter if myProc->waitProcLock == curproclock; we are a
|
||||
* holder if it is NULL or something different.
|
||||
*/
|
||||
if (curproclock->tag.myProc->waitProcLock == curproclock)
|
||||
{
|
||||
if (first_waiter)
|
||||
{
|
||||
appendStringInfo(lock_waiters_sbuf, "%d",
|
||||
curproclock->tag.myProc->pid);
|
||||
first_waiter = false;
|
||||
}
|
||||
else
|
||||
appendStringInfo(lock_waiters_sbuf, ", %d",
|
||||
curproclock->tag.myProc->pid);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (first_holder)
|
||||
{
|
||||
appendStringInfo(lock_holders_sbuf, "%d",
|
||||
curproclock->tag.myProc->pid);
|
||||
first_holder = false;
|
||||
}
|
||||
else
|
||||
appendStringInfo(lock_holders_sbuf, ", %d",
|
||||
curproclock->tag.myProc->pid);
|
||||
|
||||
(*lockHoldersNum)++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ProcWaitForSignal - wait for a signal from another backend.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user