*** a/src/backend/access/heap/heapam.c --- b/src/backend/access/heap/heapam.c *************** *** 105,115 **** static void GetMultiXactIdHintBits(MultiXactId multi, uint16 *new_infomask, uint16 *new_infomask2); static TransactionId MultiXactIdGetUpdateXid(TransactionId xmax, uint16 t_infomask); ! static void MultiXactIdWait(MultiXactId multi, MultiXactStatus status, ! int *remaining, uint16 infomask); ! static bool ConditionalMultiXactIdWait(MultiXactId multi, ! MultiXactStatus status, int *remaining, ! uint16 infomask); static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup); static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_modified, bool *copy); --- 105,116 ---- uint16 *new_infomask2); static TransactionId MultiXactIdGetUpdateXid(TransactionId xmax, uint16 t_infomask); ! static void MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask, ! Relation rel, ItemPointer ctid, const char *opr, ! int *remaining); ! static bool ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status, ! uint16 infomask, Relation rel, ItemPointer ctid, ! const char *opr, int *remaining); static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup); static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_modified, bool *copy); *************** *** 2714,2721 **** l1: if (infomask & HEAP_XMAX_IS_MULTI) { /* wait for multixact */ ! MultiXactIdWait((MultiXactId) xwait, MultiXactStatusUpdate, ! NULL, infomask); LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); /* --- 2715,2724 ---- if (infomask & HEAP_XMAX_IS_MULTI) { /* wait for multixact */ ! MultiXactIdWait((MultiXactId) xwait, MultiXactStatusUpdate, infomask, ! /* translator: string is XactLockTableWait operation name */ ! relation, &tp.t_data->t_ctid, "deleting tuple", ! NULL); LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); /* *************** *** 2741,2747 **** l1: else { /* wait for regular transaction to end */ ! XactLockTableWait(xwait); LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); /* --- 2744,2752 ---- else { /* wait for regular transaction to end */ ! XactLockTableWait(xwait, relation, &tp.t_data->t_ctid, ! /* translator: string is XactLockTableWait operation name */ ! "deleting tuple"); LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); /* *************** *** 3266,3273 **** l2: int remain; /* wait for multixact */ ! MultiXactIdWait((MultiXactId) xwait, mxact_status, &remain, ! infomask); LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); /* --- 3271,3280 ---- int remain; /* wait for multixact */ ! MultiXactIdWait((MultiXactId) xwait, mxact_status, infomask, ! /* translator: string is XactLockTableWait operation name */ ! relation, &oldtup.t_data->t_ctid, "updating tuple", ! &remain); LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); /* *************** *** 3341,3347 **** l2: else { /* wait for regular transaction to end */ ! XactLockTableWait(xwait); LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); /* --- 3348,3356 ---- else { /* wait for regular transaction to end */ ! XactLockTableWait(xwait, relation, &oldtup.t_data->t_ctid, ! /* translator: string is XactLockTableWait operation name */ ! "updating tuple"); LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); /* *************** *** 4402,4415 **** l3: if (nowait) { if (!ConditionalMultiXactIdWait((MultiXactId) xwait, ! status, NULL, infomask)) ereport(ERROR, (errcode(ERRCODE_LOCK_NOT_AVAILABLE), errmsg("could not obtain lock on row in relation \"%s\"", RelationGetRelationName(relation)))); } else ! MultiXactIdWait((MultiXactId) xwait, status, NULL, infomask); /* if there are updates, follow the update chain */ if (follow_updates && --- 4411,4430 ---- if (nowait) { if (!ConditionalMultiXactIdWait((MultiXactId) xwait, ! status, infomask, relation, ! &tuple->t_data->t_ctid, ! /* translator: string is XactLockTableWait operation name */ ! "locking tuple", NULL)) ereport(ERROR, (errcode(ERRCODE_LOCK_NOT_AVAILABLE), errmsg("could not obtain lock on row in relation \"%s\"", RelationGetRelationName(relation)))); } else ! MultiXactIdWait((MultiXactId) xwait, status, infomask, ! relation, &tuple->t_data->t_ctid, ! /* translator: string is XactLockTableWait operation name */ ! "locking tuple", NULL); /* if there are updates, follow the update chain */ if (follow_updates && *************** *** 4464,4470 **** l3: RelationGetRelationName(relation)))); } else ! XactLockTableWait(xwait); /* if there are updates, follow the update chain */ if (follow_updates && --- 4479,4487 ---- RelationGetRelationName(relation)))); } else ! XactLockTableWait(xwait, relation, &tuple->t_data->t_ctid, ! /* translator: string is XactLockTableWait operation name */ ! "locking tuple"); /* if there are updates, follow the update chain */ if (follow_updates && *************** *** 5151,5157 **** l4: if (needwait) { LockBuffer(buf, BUFFER_LOCK_UNLOCK); ! XactLockTableWait(members[i].xid); pfree(members); goto l4; } --- 5168,5178 ---- if (needwait) { LockBuffer(buf, BUFFER_LOCK_UNLOCK); ! XactLockTableWait(members[i].xid, rel, ! &mytup.t_data->t_ctid, ! /*--- ! translator: string is XactLockTableWait operation name */ ! "locking updated version of tuple"); pfree(members); goto l4; } *************** *** 5211,5217 **** l4: if (needwait) { LockBuffer(buf, BUFFER_LOCK_UNLOCK); ! XactLockTableWait(rawxmax); goto l4; } if (res != HeapTupleMayBeUpdated) --- 5232,5241 ---- if (needwait) { LockBuffer(buf, BUFFER_LOCK_UNLOCK); ! XactLockTableWait(rawxmax, rel, &mytup.t_data->t_ctid, ! /*--- ! translator: string is XactLockTableWait operation name */ ! "locking updated version of tuple"); goto l4; } if (res != HeapTupleMayBeUpdated) *************** *** 6076,6081 **** HeapTupleGetUpdateXid(HeapTupleHeader tuple) --- 6100,6114 ---- * Do_MultiXactIdWait * 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 'opr' 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 * 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 *************** *** 6093,6099 **** HeapTupleGetUpdateXid(HeapTupleHeader tuple) */ static bool Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status, ! int *remaining, uint16 infomask, bool nowait) { bool allow_old; bool result = true; --- 6126,6134 ---- */ static bool Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status, ! uint16 infomask, bool nowait, ! Relation rel, ItemPointer ctid, const char *opr, ! int *remaining) { bool allow_old; bool result = true; *************** *** 6130,6135 **** Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status, --- 6165,6176 ---- /* * This member conflicts with our multi, so we have to sleep (or * 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) { *************** *** 6138,6144 **** Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status, break; } else ! XactLockTableWait(memxid); } pfree(members); --- 6179,6185 ---- break; } else ! XactLockTableWait(memxid, rel, ctid, opr); } pfree(members); *************** *** 6159,6171 **** Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status, * * We return (in *remaining, if not NULL) the number of members that are still * running, including any (non-aborted) subtransactions of our own transaction. - * */ static void ! MultiXactIdWait(MultiXactId multi, MultiXactStatus status, ! int *remaining, uint16 infomask) { ! Do_MultiXactIdWait(multi, status, remaining, infomask, false); } /* --- 6200,6213 ---- * * We return (in *remaining, if not NULL) the number of members that are still * running, including any (non-aborted) subtransactions of our own transaction. */ static void ! MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask, ! Relation rel, ItemPointer ctid, const char *opr, ! int *remaining) { ! (void) Do_MultiXactIdWait(multi, status, infomask, false, ! rel, ctid, opr, remaining); } /* *************** *** 6183,6191 **** MultiXactIdWait(MultiXactId multi, MultiXactStatus status, */ static bool ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status, ! int *remaining, uint16 infomask) { ! return Do_MultiXactIdWait(multi, status, remaining, infomask, true); } /* --- 6225,6235 ---- */ static bool ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status, ! uint16 infomask, Relation rel, ItemPointer ctid, ! const char *opr, int *remaining) { ! return Do_MultiXactIdWait(multi, status, infomask, true, ! rel, ctid, opr, remaining); } /* *** a/src/backend/access/nbtree/nbtinsert.c --- b/src/backend/access/nbtree/nbtinsert.c *************** *** 164,170 **** top: { /* Have to wait for the other guy ... */ _bt_relbuf(rel, buf); ! XactLockTableWait(xwait); /* start over... */ _bt_freestack(stack); goto top; --- 164,171 ---- { /* Have to wait for the other guy ... */ _bt_relbuf(rel, buf); ! /* translator: string is XactLockTableWait operation name */ ! XactLockTableWait(xwait, rel, &itup->t_tid, "inserting index tuple"); /* start over... */ _bt_freestack(stack); goto top; *** a/src/backend/catalog/index.c --- b/src/backend/catalog/index.c *************** *** 2295,2301 **** IndexBuildHeapScan(Relation heapRelation, * Must drop the lock on the buffer before we wait */ LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK); ! XactLockTableWait(xwait); goto recheck; } } --- 2295,2305 ---- * Must drop the lock on the buffer before we wait */ LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK); ! XactLockTableWait(xwait, heapRelation, ! &heapTuple->t_data->t_ctid, ! /*--- ! translator: string is XactLockTableWait operation name */ ! "creating unique index tuple"); goto recheck; } } *************** *** 2341,2347 **** IndexBuildHeapScan(Relation heapRelation, * Must drop the lock on the buffer before we wait */ LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK); ! XactLockTableWait(xwait); goto recheck; } --- 2345,2355 ---- * Must drop the lock on the buffer before we wait */ LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK); ! XactLockTableWait(xwait, heapRelation, ! &heapTuple->t_data->t_ctid, ! /*--- ! translator: string is XactLockTableWait operation name */ ! "creating unique index tuple"); goto recheck; } *** a/src/backend/executor/execMain.c --- b/src/backend/executor/execMain.c *************** *** 1982,1988 **** EvalPlanQualFetch(EState *estate, Relation relation, int lockmode, if (TransactionIdIsValid(SnapshotDirty.xmax)) { ReleaseBuffer(buffer); ! XactLockTableWait(SnapshotDirty.xmax); continue; /* loop back to repeat heap_fetch */ } --- 1982,1991 ---- if (TransactionIdIsValid(SnapshotDirty.xmax)) { ReleaseBuffer(buffer); ! XactLockTableWait(SnapshotDirty.xmax, ! relation, &tuple.t_data->t_ctid, ! /* translator: string is XactLockTableWait operation name */ ! "rechecking updated version of tuple"); continue; /* loop back to repeat heap_fetch */ } *** a/src/backend/executor/execUtils.c --- b/src/backend/executor/execUtils.c *************** *** 105,111 **** CreateExecutorState(void) * Initialize all fields of the Executor State structure */ estate->es_direction = ForwardScanDirection; ! estate->es_snapshot = InvalidSnapshot; /* caller must initialize this */ estate->es_crosscheck_snapshot = InvalidSnapshot; /* no crosscheck */ estate->es_range_table = NIL; estate->es_plannedstmt = NULL; --- 105,111 ---- * Initialize all fields of the Executor State structure */ estate->es_direction = ForwardScanDirection; ! estate->es_snapshot = InvalidSnapshot; /* caller must initialize this */ estate->es_crosscheck_snapshot = InvalidSnapshot; /* no crosscheck */ estate->es_range_table = NIL; estate->es_plannedstmt = NULL; *************** *** 1307,1313 **** retry: if (TransactionIdIsValid(xwait)) { index_endscan(index_scan); ! XactLockTableWait(xwait); goto retry; } --- 1307,1315 ---- if (TransactionIdIsValid(xwait)) { index_endscan(index_scan); ! XactLockTableWait(xwait, heap, &tup->t_data->t_ctid, ! /* translator: string is XactLockTableWait operation name */ ! "rechecking exclusion constraint tuple"); goto retry; } *** a/src/backend/replication/logical/snapbuild.c --- b/src/backend/replication/logical/snapbuild.c *************** *** 1343,1349 **** SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn if (TransactionIdIsCurrentTransactionId(xid)) elog(ERROR, "waiting for ourselves"); ! XactLockTableWait(xid); } /* nothing could have built up so far, so don't perform cleanup */ --- 1343,1349 ---- if (TransactionIdIsCurrentTransactionId(xid)) elog(ERROR, "waiting for ourselves"); ! XactLockTableWait(xid, NULL, NULL, NULL); } /* nothing could have built up so far, so don't perform cleanup */ *** a/src/backend/storage/lmgr/lmgr.c --- b/src/backend/storage/lmgr/lmgr.c *************** *** 25,30 **** --- 25,32 ---- #include "utils/inval.h" + static void XactLockTableWaitErrorContextCallback(void *arg); + /* * RelationInitLockInfo * Initializes the lock information in a relation descriptor. *************** *** 471,477 **** XactLockTableDelete(TransactionId xid) /* * XactLockTableWait * ! * Wait for the specified transaction to commit or abort. * * 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. --- 473,481 ---- /* * XactLockTableWait * ! * Wait for the specified transaction to commit or abort. If we're given a ! * relation, tuple id and operation name, an error context callback is set up. ! * If opr_name is passed as NULL, no error context callback is set up. * * 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. *************** *** 481,489 **** XactLockTableDelete(TransactionId xid) * and if so wait for its parent. */ void ! XactLockTableWait(TransactionId xid) { LOCKTAG tag; for (;;) { --- 485,518 ---- * and if so wait for its parent. */ void ! XactLockTableWait(TransactionId xid, Relation rel, ItemPointer ctid, ! const char *opr_name) { LOCKTAG tag; + XactLockTableWaitLockInfo info; + ErrorContextCallback callback; + + /* + * If we're given an operation name, set up our verbose error context + * callback. + * + * Note: we avoid an empty opr_name also, because it might be used to + * lookup a translated entry later. + */ + if (opr_name != NULL && opr_name[0] != '\0') + { + Assert(RelationIsValid(rel)); + Assert(ItemPointerIsValid(ctid)); + + info.rel = rel; + info.ctid = ctid; + info.opr_name = opr_name; + + callback.callback = XactLockTableWaitErrorContextCallback; + callback.arg = &info; + callback.previous = error_context_stack; + error_context_stack = &callback; + } for (;;) { *************** *** 500,505 **** XactLockTableWait(TransactionId xid) --- 529,537 ---- break; xid = SubTransGetParent(xid); } + + if (opr_name != NULL) + error_context_stack = callback.previous; } /* *************** *** 534,539 **** ConditionalXactLockTableWait(TransactionId xid) --- 566,594 ---- } /* + * XactLockTableWaitErrorContextCallback + * Error context callback for transaction lock waits. + */ + static void + XactLockTableWaitErrorContextCallback(void *arg) + { + XactLockTableWaitLockInfo *info = (XactLockTableWaitLockInfo *) arg; + + /* + * We would like to print schema name too, but that would require a + * syscache lookup. + */ + if (ItemPointerIsValid(info->ctid) && RelationIsValid(info->rel)) + { + errcontext("while operating on tuple (%u,%u) in relation \"%s\": %s", + ItemPointerGetBlockNumber(info->ctid), + ItemPointerGetOffsetNumber(info->ctid), + RelationGetRelationName(info->rel), + _(info->opr_name)); + } + } + + /* * WaitForLockersMultiple * Wait until no transaction holds locks that conflict with the given * locktags at the given lockmode. *** a/src/include/storage/lmgr.h --- b/src/include/storage/lmgr.h *************** *** 20,25 **** --- 20,41 ---- #include "utils/rel.h" + /* + * Struct to hold context info for transaction lock waits. + * + * rel is the relation being used when we need transaction lock. + * ctid is the tuple id of tuple being used by transaction on which + * we have to wait. + * opr_name is the operation which needs to wait on transaction lock. + */ + typedef struct XactLockTableWaitLockInfo + { + Relation rel; + ItemPointer ctid; + const char *opr_name; + } XactLockTableWaitLockInfo; + + extern void RelationInitLockInfo(Relation relation); /* Lock a relation */ *************** *** 54,60 **** extern void UnlockTuple(Relation relation, ItemPointer tid, LOCKMODE lockmode); /* Lock an XID (used to wait for a transaction to finish) */ extern void XactLockTableInsert(TransactionId xid); extern void XactLockTableDelete(TransactionId xid); ! extern void XactLockTableWait(TransactionId xid); extern bool ConditionalXactLockTableWait(TransactionId xid); /* Lock VXIDs, specified by conflicting locktags */ --- 70,77 ---- /* Lock an XID (used to wait for a transaction to finish) */ extern void XactLockTableInsert(TransactionId xid); extern void XactLockTableDelete(TransactionId xid); ! extern void XactLockTableWait(TransactionId xid, Relation rel, ! ItemPointer ctid, const char *opr_name); extern bool ConditionalXactLockTableWait(TransactionId xid); /* Lock VXIDs, specified by conflicting locktags */ *** a/src/nls-global.mk --- b/src/nls-global.mk *************** *** 57,63 **** BACKEND_COMMON_GETTEXT_TRIGGERS = \ errmsg errmsg_plural:1,2 \ errdetail errdetail_log errdetail_plural:1,2 \ errhint \ ! errcontext BACKEND_COMMON_GETTEXT_FLAGS = \ 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 \ --- 57,66 ---- errmsg errmsg_plural:1,2 \ errdetail errdetail_log errdetail_plural:1,2 \ errhint \ ! errcontext \ ! XactLockTableWait:4 \ ! MultiXactIdWait:6 \ ! ConditionalMultiXactIdWait:6 BACKEND_COMMON_GETTEXT_FLAGS = \ 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 \