diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c index 075d781..9ef90cd 100644 *** a/contrib/pgrowlocks/pgrowlocks.c --- b/contrib/pgrowlocks/pgrowlocks.c *************** *** 138,151 **** pgrowlocks(PG_FUNCTION_ARGS) infomask = tuple->t_data->t_infomask; /* ! * a tuple is locked if HTSU returns BeingUpdated, and if it returns ! * MayBeUpdated but the Xmax is valid and pointing at us. */ ! if (htsu == HeapTupleBeingUpdated || ! (htsu == HeapTupleMayBeUpdated && ! !(infomask & HEAP_XMAX_INVALID) && ! !(infomask & HEAP_XMAX_IS_MULTI) && ! (xmax == GetCurrentTransactionIdIfAny()))) { char **values; --- 138,146 ---- infomask = tuple->t_data->t_infomask; /* ! * A tuple is locked if HTSU returns BeingUpdated. */ ! if (htsu == HeapTupleBeingUpdated) { char **values; diff --git a/src/backend/access/heap/hindex b5977da..6eb1de2 100644 *** a/src/backend/access/heap/heapam.c --- b/src/backend/access/heap/heapam.c *************** *** 2596,2601 **** l1: --- 2596,2621 ---- TransactionId xwait; uint16 infomask; + /* + * If any subtransaction of the current top transaction already holds + * a lock on this tuple, (and no one else does,) we must skip sleeping + * on the xwait; that would raise an assert about sleeping on our own + * Xid (or sleep indefinitely in a non-assertion enabled build.) Note + * we don't need to do this when the Xmax is a multi, because that + * code is prepared to deal with transactions of the same session. + * + * This must be done before acquiring our tuple lock, to avoid + * deadlocks with other transaction that are already waiting on the + * lock we hold. + */ + if (!(tp.t_data->t_infomask & HEAP_XMAX_IS_MULTI) && + TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tp.t_data))) + { + Assert(HEAP_XMAX_IS_LOCKED_ONLY(tp.t_data->t_infomask)); + + goto continue_deletion; + } + /* must copy state data before unlocking buffer */ xwait = HeapTupleHeaderGetRawXmax(tp.t_data); infomask = tp.t_data->t_infomask; *************** *** 2669,2674 **** l1: --- 2689,2695 ---- UpdateXmaxHintBits(tp.t_data, buffer, xwait); } + continue_deletion: /* * We may overwrite if previous xmax aborted, or if it committed but * only locked the tuple without updating it. *************** *** 3089,3094 **** l2: --- 3110,3137 ---- * heap_update directly. */ + /* + * If any subtransaction of the current top transaction already holds + * a lock on this tuple, (and no one else does,) we must skip sleeping + * on the xwait; that would raise an assert about sleeping on our own + * Xid (or sleep indefinitely in a non-assertion enabled build.) Note + * we don't need to do this when the Xmax is a multi, because that + * code is prepared to deal with transactions of the same session. + * + * This must be done before acquiring our tuple lock, to avoid + * deadlocks with other transaction that are already waiting on the + * lock we hold. + */ + if (!(oldtup.t_data->t_infomask & HEAP_XMAX_IS_MULTI) && + TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(oldtup.t_data))) + { + Assert(HEAP_XMAX_IS_LOCKED_ONLY(oldtup.t_data->t_infomask)); + + can_continue = true; + locker_remains = true; + goto continue_updating; + } + /* must copy state data before unlocking buffer */ xwait = HeapTupleHeaderGetRawXmax(oldtup.t_data); infomask = oldtup.t_data->t_infomask; *************** *** 3223,3228 **** l2: --- 3266,3272 ---- } } + continue_updating: result = can_continue ? HeapTupleMayBeUpdated : HeapTupleUpdated; } *************** *** 3945,3951 **** l3: TransactionId xwait; uint16 infomask; uint16 infomask2; ! bool require_sleep; ItemPointerData t_ctid; /* must copy state data before unlocking buffer */ --- 3989,3995 ---- TransactionId xwait; uint16 infomask; uint16 infomask2; ! bool require_sleep = true; ItemPointerData t_ctid; /* must copy state data before unlocking buffer */ *************** *** 3997,4002 **** l3: --- 4041,4064 ---- pfree(members); } + else if (HEAP_XMAX_IS_LOCKED_ONLY(infomask) && + TransactionIdIsCurrentTransactionId(xwait)) + { + /* + * If we already hold a lock on this tuple, we can fall through + * to perhaps create a new MultiXact including the previous lock + * and ours, or perhaps just keep the old lock. + */ + result = HeapTupleMayBeUpdated; + + LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE); + /* If xmax changed while we weren't looking, start over */ + if ((tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI) || + !TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple->t_data), + xwait)) + goto l3; + goto continue_locking; + } /* * Acquire tuple lock to establish our priority for the tuple. *************** *** 4330,4335 **** l3: --- 4392,4399 ---- /* By here, we're certain that we hold buffer exclusive lock again */ + continue_locking: + /* * We may lock if previous xmax aborted, or if it committed but only * locked the tuple without updating it; or if we didn't have to wait *************** *** 4365,4401 **** failed: old_infomask = tuple->t_data->t_infomask; /* - * We might already hold the desired lock (or stronger), possibly under a - * different subtransaction of the current top transaction. If so, there - * is no need to change state or issue a WAL record. We already handled - * the case where this is true for xmax being a MultiXactId, so now check - * for cases where it is a plain TransactionId. - * - * Note in particular that this covers the case where we already hold - * exclusive lock on the tuple and the caller only wants key share or - * share lock. It would certainly not do to give up the exclusive lock. - */ - if (!(old_infomask & (HEAP_XMAX_INVALID | - HEAP_XMAX_COMMITTED | - HEAP_XMAX_IS_MULTI)) && - (mode == LockTupleKeyShare ? - (HEAP_XMAX_IS_KEYSHR_LOCKED(old_infomask) || - HEAP_XMAX_IS_SHR_LOCKED(old_infomask) || - HEAP_XMAX_IS_EXCL_LOCKED(old_infomask)) : - mode == LockTupleShare ? - (HEAP_XMAX_IS_SHR_LOCKED(old_infomask) || - HEAP_XMAX_IS_EXCL_LOCKED(old_infomask)) : - (HEAP_XMAX_IS_EXCL_LOCKED(old_infomask))) && - TransactionIdIsCurrentTransactionId(xmax)) - { - LockBuffer(*buffer, BUFFER_LOCK_UNLOCK); - /* Probably can't hold tuple lock here, but may as well check */ - if (have_tuple_lock) - UnlockTupleTuplock(relation, tid, mode); - return HeapTupleMayBeUpdated; - } - - /* * If this is the first possibly-multixact-able operation in the current * transaction, set my per-backend OldestMemberMXactId setting. We can be * certain that the transaction will never become a member of any older --- 4429,4434 ---- *************** *** 4585,4591 **** l5: } else if (old_infomask & HEAP_XMAX_IS_MULTI) { ! MultiXactStatus new_status; /* * Currently we don't allow XMAX_COMMITTED to be set for multis, so --- 4618,4624 ---- } else if (old_infomask & HEAP_XMAX_IS_MULTI) { ! bool keepmulti = false; /* * Currently we don't allow XMAX_COMMITTED to be set for multis, so *************** *** 4609,4630 **** l5: } /* ! * If the XMAX is already a MultiXactId, then we need to expand it to ! * include add_to_xmax; but if all the members were lockers and are ! * all gone, we can do away with the IS_MULTI bit and just set ! * add_to_xmax as the only locker/updater. If all lockers are gone ! * and we have an updater that aborted, we can also do without a ! * multi. ! * ! * The cost of doing GetMultiXactIdMembers would be paid by ! * MultiXactIdExpand if we weren't to do this, so this check is not ! * incurring extra work anyhow. */ ! if (!MultiXactIdIsRunning(xmax)) { ! if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) || ! TransactionIdDidAbort(MultiXactIdGetUpdateXid(xmax, ! old_infomask))) { /* * Reset these bits and restart; otherwise fall through to --- 4642,4706 ---- } /* ! * Examine the members of the existing MultiXact. If any of them is ! * the current transaction and holds a lock at least as strong as we ! * are requesting, then the lock request can be satisfied with the ! * existing MultiXact; there's no need to create a new one. Note we ! * only try this for locks, not updates, for three reasons: 1. if the ! * savepoint with the update later tried to abort, we'd be screwed; 2. ! * there's no way to "upgrade" a lock to an update within a multixact ! * anyway; 3. there is no point in trying because if the multixact had ! * a valid (non-aborted) update, the tuple would be invisible and we ! * wouldn't be trying to lock it. */ ! if (!is_update) ! { ! MultiXactMember *members; ! int nmembers; ! ! nmembers = GetMultiXactIdMembers(xmax, &members, true); ! if (nmembers > 0) ! { ! int i; ! MultiXactStatus new_status; ! ! new_status = get_mxact_status_for_lock(mode, false); ! ! for (i = 0; i < nmembers; i++) ! { ! if (TransactionIdIsCurrentTransactionId(members[i].xid) && ! (members[i].status >= new_status)) ! { ! keepmulti = true; ! break; ! } ! } ! pfree(members); ! } ! } ! ! if (keepmulti) ! new_xmax = xmax; ! else { ! MultiXactStatus new_status; ! ! /* ! * If the XMAX is already a MultiXactId, then we need to expand it ! * to include add_to_xmax; but if all the members were lockers and ! * are all gone, we can do away with the IS_MULTI bit and just set ! * add_to_xmax as the only locker/updater. If all lockers are ! * gone and we have an updater that aborted, we can also do ! * without a multi. ! * ! * The cost of doing GetMultiXactIdMembers would be paid by ! * MultiXactIdExpand if we weren't to do this, so this check is ! * not incurring extra work anyhow. ! */ ! if (!MultiXactIdIsRunning(xmax) && ! (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) || ! !TransactionIdDidCommit(MultiXactIdGetUpdateXid(xmax, ! old_infomask)))) { /* * Reset these bits and restart; otherwise fall through to *************** *** 4634,4645 **** l5: old_infomask |= HEAP_XMAX_INVALID; goto l5; } - } ! new_status = get_mxact_status_for_lock(mode, is_update); - new_xmax = MultiXactIdExpand((MultiXactId) xmax, add_to_xmax, - new_status); GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2); } else if (old_infomask & HEAP_XMAX_COMMITTED) --- 4710,4722 ---- old_infomask |= HEAP_XMAX_INVALID; goto l5; } ! new_status = get_mxact_status_for_lock(mode, is_update); ! ! new_xmax = MultiXactIdExpand((MultiXactId) xmax, add_to_xmax, ! new_status); ! } GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2); } else if (old_infomask & HEAP_XMAX_COMMITTED) diff --git a/src/backend/utils/time/tquindex 44b4ddc..0d82523 100644 *** a/src/backend/utils/time/tqual.c --- b/src/backend/utils/time/tqual.c *************** *** 846,852 **** HeapTupleSatisfiesUpdate(HeapTupleHeader tuple, CommandId curcid, if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple))) { if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) ! return HeapTupleMayBeUpdated; if (HeapTupleHeaderGetCmax(tuple) >= curcid) return HeapTupleSelfUpdated; /* updated after scan started */ else --- 846,852 ---- if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple))) { if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) ! return HeapTupleBeingUpdated; if (HeapTupleHeaderGetCmax(tuple) >= curcid) return HeapTupleSelfUpdated; /* updated after scan started */ else diff --git a/src/include/access/xlog.index 83e5832..16a4c3b 100644 diff --git a/src/test/isolation/index f625650..2dfd872 100644