Commit f88d4cfc authored by Alvaro Herrera's avatar Alvaro Herrera

Setup error context callback for transaction lock waits

With this in place, a session blocking behind another one because of
tuple locks will get a context line mentioning the relation name, tuple
TID, and operation being done on tuple.  For example:

LOG:  process 11367 still waiting for ShareLock on transaction 717 after 1000.108 ms
DETAIL:  Process holding the lock: 11366. Wait queue: 11367.
CONTEXT:  while updating tuple (0,2) in relation "foo"
STATEMENT:  UPDATE foo SET value = 3;

Most usefully, the new line is displayed by log entries due to
log_lock_waits, although of course it will be printed by any other log
message as well.

Author: Christian Kruse, some tweaks by Álvaro Herrera
Reviewed-by: Amit Kapila, Andres Freund, Tom Lane, Robert Haas
parent ea8c7e90
...@@ -105,11 +105,12 @@ static void GetMultiXactIdHintBits(MultiXactId multi, uint16 *new_infomask, ...@@ -105,11 +105,12 @@ static void GetMultiXactIdHintBits(MultiXactId multi, uint16 *new_infomask,
uint16 *new_infomask2); uint16 *new_infomask2);
static TransactionId MultiXactIdGetUpdateXid(TransactionId xmax, static TransactionId MultiXactIdGetUpdateXid(TransactionId xmax,
uint16 t_infomask); uint16 t_infomask);
static void MultiXactIdWait(MultiXactId multi, MultiXactStatus status, static void MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask,
int *remaining, uint16 infomask); Relation rel, ItemPointer ctid, XLTW_Oper oper,
static bool ConditionalMultiXactIdWait(MultiXactId multi, int *remaining);
MultiXactStatus status, int *remaining, static bool ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status,
uint16 infomask); uint16 infomask, Relation rel, ItemPointer ctid,
XLTW_Oper oper, int *remaining);
static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup); static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup);
static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_modified, static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_modified,
bool *copy); bool *copy);
...@@ -2714,8 +2715,9 @@ l1: ...@@ -2714,8 +2715,9 @@ l1:
if (infomask & HEAP_XMAX_IS_MULTI) if (infomask & HEAP_XMAX_IS_MULTI)
{ {
/* wait for multixact */ /* wait for multixact */
MultiXactIdWait((MultiXactId) xwait, MultiXactStatusUpdate, MultiXactIdWait((MultiXactId) xwait, MultiXactStatusUpdate, infomask,
NULL, infomask); relation, &tp.t_data->t_ctid, XLTW_Delete,
NULL);
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
/* /*
...@@ -2741,7 +2743,7 @@ l1: ...@@ -2741,7 +2743,7 @@ l1:
else else
{ {
/* wait for regular transaction to end */ /* wait for regular transaction to end */
XactLockTableWait(xwait); XactLockTableWait(xwait, relation, &tp.t_data->t_ctid, XLTW_Delete);
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
/* /*
...@@ -3266,8 +3268,9 @@ l2: ...@@ -3266,8 +3268,9 @@ l2:
int remain; int remain;
/* wait for multixact */ /* wait for multixact */
MultiXactIdWait((MultiXactId) xwait, mxact_status, &remain, MultiXactIdWait((MultiXactId) xwait, mxact_status, infomask,
infomask); relation, &oldtup.t_data->t_ctid, XLTW_Update,
&remain);
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
/* /*
...@@ -3341,7 +3344,8 @@ l2: ...@@ -3341,7 +3344,8 @@ l2:
else else
{ {
/* wait for regular transaction to end */ /* wait for regular transaction to end */
XactLockTableWait(xwait); XactLockTableWait(xwait, relation, &oldtup.t_data->t_ctid,
XLTW_Update);
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
/* /*
...@@ -4402,14 +4406,18 @@ l3: ...@@ -4402,14 +4406,18 @@ l3:
if (nowait) if (nowait)
{ {
if (!ConditionalMultiXactIdWait((MultiXactId) xwait, if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
status, NULL, infomask)) status, infomask, relation,
&tuple->t_data->t_ctid,
XLTW_Lock, NULL))
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_LOCK_NOT_AVAILABLE), (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
errmsg("could not obtain lock on row in relation \"%s\"", errmsg("could not obtain lock on row in relation \"%s\"",
RelationGetRelationName(relation)))); RelationGetRelationName(relation))));
} }
else else
MultiXactIdWait((MultiXactId) xwait, status, NULL, infomask); MultiXactIdWait((MultiXactId) xwait, status, infomask,
relation, &tuple->t_data->t_ctid,
XLTW_Lock, NULL);
/* if there are updates, follow the update chain */ /* if there are updates, follow the update chain */
if (follow_updates && if (follow_updates &&
...@@ -4464,7 +4472,8 @@ l3: ...@@ -4464,7 +4472,8 @@ l3:
RelationGetRelationName(relation)))); RelationGetRelationName(relation))));
} }
else else
XactLockTableWait(xwait); XactLockTableWait(xwait, relation, &tuple->t_data->t_ctid,
XLTW_Lock);
/* if there are updates, follow the update chain */ /* if there are updates, follow the update chain */
if (follow_updates && if (follow_updates &&
...@@ -5151,7 +5160,9 @@ l4: ...@@ -5151,7 +5160,9 @@ l4:
if (needwait) if (needwait)
{ {
LockBuffer(buf, BUFFER_LOCK_UNLOCK); LockBuffer(buf, BUFFER_LOCK_UNLOCK);
XactLockTableWait(members[i].xid); XactLockTableWait(members[i].xid, rel,
&mytup.t_data->t_ctid,
XLTW_LockUpdated);
pfree(members); pfree(members);
goto l4; goto l4;
} }
...@@ -5211,7 +5222,8 @@ l4: ...@@ -5211,7 +5222,8 @@ l4:
if (needwait) if (needwait)
{ {
LockBuffer(buf, BUFFER_LOCK_UNLOCK); LockBuffer(buf, BUFFER_LOCK_UNLOCK);
XactLockTableWait(rawxmax); XactLockTableWait(rawxmax, rel, &mytup.t_data->t_ctid,
XLTW_LockUpdated);
goto l4; goto l4;
} }
if (res != HeapTupleMayBeUpdated) if (res != HeapTupleMayBeUpdated)
...@@ -6076,6 +6088,15 @@ HeapTupleGetUpdateXid(HeapTupleHeader tuple) ...@@ -6076,6 +6088,15 @@ HeapTupleGetUpdateXid(HeapTupleHeader tuple)
* Do_MultiXactIdWait * Do_MultiXactIdWait
* Actual implementation for the two functions below. * Actual implementation for the two functions below.
* *
* 'multi', 'status' and 'infomask' indicate what to sleep on (the status is
* needed to ensure we only sleep on conflicting members, and the infomask is
* used to optimize multixact access in case it's a lock-only multi); 'nowait'
* indicates whether to use conditional lock acquisition, to allow callers to
* fail if lock is unavailable. 'rel', 'ctid' and 'oper' are used to set up
* context information for error messages. 'remaining', if not NULL, receives
* the number of members that are still running, including any (non-aborted)
* subtransactions of our own transaction.
*
* We do this by sleeping on each member using XactLockTableWait. Any * We do this by sleeping on each member using XactLockTableWait. Any
* members that belong to the current backend are *not* waited for, however; * members that belong to the current backend are *not* waited for, however;
* this would not merely be useless but would lead to Assert failure inside * this would not merely be useless but would lead to Assert failure inside
...@@ -6093,7 +6114,9 @@ HeapTupleGetUpdateXid(HeapTupleHeader tuple) ...@@ -6093,7 +6114,9 @@ HeapTupleGetUpdateXid(HeapTupleHeader tuple)
*/ */
static bool static bool
Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status, Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status,
int *remaining, uint16 infomask, bool nowait) uint16 infomask, bool nowait,
Relation rel, ItemPointer ctid, XLTW_Oper oper,
int *remaining)
{ {
bool allow_old; bool allow_old;
bool result = true; bool result = true;
...@@ -6130,6 +6153,12 @@ Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status, ...@@ -6130,6 +6153,12 @@ Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status,
/* /*
* This member conflicts with our multi, so we have to sleep (or * This member conflicts with our multi, so we have to sleep (or
* return failure, if asked to avoid waiting.) * return failure, if asked to avoid waiting.)
*
* Note that we don't set up an error context callback ourselves,
* but instead we pass the info down to XactLockTableWait. This
* might seem a bit wasteful because the context is set up and
* tore down for each member of the multixact, but in reality it
* should be barely noticeable, and it avoids duplicate code.
*/ */
if (nowait) if (nowait)
{ {
...@@ -6138,7 +6167,7 @@ Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status, ...@@ -6138,7 +6167,7 @@ Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status,
break; break;
} }
else else
XactLockTableWait(memxid); XactLockTableWait(memxid, rel, ctid, oper);
} }
pfree(members); pfree(members);
...@@ -6159,13 +6188,14 @@ Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status, ...@@ -6159,13 +6188,14 @@ Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status,
* *
* We return (in *remaining, if not NULL) the number of members that are still * We return (in *remaining, if not NULL) the number of members that are still
* running, including any (non-aborted) subtransactions of our own transaction. * running, including any (non-aborted) subtransactions of our own transaction.
*
*/ */
static void static void
MultiXactIdWait(MultiXactId multi, MultiXactStatus status, MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask,
int *remaining, uint16 infomask) Relation rel, ItemPointer ctid, XLTW_Oper oper,
int *remaining)
{ {
Do_MultiXactIdWait(multi, status, remaining, infomask, false); (void) Do_MultiXactIdWait(multi, status, infomask, false,
rel, ctid, oper, remaining);
} }
/* /*
...@@ -6183,9 +6213,11 @@ MultiXactIdWait(MultiXactId multi, MultiXactStatus status, ...@@ -6183,9 +6213,11 @@ MultiXactIdWait(MultiXactId multi, MultiXactStatus status,
*/ */
static bool static bool
ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status, ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status,
int *remaining, uint16 infomask) uint16 infomask, Relation rel, ItemPointer ctid,
XLTW_Oper oper, int *remaining)
{ {
return Do_MultiXactIdWait(multi, status, remaining, infomask, true); return Do_MultiXactIdWait(multi, status, infomask, true,
rel, ctid, oper, remaining);
} }
/* /*
......
...@@ -168,7 +168,7 @@ top: ...@@ -168,7 +168,7 @@ top:
{ {
/* Have to wait for the other guy ... */ /* Have to wait for the other guy ... */
_bt_relbuf(rel, buf); _bt_relbuf(rel, buf);
XactLockTableWait(xwait); XactLockTableWait(xwait, rel, &itup->t_tid, XLTW_InsertIndex);
/* start over... */ /* start over... */
_bt_freestack(stack); _bt_freestack(stack);
goto top; goto top;
......
...@@ -2295,7 +2295,9 @@ IndexBuildHeapScan(Relation heapRelation, ...@@ -2295,7 +2295,9 @@ IndexBuildHeapScan(Relation heapRelation,
* Must drop the lock on the buffer before we wait * Must drop the lock on the buffer before we wait
*/ */
LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK); LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
XactLockTableWait(xwait); XactLockTableWait(xwait, heapRelation,
&heapTuple->t_data->t_ctid,
XLTW_InsertIndexUnique);
goto recheck; goto recheck;
} }
} }
...@@ -2341,7 +2343,9 @@ IndexBuildHeapScan(Relation heapRelation, ...@@ -2341,7 +2343,9 @@ IndexBuildHeapScan(Relation heapRelation,
* Must drop the lock on the buffer before we wait * Must drop the lock on the buffer before we wait
*/ */
LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK); LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
XactLockTableWait(xwait); XactLockTableWait(xwait, heapRelation,
&heapTuple->t_data->t_ctid,
XLTW_InsertIndexUnique);
goto recheck; goto recheck;
} }
......
...@@ -1982,7 +1982,9 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode, ...@@ -1982,7 +1982,9 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
if (TransactionIdIsValid(SnapshotDirty.xmax)) if (TransactionIdIsValid(SnapshotDirty.xmax))
{ {
ReleaseBuffer(buffer); ReleaseBuffer(buffer);
XactLockTableWait(SnapshotDirty.xmax); XactLockTableWait(SnapshotDirty.xmax,
relation, &tuple.t_data->t_ctid,
XLTW_FetchUpdated);
continue; /* loop back to repeat heap_fetch */ continue; /* loop back to repeat heap_fetch */
} }
......
...@@ -1307,7 +1307,8 @@ retry: ...@@ -1307,7 +1307,8 @@ retry:
if (TransactionIdIsValid(xwait)) if (TransactionIdIsValid(xwait))
{ {
index_endscan(index_scan); index_endscan(index_scan);
XactLockTableWait(xwait); XactLockTableWait(xwait, heap, &tup->t_data->t_ctid,
XLTW_RecheckExclusionConstr);
goto retry; goto retry;
} }
......
...@@ -1343,7 +1343,7 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn ...@@ -1343,7 +1343,7 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
if (TransactionIdIsCurrentTransactionId(xid)) if (TransactionIdIsCurrentTransactionId(xid))
elog(ERROR, "waiting for ourselves"); elog(ERROR, "waiting for ourselves");
XactLockTableWait(xid); XactLockTableWait(xid, NULL, NULL, XLTW_None);
} }
/* nothing could have built up so far, so don't perform cleanup */ /* nothing could have built up so far, so don't perform cleanup */
......
...@@ -25,6 +25,21 @@ ...@@ -25,6 +25,21 @@
#include "utils/inval.h" #include "utils/inval.h"
/*
* Struct to hold context info for transaction lock waits.
*
* 'oper' is the operation that needs to wait for the other transaction; 'rel'
* and 'ctid' specify the address of the tuple being waited for.
*/
typedef struct XactLockTableWaitInfo
{
XLTW_Oper oper;
Relation rel;
ItemPointer ctid;
} XactLockTableWaitInfo;
static void XactLockTableWaitErrorCb(void *arg);
/* /*
* RelationInitLockInfo * RelationInitLockInfo
* Initializes the lock information in a relation descriptor. * Initializes the lock information in a relation descriptor.
...@@ -471,7 +486,9 @@ XactLockTableDelete(TransactionId xid) ...@@ -471,7 +486,9 @@ XactLockTableDelete(TransactionId xid)
/* /*
* XactLockTableWait * XactLockTableWait
* *
* Wait for the specified transaction to commit or abort. * Wait for the specified transaction to commit or abort. If an operation
* is specified, an error context callback is set up. If 'oper' is passed as
* None, no error context callback is set up.
* *
* Note that this does the right thing for subtransactions: if we wait on a * Note that this does the right thing for subtransactions: if we wait on a
* subtransaction, we will exit as soon as it aborts or its top parent commits. * subtransaction, we will exit as soon as it aborts or its top parent commits.
...@@ -481,9 +498,31 @@ XactLockTableDelete(TransactionId xid) ...@@ -481,9 +498,31 @@ XactLockTableDelete(TransactionId xid)
* and if so wait for its parent. * and if so wait for its parent.
*/ */
void void
XactLockTableWait(TransactionId xid) XactLockTableWait(TransactionId xid, Relation rel, ItemPointer ctid,
XLTW_Oper oper)
{ {
LOCKTAG tag; LOCKTAG tag;
XactLockTableWaitInfo info;
ErrorContextCallback callback;
/*
* If an operation is specified, set up our verbose error context
* callback.
*/
if (oper != XLTW_None)
{
Assert(RelationIsValid(rel));
Assert(ItemPointerIsValid(ctid));
info.rel = rel;
info.ctid = ctid;
info.oper = oper;
callback.callback = XactLockTableWaitErrorCb;
callback.arg = &info;
callback.previous = error_context_stack;
error_context_stack = &callback;
}
for (;;) for (;;)
{ {
...@@ -500,6 +539,9 @@ XactLockTableWait(TransactionId xid) ...@@ -500,6 +539,9 @@ XactLockTableWait(TransactionId xid)
break; break;
xid = SubTransGetParent(xid); xid = SubTransGetParent(xid);
} }
if (oper != XLTW_None)
error_context_stack = callback.previous;
} }
/* /*
...@@ -533,6 +575,62 @@ ConditionalXactLockTableWait(TransactionId xid) ...@@ -533,6 +575,62 @@ ConditionalXactLockTableWait(TransactionId xid)
return true; return true;
} }
/*
* XactLockTableWaitErrorContextCb
* Error context callback for transaction lock waits.
*/
static void
XactLockTableWaitErrorCb(void *arg)
{
XactLockTableWaitInfo *info = (XactLockTableWaitInfo *) arg;
/*
* We would like to print schema name too, but that would require a
* syscache lookup.
*/
if (info->oper != XLTW_None &&
ItemPointerIsValid(info->ctid) && RelationIsValid(info->rel))
{
const char *cxt;
switch (info->oper)
{
case XLTW_Update:
cxt = gettext_noop("while updating tuple (%u,%u) in relation \"%s\"");
break;
case XLTW_Delete:
cxt = gettext_noop("while deleting tuple (%u,%u) in relation \"%s\"");
break;
case XLTW_Lock:
cxt = gettext_noop("while locking tuple (%u,%u) in relation \"%s\"");
break;
case XLTW_LockUpdated:
cxt = gettext_noop("while locking updated version (%u,%u) of tuple in relation \"%s\"");
break;
case XLTW_InsertIndex:
cxt = gettext_noop("while inserting index tuple (%u,%u) in relation \"%s\"");
break;
case XLTW_InsertIndexUnique:
cxt = gettext_noop("while checking uniqueness of tuple (%u,%u) in relation \"%s\"");
break;
case XLTW_FetchUpdated:
cxt = gettext_noop("while rechecking updated tuple (%u,%u) in relation \"%s\"");
break;
case XLTW_RecheckExclusionConstr:
cxt = gettext_noop("while checking exclusion constraint on tuple (%u,%u) in relation \"%s\"");
break;
default:
return;
}
errcontext(cxt,
ItemPointerGetBlockNumber(info->ctid),
ItemPointerGetOffsetNumber(info->ctid),
RelationGetRelationName(info->rel));
}
}
/* /*
* WaitForLockersMultiple * WaitForLockersMultiple
* Wait until no transaction holds locks that conflict with the given * Wait until no transaction holds locks that conflict with the given
......
...@@ -20,6 +20,20 @@ ...@@ -20,6 +20,20 @@
#include "utils/rel.h" #include "utils/rel.h"
/* XactLockTableWait operations */
typedef enum XLTW_Oper
{
XLTW_None,
XLTW_Update,
XLTW_Delete,
XLTW_Lock,
XLTW_LockUpdated,
XLTW_InsertIndex,
XLTW_InsertIndexUnique,
XLTW_FetchUpdated,
XLTW_RecheckExclusionConstr
} XLTW_Oper;
extern void RelationInitLockInfo(Relation relation); extern void RelationInitLockInfo(Relation relation);
/* Lock a relation */ /* Lock a relation */
...@@ -54,7 +68,8 @@ extern void UnlockTuple(Relation relation, ItemPointer tid, LOCKMODE lockmode); ...@@ -54,7 +68,8 @@ extern void UnlockTuple(Relation relation, ItemPointer tid, LOCKMODE lockmode);
/* Lock an XID (used to wait for a transaction to finish) */ /* Lock an XID (used to wait for a transaction to finish) */
extern void XactLockTableInsert(TransactionId xid); extern void XactLockTableInsert(TransactionId xid);
extern void XactLockTableDelete(TransactionId xid); extern void XactLockTableDelete(TransactionId xid);
extern void XactLockTableWait(TransactionId xid); extern void XactLockTableWait(TransactionId xid, Relation rel,
ItemPointer ctid, XLTW_Oper oper);
extern bool ConditionalXactLockTableWait(TransactionId xid); extern bool ConditionalXactLockTableWait(TransactionId xid);
/* Lock VXIDs, specified by conflicting locktags */ /* Lock VXIDs, specified by conflicting locktags */
......
...@@ -57,7 +57,10 @@ BACKEND_COMMON_GETTEXT_TRIGGERS = \ ...@@ -57,7 +57,10 @@ BACKEND_COMMON_GETTEXT_TRIGGERS = \
errmsg errmsg_plural:1,2 \ errmsg errmsg_plural:1,2 \
errdetail errdetail_log errdetail_plural:1,2 \ errdetail errdetail_log errdetail_plural:1,2 \
errhint \ errhint \
errcontext errcontext \
XactLockTableWait:4 \
MultiXactIdWait:6 \
ConditionalMultiXactIdWait:6
BACKEND_COMMON_GETTEXT_FLAGS = \ BACKEND_COMMON_GETTEXT_FLAGS = \
errmsg:1:c-format errmsg_plural:1:c-format errmsg_plural:2:c-format \ errmsg:1:c-format errmsg_plural:1:c-format errmsg_plural:2:c-format \
errdetail:1:c-format errdetail_log:1:c-format errdetail_plural:1:c-format errdetail_plural:2:c-format \ errdetail:1:c-format errdetail_log:1:c-format errdetail_plural:1:c-format errdetail_plural:2:c-format \
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment