/* Get the LOCKMODE for a given MultiXactStatus */ #define LOCKMODE_from_mxstatus(status) \
(tupleLockExtraInfo[TUPLOCK_from_mxstatus((status))].hwlock)
/* *heap_index_delete_tuplesusesthiswhendeterminingwhichheapblocksit *mustvisittohelpitsbottom-upindexdeletioncaller
*/ typedefstruct IndexDeleteCounts
{
int16 npromisingtids; /* Number of "promising" TIDs in group */
int16 ntids; /* Number of TIDs in group */
int16 ifirsttid; /* Offset to group's first deltid */
} IndexDeleteCounts;
/* may return InvalidBlockNumber if there are no more blocks */
scan->rs_prefetch_block = table_block_parallelscan_nextpage(scan->rs_base.rs_rd,
scan->rs_parallelworkerdata,
(ParallelBlockTableScanDesc) scan->rs_base.rs_parallel);
scan->rs_inited = true;
} else
{
scan->rs_prefetch_block = table_block_parallelscan_nextpage(scan->rs_base.rs_rd,
scan->rs_parallelworkerdata, (ParallelBlockTableScanDesc)
scan->rs_base.rs_parallel);
}
/* ensure we're not accidentally being used when not in pagemode */
Assert(scan->rs_base.rs_flags & SO_ALLOW_PAGEMODE);
snapshot = scan->rs_base.rs_snapshot;
/* we're done if we're back at where we started */ if (block == scan->rs_startblock) return InvalidBlockNumber;
/* check if the limit imposed by heap_setscanlimits() is met */ if (scan->rs_numblocks != InvalidBlockNumber)
{ if (--scan->rs_numblocks == 0) return InvalidBlockNumber;
}
return block;
} else
{ /* we're done if the last block is the start position */ if (block == scan->rs_startblock) return InvalidBlockNumber;
/* check if the limit imposed by heap_setscanlimits() is met */ if (scan->rs_numblocks != InvalidBlockNumber)
{ if (--scan->rs_numblocks == 0) return InvalidBlockNumber;
}
/* wrap to the end of the heap when the last page was page 0 */ if (block == 0)
block = scan->rs_nblocks;
/* skip tuples not visible to this snapshot */ if (!visible) continue;
/* skip any tuples that don't match the scan key */ if (key != NULL &&
!HeapKeyTest(tuple, RelationGetDescr(scan->rs_base.rs_rd),
nkeys, key)) continue;
if (likely(scan->rs_inited))
{ /* continue from previously returned page/tuple */
page = BufferGetPage(scan->rs_cbuf);
lineindex = scan->rs_cindex + dir; if (ScanDirectionIsForward(dir))
linesleft = scan->rs_ntuples - lineindex; else
linesleft = scan->rs_cindex; /* lineindex now references the next or previous visible tid */
goto continue_page;
}
/* *advancethescanuntilwefindaqualifyingtupleorrunoutofstuff *toscan
*/ while (true)
{
heap_fetch_next_buffer(scan, dir);
/* did we run out of blocks to scan? */ if (!BufferIsValid(scan->rs_cbuf)) break;
/* skip any tuples that don't match the scan key */ if (key != NULL &&
!HeapKeyTest(tuple, RelationGetDescr(scan->rs_base.rs_rd),
nkeys, key)) continue;
scan->rs_cindex = lineindex; return;
}
}
/* end of scan */ if (BufferIsValid(scan->rs_cbuf))
ReleaseBuffer(scan->rs_cbuf);
scan->rs_cbuf = InvalidBuffer;
scan->rs_cblock = InvalidBlockNumber;
scan->rs_prefetch_block = InvalidBlockNumber;
tuple->t_data = NULL;
scan->rs_inited = false;
}
/* *Checkforanemptyrangeandprotectfromwouldbenegativeresults *fromthenumBlkscalculationbelow.
*/ if (ItemPointerCompare(&highestItem, &lowestItem) < 0)
{ /* Set an empty range of blocks to scan */
heap_setscanlimits(sscan, 0, 0); return;
}
/* Set the start block and number of blocks to scan */
heap_setscanlimits(sscan, startBlk, numBlks);
/* Finally, set the TID range in sscan */
ItemPointerCopy(&lowestItem, &sscan->st.tidrange.rs_mintid);
ItemPointerCopy(&highestItem, &sscan->st.tidrange.rs_maxtid);
}
if (valid)
{ /* *Allcheckspassed,soreturnthetupleasvalid.Callerisnow *responsibleforreleasingthebuffer.
*/
*userbuf = buffer;
returntrue;
}
/* Tuple failed time qual, but maybe caller wants to see it anyway. */ if (keep_buf)
*userbuf = buffer; else
{
ReleaseBuffer(buffer);
*userbuf = InvalidBuffer;
tuple->t_data = NULL;
}
if (newbuf == buffer)
{ /* *Sincethenewtupleisgoingintothesamepage,wemightbeable *todoaHOTupdate.Checkifanyoftheindexcolumnshavebeen *changed.
*/ if (!bms_overlap(modified_attrs, hot_attrs))
{
use_hot_update = true;
/* *Ifnoneofthecolumnsthatareusedinhot-blockingindexes *wereupdated,wecanapplyHOT,butwedostillneedtocheck *ifweneedtoupdatethesummarizingindexes,andupdatethose *indexesifthecolumnswereupdated,orwemayfailtodetect *e.g.valueboundchangesinBRINminmaxindexes.
*/ if (bms_overlap(modified_attrs, sum_attrs))
summarized_update = true;
}
} else
{ /* Set a hint that the old page could use prune/defrag */
PageSetFull(page);
}
if (use_hot_update)
{ /* Mark the old tuple as HOT-updated */
HeapTupleSetHotUpdated(&oldtup); /* And mark the new tuple as heap-only */
HeapTupleSetHeapOnly(heaptup); /* Mark the caller's copy too, in case different from heaptup */
HeapTupleSetHeapOnly(newtup);
} else
{ /* Make sure tuples are correctly marked as not-HOT */
HeapTupleClearHotUpdated(&oldtup);
HeapTupleClearHeapOnly(heaptup);
HeapTupleClearHeapOnly(newtup);
}
RelationPutHeapTuple(relation, newbuf, heaptup, false); /* insert new tuple */
/* Clear obsolete visibility flags, possibly set by ourselves above... */
oldtup.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
oldtup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED; /* ... and store info about transaction updating this tuple */
Assert(TransactionIdIsValid(xmax_old_tuple));
HeapTupleHeaderSetXmax(oldtup.t_data, xmax_old_tuple);
oldtup.t_data->t_infomask |= infomask_old_tuple;
oldtup.t_data->t_infomask2 |= infomask2_old_tuple;
HeapTupleHeaderSetCmax(oldtup.t_data, cid, iscombo);
/* record address of new tuple in t_ctid of old one */
oldtup.t_data->t_ctid = heaptup->t_self;
/* Now we can release the buffer(s) */ if (newbuf != buffer)
ReleaseBuffer(newbuf);
ReleaseBuffer(buffer); if (BufferIsValid(vmbuffer_new))
ReleaseBuffer(vmbuffer_new); if (BufferIsValid(vmbuffer))
ReleaseBuffer(vmbuffer);
/* *Releasethelmgrtuplelock,ifwehadit.
*/ if (have_tuple_lock)
UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode);
switch (RelationGetRelid(relation))
{
case RelationRelationId:
{ /* LOCKTAG_TUPLE or LOCKTAG_RELATION ok */
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(newtup);
Oid relid = classForm->oid;
Oid dbid;
LOCKTAG tag;
if (IsSharedRelation(relid))
dbid = InvalidOid; else
dbid = MyDatabaseId;
if (classForm->relkind == RELKIND_INDEX)
{
Relation irel = index_open(relid, AccessShareLock);
if (!LockHeldByMe(&tag, ShareUpdateExclusiveLock, true))
elog(WARNING, "missing lock for relation \"%s\" (OID %u, relkind %c) @ TID (%u,%u)",
NameStr(classForm->relname),
relid,
classForm->relkind,
ItemPointerGetBlockNumber(&oldtup->t_self),
ItemPointerGetOffsetNumber(&oldtup->t_self));
} #endif
/* *Checkifthespecifiedattribute'svaluesarethesame.Subroutinefor *HeapDetermineColumnsInfo.
*/ static bool
heap_attr_equals(TupleDesc tupdesc, int attrnum, Datum value1, Datum value2,
bool isnull1, bool isnull2)
{ /* *IfonevalueisNULLandotherisnot,thentheyarecertainlynot *equal
*/ if (isnull1 != isnull2) returnfalse;
/* *IfbothareNULL,theycanbeconsideredequal.
*/ if (isnull1) returntrue;
/* *Wedosimplebinarycomparisonofthetwodatums.Thismaybeoverly *strictbecausetherecanbemultiplebinaryrepresentationsforthe *samelogicalvalue.ButweshouldbeOKaslongastherearenofalse *positives.Usingatype-specificequalityoperatorismessybecause *therecouldbemultiplenotionsofequalityindifferentoperator *classes;furthermore,wecannotsafelyinvokeuser-definedfunctions *whileholdingexclusivebufferlock.
*/ if (attrnum <= 0)
{ /* The only allowed system columns are OIDs, so do this */ return (DatumGetObjectId(value1) == DatumGetObjectId(value2));
} else
{
CompactAttribute *att;
attidx = -1; while ((attidx = bms_next_member(interesting_cols, attidx)) >= 0)
{ /* attidx is zero-based, attrnum is the normal attribute number */
AttrNumber attrnum = attidx + FirstLowInvalidHeapAttributeNumber;
Datum value1,
value2;
bool isnull1,
isnull2;
/*
* Extract the corresponding values. XXX this is pretty inefficient
* if there are many indexed columns. Should we do a single
* heap_deform_tuple call on each tuple, instead? But that doesn't
* work for system columns ...
*/
value1 = heap_getattr(oldtup, attrnum, tupdesc, &isnull1);
value2 = heap_getattr(newtup, attrnum, tupdesc, &isnull2);
/*
* No need to check attributes that can't be stored externally. Note
* that system attributes can't be stored externally.
*/
if (attrnum < 0 || isnull1 ||
TupleDescCompactAttr(tupdesc, attrnum - 1)->attlen != -1)
continue;
/*
* Check if the old tuple's attribute is stored externally and is a
* member of external_cols.
*/
if (VARATT_IS_EXTERNAL((struct varlena *) DatumGetPointer(value1)) &&
bms_is_member(attidx, external_cols))
*has_external = true;
}
return modified;
}
/*
* simple_heap_update - replace a tuple
*
* This routine may be used to update a tuple when concurrent updates of
* the target tuple are not expected (for example, because we have a lock
* on the relation associated with the tuple). Any failure is reported
* via ereport().
*/
void
simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup,
TU_UpdateIndexes *update_indexes)
{
TM_Result result;
TM_FailureData tmfd;
LockTupleMode lockmode;
result = heap_update(relation, otid, tup,
GetCurrentCommandId(true), InvalidSnapshot,
true /* wait for commit */ ,
&tmfd, &lockmode, update_indexes);
switch (result)
{
case TM_SelfModified:
/* Tuple was already updated in current command? */
elog(ERROR, "tuple already updated by self");
break;
case TM_Ok:
/* done successfully */
break;
case TM_Updated:
elog(ERROR, "tuple concurrently updated");
break;
case TM_Deleted:
elog(ERROR, "tuple concurrently deleted");
break;
/*
* Return the MultiXactStatus corresponding to the given tuple lock mode.
*/
static MultiXactStatus
get_mxact_status_for_lock(LockTupleMode mode, bool is_update)
{
int retval;
if (is_update)
retval = tupleLockExtraInfo[mode].updstatus;
else
retval = tupleLockExtraInfo[mode].lockstatus;
/*
* heap_lock_tuple - lock a tuple in shared or exclusive mode
*
* Note that this acquires a buffer pin, which the caller must release.
*
* Input parameters:
* relation: relation containing tuple (caller must hold suitable lock)
* tid: TID of tuple to lock
* cid: current command ID (used for visibility test, and stored into
* tuple's cmax if lock is successful)
* mode: indicates if shared or exclusive tuple lock is desired
* wait_policy: what to do if tuple lock is not available
* follow_updates: if true, follow the update chain to also lock descendant
* tuples.
*
* Output parameters:
* *tuple: all fields filled in
* *buffer: set to buffer holding tuple (pinned but not locked at exit)
* *tmfd: filled in failure cases (see below)
*
* Function results are the same as the ones for table_tuple_lock().
*
* In the failure cases other than TM_Invisible, the routine fills
* *tmfd with the tuple's t_ctid, t_xmax (resolving a possible MultiXact,
* if necessary), and t_cmax (the last only for TM_SelfModified,
* since we cannot obtain cmax from a combo CID generated by another
* transaction).
* See comments for struct TM_FailureData for additional info.
*
* See README.tuplock for a thorough explanation of this mechanism.
*/
TM_Result
heap_lock_tuple(Relation relation, HeapTuple tuple,
CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
bool follow_updates,
Buffer *buffer, TM_FailureData *tmfd)
{
TM_Result result;
ItemPointer tid = &(tuple->t_self);
ItemId lp;
Page page;
Buffer vmbuffer = InvalidBuffer;
BlockNumber block;
TransactionId xid,
xmax;
uint16 old_infomask,
new_infomask,
new_infomask2;
bool first_time = true;
bool skip_tuple_lock = false;
bool have_tuple_lock = false;
bool cleared_all_frozen = false;
/*
* Before locking the buffer, pin the visibility map page if it appears to
* be necessary. Since we haven't got the lock yet, someone else might be
* in the middle of changing this, so we'll need to recheck after we have
* the lock.
*/
if (PageIsAllVisible(BufferGetPage(*buffer)))
visibilitymap_pin(relation, block, &vmbuffer);
l3:
result = HeapTupleSatisfiesUpdate(tuple, cid, *buffer);
if (result == TM_Invisible)
{
/*
* This is possible, but only when locking a tuple for ON CONFLICT
* UPDATE. We return this value here rather than throwing an error in
* order to give that case the opportunity to throw a more specific
* error.
*/
result = TM_Invisible;
goto out_locked;
}
else if (result == TM_BeingModified ||
result == TM_Updated ||
result == TM_Deleted)
{
TransactionId xwait;
uint16 infomask;
uint16 infomask2;
bool require_sleep;
ItemPointerData t_ctid;
/* must copy state data before unlocking buffer */
xwait = HeapTupleHeaderGetRawXmax(tuple->t_data);
infomask = tuple->t_data->t_infomask;
infomask2 = tuple->t_data->t_infomask2;
ItemPointerCopy(&tuple->t_data->t_ctid, &t_ctid);
LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
/*
* If any subtransaction of the current top transaction already holds
* a lock as strong as or stronger than what we're requesting, we
* effectively hold the desired lock already. We *must* succeed
* without trying to take the tuple lock, else we will deadlock
* against anyone wanting to acquire a stronger lock.
*
* Note we only do this the first time we loop on the HTSU result;
* there is no point in testing in subsequent passes, because
* evidently our own transaction cannot have acquired a new lock after
* the first time we checked.
*/
if (first_time)
{
first_time = false;
if (infomask & HEAP_XMAX_IS_MULTI)
{
int i;
int nmembers;
MultiXactMember *members;
/*
* We don't need to allow old multixacts here; if that had
* been the case, HeapTupleSatisfiesUpdate would have returned
* MayBeUpdated and we wouldn't be here.
*/
nmembers =
GetMultiXactIdMembers(xwait, &members, false,
HEAP_XMAX_IS_LOCKED_ONLY(infomask));
for (i = 0; i < nmembers; i++)
{
/* only consider members of our own transaction */
if (!TransactionIdIsCurrentTransactionId(members[i].xid))
continue;
if (TUPLOCK_from_mxstatus(members[i].status) >= mode)
{
pfree(members);
result = TM_Ok;
goto out_unlocked;
}
else
{
/*
* Disable acquisition of the heavyweight tuple lock.
* Otherwise, when promoting a weaker lock, we might
* deadlock with another locker that has acquired the
* heavyweight tuple lock and is waiting for our
* transaction to finish.
*
* Note that in this case we still need to wait for
* the multixact if required, to avoid acquiring
* conflicting locks.
*/
skip_tuple_lock = true;
}
}
if (members)
pfree(members);
}
else if (TransactionIdIsCurrentTransactionId(xwait))
{
switch (mode)
{
case LockTupleKeyShare:
Assert(HEAP_XMAX_IS_KEYSHR_LOCKED(infomask) ||
HEAP_XMAX_IS_SHR_LOCKED(infomask) ||
HEAP_XMAX_IS_EXCL_LOCKED(infomask));
result = TM_Ok;
goto out_unlocked;
case LockTupleShare:
if (HEAP_XMAX_IS_SHR_LOCKED(infomask) ||
HEAP_XMAX_IS_EXCL_LOCKED(infomask))
{
result = TM_Ok;
goto out_unlocked;
}
break;
case LockTupleNoKeyExclusive:
if (HEAP_XMAX_IS_EXCL_LOCKED(infomask))
{
result = TM_Ok;
goto out_unlocked;
}
break;
case LockTupleExclusive:
if (HEAP_XMAX_IS_EXCL_LOCKED(infomask) &&
infomask2 & HEAP_KEYS_UPDATED)
{
result = TM_Ok;
goto out_unlocked;
}
break;
}
}
}
/*
* Initially assume that we will have to wait for the locking
* transaction(s) to finish. We check various cases below in which
* this can be turned off.
*/
require_sleep = true;
if (mode == LockTupleKeyShare)
{
/*
* If we're requesting KeyShare, and there's no update present, we
* don't need to wait. Even if there is an update, we can still
* continue if the key hasn't been modified.
*
* However, if there are updates, we need to walk the update chain
* to mark future versions of the row as locked, too. That way,
* if somebody deletes that future version, we're protected
* against the key going away. This locking of future versions
* could block momentarily, if a concurrent transaction is
* deleting a key; or it could return a value to the effect that
* the transaction deleting the key has already committed. So we
* do this before re-locking the buffer; otherwise this would be
* prone to deadlocks.
*
* Note that the TID we're locking was grabbed before we unlocked
* the buffer. For it to change while we're not looking, the
* other properties we're testing for below after re-locking the
* buffer would also change, in which case we would restart this
* loop above.
*/
if (!(infomask2 & HEAP_KEYS_UPDATED))
{
bool updated;
updated = !HEAP_XMAX_IS_LOCKED_ONLY(infomask);
/*
* If there are updates, follow the update chain; bail out if
* that cannot be done.
*/
if (follow_updates && updated &&
!ItemPointerEquals(&tuple->t_self, &t_ctid))
{
TM_Result res;
res = heap_lock_updated_tuple(relation,
infomask, xwait, &t_ctid,
GetCurrentTransactionId(),
mode);
if (res != TM_Ok)
{
result = res;
/* recovery code expects to have buffer lock held */
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
goto failed;
}
}
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
/*
* Make sure it's still an appropriate lock, else start over.
* Also, if it wasn't updated before we released the lock, but
* is updated now, we start over too; the reason is that we
* now need to follow the update chain to lock the new
* versions.
*/
if (!HeapTupleHeaderIsOnlyLocked(tuple->t_data) &&
((tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED) ||
!updated))
goto l3;
/* Things look okay, so we can skip sleeping */
require_sleep = false;
/*
* Note we allow Xmax to change here; other updaters/lockers
* could have modified it before we grabbed the buffer lock.
* However, this is not a problem, because with the recheck we
* just did we ensure that they still don't conflict with the
* lock we want.
*/
}
}
else if (mode == LockTupleShare)
{
/*
* If we're requesting Share, we can similarly avoid sleeping if
* there's no update and no exclusive lock present.
*/
if (HEAP_XMAX_IS_LOCKED_ONLY(infomask) &&
!HEAP_XMAX_IS_EXCL_LOCKED(infomask))
{
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
/*
* Make sure it's still an appropriate lock, else start over.
* See above about allowing xmax to change.
*/
if (!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask) ||
HEAP_XMAX_IS_EXCL_LOCKED(tuple->t_data->t_infomask))
goto l3;
require_sleep = false;
}
}
else if (mode == LockTupleNoKeyExclusive)
{
/*
* If we're requesting NoKeyExclusive, we might also be able to
* avoid sleeping; just ensure that there no conflicting lock
* already acquired.
*/
if (infomask & HEAP_XMAX_IS_MULTI)
{
if (!DoesMultiXactIdConflict((MultiXactId) xwait, infomask,
mode, NULL))
{
/*
* No conflict, but if the xmax changed under us in the
* meantime, start over.
*/
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple->t_data),
xwait))
goto l3;
/* otherwise, we're good */
require_sleep = false;
}
}
else if (HEAP_XMAX_IS_KEYSHR_LOCKED(infomask))
{
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
/* if the xmax changed in the meantime, start over */
if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple->t_data),
xwait))
goto l3;
/* otherwise, we're good */
require_sleep = false;
}
}
/*
* As a check independent from those above, we can also avoid sleeping
* if the current transaction is the sole locker of the tuple. Note
* that the strength of the lock already held is irrelevant; this is
* not about recording the lock in Xmax (which will be done regardless
* of this optimization, below). Also, note that the cases where we
* hold a lock stronger than we are requesting are already handled
* above by not doing anything.
*
* Note we only deal with the non-multixact case here; MultiXactIdWait
* is well equipped to deal with this situation on its own.
*/
if (require_sleep && !(infomask & HEAP_XMAX_IS_MULTI) &&
TransactionIdIsCurrentTransactionId(xwait))
{
/* ... but if the xmax changed in the meantime, start over */
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple->t_data),
xwait))
goto l3;
Assert(HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask));
require_sleep = false;
}
/*
* Time to sleep on the other transaction/multixact, if necessary.
*
* If the other transaction is an update/delete that's already
* committed, then sleeping cannot possibly do any good: if we're
* required to sleep, get out to raise an error instead.
*
* By here, we either have already acquired the buffer exclusive lock,
* or we must wait for the locking transaction or multixact; so below
* we ensure that we grab buffer lock after the sleep.
*/
if (require_sleep && (result == TM_Updated || result == TM_Deleted))
{
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
goto failed;
}
else if (require_sleep)
{
/*
* Acquire tuple lock to establish our priority for the tuple, or
* die trying. LockTuple will release us when we are next-in-line
* for the tuple. We must do this even if we are share-locking,
* but not if we already have a weaker lock on the tuple.
*
* If we are forced to "start over" below, we keep the tuple lock;
* this arranges that we stay at the head of the line while
* rechecking tuple state.
*/
if (!skip_tuple_lock &&
!heap_acquire_tuplock(relation, tid, mode, wait_policy,
&have_tuple_lock))
{
/*
* This can only happen if wait_policy is Skip and the lock
* couldn't be obtained.
*/
result = TM_WouldBlock;
/* recovery code expects to have buffer lock held */
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
goto failed;
}
if (infomask & HEAP_XMAX_IS_MULTI)
{
MultiXactStatus status = get_mxact_status_for_lock(mode, false);
/* We only ever lock tuples, never update them */
if (status >= MultiXactStatusNoKeyUpdate)
elog(ERROR, "invalid lock mode in heap_lock_tuple");
/* wait for multixact to end, or die trying */
switch (wait_policy)
{
case LockWaitBlock:
MultiXactIdWait((MultiXactId) xwait, status, infomask,
relation, &tuple->t_self, XLTW_Lock, NULL);
break;
case LockWaitSkip:
if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
status, infomask, relation,
NULL, false))
{
result = TM_WouldBlock;
/* recovery code expects to have buffer lock held */
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
goto failed;
}
break;
case LockWaitError:
if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
status, infomask, relation,
NULL, log_lock_failures))
ereport(ERROR,
(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
errmsg("could not obtain lock on row in relation \"%s\"",
RelationGetRelationName(relation))));
break;
}
/*
* Of course, the multixact might not be done here: if we're
* requesting a light lock mode, other transactions with light
* locks could still be alive, as well as locks owned by our
* own xact or other subxacts of this backend. We need to
* preserve the surviving MultiXact members. Note that it
* isn't absolutely necessary in the latter case, but doing so
* is simpler.
*/
}
else
{
/* wait for regular transaction to end, or die trying */
switch (wait_policy)
{
case LockWaitBlock:
XactLockTableWait(xwait, relation, &tuple->t_self,
XLTW_Lock);
break;
case LockWaitSkip:
if (!ConditionalXactLockTableWait(xwait, false))
{
result = TM_WouldBlock;
/* recovery code expects to have buffer lock held */
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
goto failed;
}
break;
case LockWaitError:
if (!ConditionalXactLockTableWait(xwait, log_lock_failures))
ereport(ERROR,
(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
errmsg("could not obtain lock on row in relation \"%s\"",
RelationGetRelationName(relation))));
break;
}
}
/* if there are updates, follow the update chain */
if (follow_updates && !HEAP_XMAX_IS_LOCKED_ONLY(infomask) &&
!ItemPointerEquals(&tuple->t_self, &t_ctid))
{
TM_Result res;
res = heap_lock_updated_tuple(relation,
infomask, xwait, &t_ctid,
GetCurrentTransactionId(),
mode);
if (res != TM_Ok)
{
result = res;
/* recovery code expects to have buffer lock held */
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
goto failed;
}
}
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
/*
* xwait is done, but if xwait had just locked the tuple then some
* other xact could update this tuple before we get to this point.
* Check for xmax change, and start over if so.
*/
if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple->t_data),
xwait))
goto l3;
if (!(infomask & HEAP_XMAX_IS_MULTI))
{
/*
* Otherwise check if it committed or aborted. Note we cannot
* be here if the tuple was only locked by somebody who didn't
* conflict with us; that would have been handled above. So
* that transaction must necessarily be gone by now. But
* don't check for this in the multixact case, because some
* locker transactions might still be running.
*/
UpdateXmaxHintBits(tuple->t_data, *buffer, xwait);
}
}
/* By here, we're certain that we hold buffer exclusive lock again */
/*
* 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
* at all for whatever reason.
*/
if (!require_sleep ||
(tuple->t_data->t_infomask & HEAP_XMAX_INVALID) ||
HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask) ||
HeapTupleHeaderIsOnlyLocked(tuple->t_data))
result = TM_Ok;
else if (!ItemPointerEquals(&tuple->t_self, &tuple->t_data->t_ctid))
result = TM_Updated;
else
result = TM_Deleted;
}
failed:
if (result != TM_Ok)
{
Assert(result == TM_SelfModified || result == TM_Updated ||
result == TM_Deleted || result == TM_WouldBlock);
/*
* When locking a tuple under LockWaitSkip semantics and we fail with
* TM_WouldBlock above, it's possible for concurrent transactions to
* release the lock and set HEAP_XMAX_INVALID in the meantime. So
* this assert is slightly different from the equivalent one in
* heap_delete and heap_update.
*/
Assert((result == TM_WouldBlock) ||
!(tuple->t_data->t_infomask & HEAP_XMAX_INVALID));
Assert(result != TM_Updated ||
!ItemPointerEquals(&tuple->t_self, &tuple->t_data->t_ctid));
tmfd->ctid = tuple->t_data->t_ctid;
tmfd->xmax = HeapTupleHeaderGetUpdateXid(tuple->t_data);
if (result == TM_SelfModified)
tmfd->cmax = HeapTupleHeaderGetCmax(tuple->t_data);
else
tmfd->cmax = InvalidCommandId;
goto out_locked;
}
/*
* If we didn't pin the visibility map page and the page has become all
* visible while we were busy locking the buffer, or during some
* subsequent window during which we had it unlocked, we'll have to unlock
* and re-lock, to avoid holding the buffer lock across I/O. That's a bit
* unfortunate, especially since we'll now have to recheck whether the
* tuple has been locked or updated under us, but hopefully it won't
* happen very often.
*/
if (vmbuffer == InvalidBuffer && PageIsAllVisible(page))
{
LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
visibilitymap_pin(relation, block, &vmbuffer);
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
goto l3;
}
/*
* 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
* MultiXactIds than that. (We have to do this even if we end up just
* using our own TransactionId below, since some other backend could
* incorporate our XID into a MultiXact immediately afterwards.)
*/
MultiXactIdSetOldestMember();
/*
* Compute the new xmax and infomask to store into the tuple. Note we do
* not modify the tuple just yet, because that would leave it in the wrong
* state if multixact.c elogs.
*/
compute_new_xmax_infomask(xmax, old_infomask, tuple->t_data->t_infomask2,
GetCurrentTransactionId(), mode, false,
&xid, &new_infomask, &new_infomask2);
START_CRIT_SECTION();
/*
* Store transaction information of xact locking the tuple.
*
* Note: Cmax is meaningless in this context, so don't set it; this avoids
* possibly generating a useless combo CID. Moreover, if we're locking a
* previously updated tuple, it's important to preserve the Cmax.
*
* Also reset the HOT UPDATE bit, but only if there's no update; otherwise
* we would break the HOT chain.
*/
tuple->t_data->t_infomask &= ~HEAP_XMAX_BITS;
tuple->t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
tuple->t_data->t_infomask |= new_infomask;
tuple->t_data->t_infomask2 |= new_infomask2;
if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
HeapTupleHeaderClearHotUpdated(tuple->t_data);
HeapTupleHeaderSetXmax(tuple->t_data, xid);
/*
* Make sure there is no forward chain link in t_ctid. Note that in the
* cases where the tuple has been updated, we must not overwrite t_ctid,
* because it was set by the updater. Moreover, if the tuple has been
* updated, we need to follow the update chain to lock the new versions of
* the tuple as well.
*/
if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
tuple->t_data->t_ctid = *tid;
/* Clear only the all-frozen bit on visibility map if needed */
if (PageIsAllVisible(page) &&
visibilitymap_clear(relation, block, vmbuffer,
VISIBILITYMAP_ALL_FROZEN))
cleared_all_frozen = true;
MarkBufferDirty(*buffer);
/*
* XLOG stuff. You might think that we don't need an XLOG record because
* there is no state change worth restoring after a crash. You would be
* wrong however: we have just written either a TransactionId or a
* MultiXactId that may never have been seen on disk before, and we need
* to make sure that there are XLOG entries covering those ID numbers.
* Else the same IDs might be re-used after a crash, which would be
* disastrous if this page made it to disk before the crash. Essentially
* we have to enforce the WAL log-before-data rule even in this case.
* (Also, in a PITR log-shipping or 2PC environment, we have to have XLOG
* entries for everything anyway.)
*/
if (RelationNeedsWAL(relation))
{
xl_heap_lock xlrec;
XLogRecPtr recptr;
out_unlocked:
if (BufferIsValid(vmbuffer))
ReleaseBuffer(vmbuffer);
/*
* Don't update the visibility map here. Locking a tuple doesn't change
* visibility info.
*/
/*
* Now that we have successfully marked the tuple as locked, we can
* release the lmgr tuple lock, if we had it.
*/
if (have_tuple_lock)
UnlockTupleTuplock(relation, tid, mode);
return result;
}
/*
* Acquire heavyweight lock on the given tuple, in preparation for acquiring
* its normal, Xmax-based tuple lock.
*
* have_tuple_lock is an input and output parameter: on input, it indicates
* whether the lock has previously been acquired (and this function does
* nothing in that case). If this function returns success, have_tuple_lock
* has been flipped to true.
*
* Returns false if it was unable to obtain the lock; this can only happen if
* wait_policy is Skip.
*/
static bool
heap_acquire_tuplock(Relation relation, ItemPointer tid, LockTupleMode mode,
LockWaitPolicy wait_policy, bool *have_tuple_lock)
{
if (*have_tuple_lock)
return true;
switch (wait_policy)
{
case LockWaitBlock:
LockTupleTuplock(relation, tid, mode);
break;
case LockWaitSkip:
if (!ConditionalLockTupleTuplock(relation, tid, mode, false))
return false;
break;
case LockWaitError:
if (!ConditionalLockTupleTuplock(relation, tid, mode, log_lock_failures))
ereport(ERROR,
(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
errmsg("could not obtain lock on row in relation \"%s\"",
RelationGetRelationName(relation))));
break;
}
*have_tuple_lock = true;
return true;
}
/*
* Given an original set of Xmax and infomask, and a transaction (identified by
* add_to_xmax) acquiring a new lock of some mode, compute the new Xmax and
* corresponding infomasks to use on the tuple.
*
* Note that this might have side effects such as creating a new MultiXactId.
*
* Most callers will have called HeapTupleSatisfiesUpdate before this function;
* that will have set the HEAP_XMAX_INVALID bit if the xmax was a MultiXactId
* but it was not running anymore. There is a race condition, which is that the
* MultiXactId may have finished since then, but that uncommon case is handled
* either here, or within MultiXactIdExpand.
*
Therea similar race condition possible when the oldxmax was regular
* java.lang.StringIndexOutOfBoundsException: Range [52, 16) out of bounds for length 77
butit' possible to endup creatingan java.lang.StringIndexOutOfBoundsException: Range [68, 69) out of bounds for length 68
* java.lang.StringIndexOutOfBoundsException: Index 14 out of bounds for length 0
*/
static void
compute_new_xmax_infomask(TransactionId xmax, uint16 old_infomask,
uint16 old_infomask2,TransactionId add_to_xmax
LockTupleMode ,boolis_update,
TransactionId *result_xmax, uint16 *result_infomask,
uint16 *result_infomask2)
java.lang.StringIndexOutOfBoundsException: Index 1 out of bounds for length 1
TransactionId new_xmax;
uint16 new_infomask,by Contributorthatare necessarily infringedby their
Assert((add_to_xmax);
l5:
new_infomask = 0;
new_infomask2 = 0 mayadd Yourown copyrightstatement to Your modifications and
if &HEAP_XMAX_INVALID)
{
/*
*previous;weinsertour TransactionId.
*
thats critical that this casebe the java.lang.StringIndexOutOfBoundsException: Range [69, 68) out of bounds for length 69
* because there are several blocks below that come back to this one
* to implement certain optimizations; old_infomask might contain
* other dirty bits in those cases, but we don't really care.
*/
if (is_update)
{
new_xmax = add_to_xmax;
if (mode == LockTupleExclusive)
new_infomask2 |= HEAP_KEYS_UPDATED;
}
else
{
new_infomask |= HEAP_XMAX_LOCK_ONLY;
switch (mode)
{
case LockTupleKeyShare:
new_xmax = add_to_xmax;
new_infomask |= HEAP_XMAX_KEYSHR_LOCK;
break;
case LockTupleShare:
new_xmax = add_to_xmax;
new_infomask |= HEAP_XMAX_SHR_LOCK;
break;
case LockTupleNoKeyExclusive:
new_xmax = add_to_xmax;
new_infomask |= HEAP_XMAX_EXCL_LOCK;
break;
case LockTupleExclusive:
new_xmax = add_to_xmax;
new_infomask |= HEAP_XMAX_EXCL_LOCK;
new_infomask2 |= HEAP_KEYS_UPDATED;
break;
default:
new_xmax = InvalidTransactionId; /* silence compiler */
elog(ERROR, "invalid lock mode");
}
}
}
else if (old_infomask & HEAP_XMAX_IS_MULTI)
{
MultiXactStatus new_status;
/*
* Currently we don't allow XMAX_COMMITTED to be set for multis, so
* cross-check.
*/
Assert(!(old_infomask & HEAP_XMAX_COMMITTED));
/*
* A multixact together with LOCK_ONLY set but neither lock bit set
* (i.e. a pg_upgraded share locked tuple) cannot possibly be running
* anymore. This check is critical for databases upgraded by
* pg_upgrade; both MultiXactIdIsRunning and MultiXactIdExpand assume
* that such multis are never passed.
*/
if (HEAP_LOCKED_UPGRADED(old_infomask))
{
old_infomask &= ~HEAP_XMAX_IS_MULTI;
old_infomask |= HEAP_XMAX_INVALID;
goto 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, HEAP_XMAX_IS_LOCKED_ONLY(old_infomask)))
{
if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) ||
!TransactionIdDidCommit(MultiXactIdGetUpdateXid(xmax,
old_infomask)))
{
/*
* Reset these bits and restart; otherwise fall through to
* create a new multi below.
*/
old_infomask &= ~HEAP_XMAX_IS_MULTI;
old_infomask |= HEAP_XMAX_INVALID;
goto l5;
}
}
new_xmax = MultiXactIdExpand((MultiXactId) xmax, add_to_xmax,
new_status);
GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
}
else if (old_infomask & HEAP_XMAX_COMMITTED)
{
/*
* It's a committed update, so we need to preserve him as updater of
* the tuple.
*/
MultiXactStatus status;
MultiXactStatus new_status;
if (old_infomask2 & HEAP_KEYS_UPDATED)
status = MultiXactStatusUpdate;
else
status = MultiXactStatusNoKeyUpdate;
/*
* since it's not running, it's obviously impossible for the old
* updater to be identical to the current one, so we need not check
* for that case as we do in the block above.
*/
new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status);
GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
}
else if (TransactionIdIsInProgress(xmax))
{
/*
* If the XMAX is a valid, in-progress TransactionId, then we need to
* create a new MultiXactId that includes both the old locker or
* updater and our own TransactionId.
*/
MultiXactStatus new_status;
MultiXactStatus old_status;
LockTupleMode old_mode;
if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask))
{
if (HEAP_XMAX_IS_KEYSHR_LOCKED(old_infomask))
old_status = MultiXactStatusForKeyShare;
else if (HEAP_XMAX_IS_SHR_LOCKED(old_infomask))
old_status = MultiXactStatusForShare;
else if (HEAP_XMAX_IS_EXCL_LOCKED(old_infomask))
{
if (old_infomask2 & HEAP_KEYS_UPDATED)
old_status = MultiXactStatusForUpdate;
else
old_status = MultiXactStatusForNoKeyUpdate;
}
else
{
/*
* LOCK_ONLY can be present alone only when a page has been
* upgraded by pg_upgrade. But in that case,
* TransactionIdIsInProgress() should have returned false. We
* assume it's no longer locked in this case.
*/
elog(WARNING, "LOCK_ONLY found for Xid in progress %u", xmax);
old_infomask |= HEAP_XMAX_INVALID;
old_infomask &= ~HEAP_XMAX_LOCK_ONLY;
goto l5;
}
}
else
{
/* it's an update, but which kind? */
if (old_infomask2 & HEAP_KEYS_UPDATED)
old_status = MultiXactStatusUpdate;
else
old_status = MultiXactStatusNoKeyUpdate;
}
old_mode = TUPLOCK_from_mxstatus(old_status);
/*
* If the lock to be acquired is for the same TransactionId as the
* existing lock, there's an optimization possible: consider only the
* strongest of both locks as the only one present, and restart.
*/
if (xmax == add_to_xmax)
{
/*
* Note that it's not possible for the original tuple to be
* updated: we wouldn't be here because the tuple would have been
* invisible and we wouldn't try to update it. As a subtlety,
* this code can also run when traversing an update chain to lock
* future versions of a tuple. But we wouldn't be here either,
* because the add_to_xmax would be different from the original
* updater.
*/
Assert(HEAP_XMAX_IS_LOCKED_ONLY(old_infomask));
/* acquire the strongest of both */
if (mode < old_mode)
mode = old_mode;
/* mustn't touch is_update */
old_infomask |= HEAP_XMAX_INVALID;
goto l5;
}
/* otherwise, just fall back to creating a new multixact */
new_status = get_mxact_status_for_lock(mode, is_update);
new_xmax = MultiXactIdCreate(xmax, old_status,
add_to_xmax, new_status);
GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
}
else if (!HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) &&
TransactionIdDidCommit(xmax))
{
/*
* It's a committed update, so we gotta preserve him as updater of the
* tuple.
*/
MultiXactStatus status;
MultiXactStatus new_status;
if (old_infomask2 & HEAP_KEYS_UPDATED)
status = MultiXactStatusUpdate;
else
status = MultiXactStatusNoKeyUpdate;
/*
* since it's not running, it's obviously impossible for the old
* updater to be identical to the current one, so we need not check
* for that case as we do in the block above.
*/
new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status);
GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
}
else
{
/*
* Can get here iff the locking/updating transaction was running when
* the infomask was extracted from the tuple, but finished before
* TransactionIdIsInProgress got to run. Deal with it as if there was
* no locker at all in the first place.
*/
old_infomask |= HEAP_XMAX_INVALID;
goto l5;
}
/*
* Subroutine for heap_lock_updated_tuple_rec.
*
* Given a hypothetical multixact status held by the transaction identified
* with the given xid, does the current transaction need to wait, fail, or can
* it continue if it wanted to acquire a lock of the given mode? "needwait"
* is set to true if waiting is necessary; if it can continue, then TM_Ok is
* returned. If the lock is already held by the current transaction, return
* TM_SelfModified. In case of a conflict with another transaction, a
* different HeapTupleSatisfiesUpdate return code is returned.
*
* The held status is said to be hypothetical because it might correspond to a
* lock held by a single Xid, i.e. not a real MultiXactId; we express it this
* way for simplicity of API.
*/
static TM_Result
test_lockmode_for_conflict(MultiXactStatus status, TransactionId xid,
LockTupleMode mode, HeapTuple tup,
bool *needwait)
{
MultiXactStatus wantedstatus;
/*
* Note: we *must* check TransactionIdIsInProgress before
* TransactionIdDidAbort/Commit; see comment at top of heapam_visibility.c
* for an explanation.
*/
if (TransactionIdIsCurrentTransactionId(xid))
{
/*
* The tuple has already been locked by our own transaction. This is
* very rare but can happen if multiple transactions are trying to
* lock an ancient version of the same tuple.
*/
return TM_SelfModified;
}
else if (TransactionIdIsInProgress(xid))
{
/*
* If the locking transaction is running, what we do depends on
* whether the lock modes conflict: if they do, then we must wait for
* it to finish; otherwise we can fall through to lock this tuple
* version without waiting.
*/
if (DoLockModesConflict(LOCKMODE_from_mxstatus(status),
LOCKMODE_from_mxstatus(wantedstatus)))
{
*needwait = true;
}
/*
* If we set needwait above, then this value doesn't matter;
* otherwise, this value signals to caller that it's okay to proceed.
*/
return TM_Ok;
}
else if (TransactionIdDidAbort(xid))
return TM_Ok;
else if (TransactionIdDidCommit(xid))
{
/*
* The other transaction committed. If it was only a locker, then the
* lock is completely gone now and we can return success; but if it
* was an update, then what we do depends on whether the two lock
* modes conflict. If they conflict, then we must report error to
* caller. But if they don't, we can fall through to allow the current
* transaction to lock the tuple.
*
* Note: the reason we worry about ISUPDATE here is because as soon as
* a transaction ends, all its locks are gone and meaningless, and
* thus we can ignore them; whereas its updates persist. In the
* TransactionIdIsInProgress case, above, we don't need to check
* because we know the lock is still "alive" and thus a conflict needs
* always be checked.
*/
if (!ISUPDATE_from_mxstatus(status))
return TM_Ok;
if (DoLockModesConflict(LOCKMODE_from_mxstatus(status),
LOCKMODE_from_mxstatus(wantedstatus)))
{
/* bummer */
if (!ItemPointerEquals(&tup->t_self, &tup->t_data->t_ctid))
return TM_Updated;
else
return TM_Deleted;
}
return TM_Ok;
}
/* Not in progress, not aborted, not committed -- must have crashed */
return TM_Ok;
}
/*
* Recursive part of heap_lock_updated_tuple
*
* Fetch the tuple pointed to by tid in rel, and mark it as locked by the given
* xid with the given mode; if this tuple is updated, recurse to lock the new
* version as well.
*/
static TM_Result
heap_lock_updated_tuple_rec(Relation rel, TransactionId priorXmax,
const ItemPointerData *tid, TransactionId xid,
LockTupleMode mode)
{
TM_Result result;
ItemPointerData tupid;
HeapTupleData mytup;
Buffer buf;
uint16 new_infomask,
new_infomask2,
old_infomask,
old_infomask2;
TransactionId xmax,
new_xmax;
bool cleared_all_frozen = false;
bool pinned_desired_page;
Buffer vmbuffer = InvalidBuffer;
BlockNumber block;
if (!heap_fetch(rel, SnapshotAny, &mytup, &buf, false))
{
/*
* if we fail to find the updated version of the tuple, it's
* because it was vacuumed/pruned away after its creator
* transaction aborted. So behave as if we got to the end of the
* chain, and there's no further tuple to lock: return success to
* caller.
*/
result = TM_Ok;
goto out_unlocked;
}
/* *Foranon-multiXmax,wefirstneedtocomputethe *correspondingMultiXactStatusbyusingtheinfomaskbits.
*/ if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask))
{ if (HEAP_XMAX_IS_KEYSHR_LOCKED(old_infomask))
status = MultiXactStatusForKeyShare; elseif (HEAP_XMAX_IS_SHR_LOCKED(old_infomask))
status = MultiXactStatusForShare; elseif (HEAP_XMAX_IS_EXCL_LOCKED(old_infomask))
{ if (old_infomask2 & HEAP_KEYS_UPDATED)
status = MultiXactStatusForUpdate; else
status = MultiXactStatusForNoKeyUpdate;
} else
{ /* *LOCK_ONLYpresentalone(apg_upgradedtuplemarked *asshare-lockedintheoldcluster)shouldn'tbe *seeninthemiddleofanupdatechain.
*/
elog(ERROR, "invalid lock status in tuple");
}
} else
{ /* it's an update, but which kind? */ if (old_infomask2 & HEAP_KEYS_UPDATED)
status = MultiXactStatusUpdate; else
status = MultiXactStatusNoKeyUpdate;
}
result = test_lockmode_for_conflict(status, rawxmax, mode,
&mytup, &needwait);
/* compute the new Xmax and infomask values for the tuple ... */
compute_new_xmax_infomask(xmax, old_infomask, mytup.t_data->t_infomask2,
xid, mode, false,
&new_xmax, &new_infomask, &new_infomask2);
if (PageIsAllVisible(BufferGetPage(buf)) &&
visibilitymap_clear(rel, block, vmbuffer,
VISIBILITYMAP_ALL_FROZEN))
cleared_all_frozen = true;
START_CRIT_SECTION();
/* ... and set them */
HeapTupleHeaderSetXmax(mytup.t_data, new_xmax);
mytup.t_data->t_infomask &= ~HEAP_XMAX_BITS;
mytup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
mytup.t_data->t_infomask |= new_infomask;
mytup.t_data->t_infomask2 |= new_infomask2;
next: /* if we find the end of update chain, we're done. */ if (mytup.t_data->t_infomask & HEAP_XMAX_INVALID ||
HeapTupleHeaderIndicatesMovedPartitions(mytup.t_data) ||
ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid) ||
HeapTupleHeaderIsOnlyLocked(mytup.t_data))
{
result = TM_Ok; goto out_locked;
}
/* *Sanitycheckthatthetuplereallyisaspeculativelyinsertedtuple, *insertedbyus.
*/ if (tp.t_data->t_choice.t_heap.t_xmin != xid)
elog(ERROR, "attempted to kill a tuple inserted by another transaction"); if (!(IsToastRelation(relation) || HeapTupleHeaderIsSpeculative(tp.t_data)))
elog(ERROR, "attempted to kill a non-speculative tuple");
Assert(!HeapTupleHeaderIsHeapOnly(tp.t_data));
/* store transaction information of xact deleting the tuple */
tp.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
tp.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
if (result == TM_Invisible)
{ /* no known way this can happen */
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg_internal("attempted to overwrite invisible tuple")));
} elseif (result == TM_SelfModified)
{ /* *CREATEINDEXmightreachthisifanexpressionissillyenoughto *calle.g.SELECT...FROMpg_classFORSHARE.CcodeofotherSQL *statementsmightgethereafteraheap_update()ofthesamerow,in *theabsenceofaninterveningCommandCounterIncrement().
*/
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("tuple to be updated was already modified by an operation triggered by the current command")));
} elseif (result == TM_BeingModified)
{
TransactionId xwait;
uint16 infomask;
/* Like RecordTransactionCommit(), log only if needed */ if (XLogStandbyInfoActive())
nmsgs = inplaceGetInvalidationMessages(&invalMessages,
&RelcacheInitFileInval);
/* *Thisoldmulticannotpossiblyhavemembersstillrunning,but *verifyjustincase.Ifitwasalockeronly,itcanberemoved *withoutanyfurtherconsideration;butifitcontainedanupdate, *wemightneedtopreserveit.
*/ if (MultiXactIdIsRunning(multi,
HEAP_XMAX_IS_LOCKED_ONLY(t_infomask)))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
errmsg_internal("multixact %u from before multi freeze cutoff %u found to be still running",
multi, cutoffs->OldestMxact)));
if (TransactionIdPrecedes(xid, cutoffs->FreezeLimit))
{
/* Can't violate the FreezeLimit postcondition */
need_replace = true;
break;
}
if (TransactionIdPrecedes(xid, FreezePageRelfrozenXid))
FreezePageRelfrozenXid = xid;
}
/* Can't violate the MultiXactCutoff postcondition, either */
if (!need_replace)
need_replace = MultiXactIdPrecedes(multi, cutoffs->MultiXactCutoff);
if (!need_replace)
{
/*
* vacuumlazy.c might ratchet back NewRelminMxid, NewRelfrozenXid, or
* both together to make it safe to retain this particular multi after
* freezing its page
*/
*flags |= FRM_NOOP;
pagefrz->FreezePageRelfrozenXid = FreezePageRelfrozenXid;
if (MultiXactIdPrecedes(multi, pagefrz->FreezePageRelminMxid))
pagefrz->FreezePageRelminMxid = multi;
pfree(members);
return multi;
}
/*
* Do a more thorough second pass over the multi to figure out which
* member XIDs actually need to be kept. Checking the precise status of
* individual members might even show that we don't need to keep anything.
* That is quite possible even though the Multi must be >= OldestMxact,
* since our second pass only keeps member XIDs when it's truly necessary;
* even member XIDs >= OldestXmin often won't be kept by second pass.
*/
nnewmembers = 0;
newmembers = palloc(sizeof(MultiXactMember) * nmembers);
has_lockers = false;
update_xid = InvalidTransactionId;
update_committed = false;
/*
* Determine whether to keep each member xid, or to ignore it instead
*/
for (int i = 0; i < nmembers; i++)
{
TransactionId xid = members[i].xid;
MultiXactStatus mstatus = members[i].status;
if (!ISUPDATE_from_mxstatus(mstatus))
{
/*
* Locker XID (not updater XID). We only keep lockers that are
* still running.
*/
if (TransactionIdIsCurrentTransactionId(xid) ||
TransactionIdIsInProgress(xid))
{
if (TransactionIdPrecedes(xid, cutoffs->OldestXmin))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
errmsg_internal("multixact %u contains running locker XID %u from before removable cutoff %u",
multi, xid,
cutoffs->OldestXmin)));
newmembers[nnewmembers++] = members[i];
has_lockers = true;
}
continue;
}
/*
* Updater XID (not locker XID). Should we keep it?
*
* Since the tuple wasn't totally removed when vacuum pruned, the
* update Xid cannot possibly be older than OldestXmin cutoff unless
* the updater XID aborted. If the updater transaction is known
* aborted or crashed then it's okay to ignore it, otherwise not.
*
* In any case the Multi should never contain two updaters, whatever
* their individual commit status. Check for that first, in passing.
*/
if (TransactionIdIsValid(update_xid))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
errmsg_internal("multixact %u has two or more updating members",
multi),
errdetail_internal("First updater XID=%u second updater XID=%u.",
update_xid, xid)));
/*
* As with all tuple visibility routines, it's critical to test
* TransactionIdIsInProgress before TransactionIdDidCommit, because of
* race conditions explained in detail in heapam_visibility.c.
*/
if (TransactionIdIsCurrentTransactionId(xid) ||
TransactionIdIsInProgress(xid))
update_xid = xid;
else if (TransactionIdDidCommit(xid))
{
/*
* The transaction committed, so we can tell caller to set
* HEAP_XMAX_COMMITTED. (We can only do this because we know the
* transaction is not running.)
*/
update_committed = true;
update_xid = xid;
}
else
{
/*
* Not in progress, not committed -- must be aborted or crashed;
* we can ignore it.
*/
continue;
}
/*
* We determined that updater must be kept -- add it to pending new
* members list
*/
if (TransactionIdPrecedes(xid, cutoffs->OldestXmin))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
errmsg_internal("multixact %u contains committed update XID %u from before removable cutoff %u",
multi, xid, cutoffs->OldestXmin)));
newmembers[nnewmembers++] = members[i];
}
pfree(members);
/*
* Determine what to do with caller's multi based on information gathered
* during our second pass
*/
if (nnewmembers == 0)
{
/* Nothing worth keeping */
*flags |= FRM_INVALIDATE_XMAX;
newxmax = InvalidTransactionId;
}
else if (TransactionIdIsValid(update_xid) && !has_lockers)
{
/*
* If there's a single member and it's an update, pass it back alone
* without creating a new Multi. (XXX we could do this when there's a
* single remaining locker, too, but that would complicate the API too
* much; moreover, the case with the single updater is more
* interesting, because those are longer-lived.)
*/
Assert(nnewmembers == 1);
*flags |= FRM_RETURN_IS_XID;
if (update_committed)
*flags |= FRM_MARK_COMMITTED;
newxmax = update_xid;
}
else
{
/*
* Create a new multixact with the surviving members of the previous
* one, to set as new Xmax in the tuple
*/
newxmax = MultiXactIdCreateFromMembers(nnewmembers, newmembers);
*flags |= FRM_RETURN_IS_MULTI;
}
/*
* heap_prepare_freeze_tuple
*
* Check to see whether any of the XID fields of a tuple (xmin, xmax, xvac)
* are older than the OldestXmin and/or OldestMxact freeze cutoffs. If so,
* setup enough state (in the *frz output argument) to enable caller to
* process this tuple as part of freezing its page, and return true. Return
* false if nothing can be changed about the tuple right now.
*
* Also sets *totally_frozen to true if the tuple will be totally frozen once
* caller executes returned freeze plan (or if the tuple was already totally
* frozen by an earlier VACUUM). This indicates that there are no remaining
* XIDs or MultiXactIds that will need to be processed by a future VACUUM.
*
* VACUUM caller must assemble HeapTupleFreeze freeze plan entries for every
* tuple that we returned true for, and then execute freezing. Caller must
* initialize pagefrz fields for page as a whole before first call here for
* each heap page.
*
* VACUUM caller decides on whether or not to freeze the page as a whole.
* We'll often prepare freeze plans for a page that caller just discards.
* However, VACUUM doesn't always get to make a choice; it must freeze when
* pagefrz.freeze_required is set, to ensure that any XIDs < FreezeLimit (and
* MXIDs < MultiXactCutoff) can never be left behind. We help to make sure
* that VACUUM always follows that rule.
*
* We sometimes force freezing of xmax MultiXactId values long before it is
* strictly necessary to do so just to ensure the FreezeLimit postcondition.
* It's worth processing MultiXactIds proactively when it is cheap to do so,
* and it's convenient to make that happen by piggy-backing it on the "force
* freezing" mechanism. Conversely, we sometimes delay freezing MultiXactIds
* because it is expensive right now (though only when it's still possible to
* do so without violating the FreezeLimit/MultiXactCutoff postcondition).
*
* It is assumed that the caller has checked the tuple with
* HeapTupleSatisfiesVacuum() and determined that it is not HEAPTUPLE_DEAD
* (else we should be removing the tuple, not freezing it).
*
* NB: This function has side effects: it might allocate a new MultiXactId.
* It will be set as tuple's new xmax when our *frz output is processed within
* heap_execute_freeze_tuple later on. If the tuple is in a shared buffer
* then caller had better have an exclusive lock on it already.
*/
bool
heap_prepare_freeze_tuple(HeapTupleHeader tuple,
const struct VacuumCutoffs *cutoffs,
HeapPageFreeze *pagefrz,
HeapTupleFreeze *frz, bool *totally_frozen)
{
bool xmin_already_frozen = false,
xmax_already_frozen = false;
bool freeze_xmin = false,
replace_xvac = false,
replace_xmax = false,
freeze_xmax = false;
TransactionId xid;
/*
* Process xmin, while keeping track of whether it's already frozen, or
* will become frozen iff our freeze plan is executed by caller (could be
* neither).
*/
xid = HeapTupleHeaderGetXmin(tuple);
if (!TransactionIdIsNormal(xid))
xmin_already_frozen = true;
else
{
if (TransactionIdPrecedes(xid, cutoffs->relfrozenxid))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
errmsg_internal("found xmin %u from before relfrozenxid %u",
xid, cutoffs->relfrozenxid)));
/* Will set freeze_xmin flags in freeze plan below */
freeze_xmin = TransactionIdPrecedes(xid, cutoffs->OldestXmin);
/* Verify that xmin committed if and when freeze plan is executed */
if (freeze_xmin)
frz->checkflags |= HEAP_FREEZE_CHECK_XMIN_COMMITTED;
}
/*
* Old-style VACUUM FULL is gone, but we have to process xvac for as long
* as we support having MOVED_OFF/MOVED_IN tuples in the database
*/
xid = HeapTupleHeaderGetXvac(tuple);
if (TransactionIdIsNormal(xid))
{
Assert(TransactionIdPrecedesOrEquals(cutoffs->relfrozenxid, xid));
Assert(TransactionIdPrecedes(xid, cutoffs->OldestXmin));
/*
* For Xvac, we always freeze proactively. This allows totally_frozen
* tracking to ignore xvac.
*/
replace_xvac = pagefrz->freeze_required = true;
/* Will set replace_xvac flags in freeze plan below */
}
/* Now process xmax */
xid = frz->xmax;
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
/* Raw xmax is a MultiXactId */
TransactionId newxmax;
uint16 flags;
/*
* We will either remove xmax completely (in the "freeze_xmax" path),
* process xmax by replacing it (in the "replace_xmax" path), or
* perform no-op xmax processing. The only constraint is that the
* FreezeLimit/MultiXactCutoff postcondition must never be violated.
*/
newxmax = FreezeMultiXactId(xid, tuple->t_infomask, cutoffs,
&flags, pagefrz);
if (flags & FRM_NOOP)
{
/*
* xmax is a MultiXactId, and nothing about it changes for now.
* This is the only case where 'freeze_required' won't have been
* set for us by FreezeMultiXactId, as well as the only case where
* neither freeze_xmax nor replace_xmax are set (given a multi).
*
* This is a no-op, but the call to FreezeMultiXactId might have
* ratcheted back NewRelfrozenXid and/or NewRelminMxid trackers
* for us (the "freeze page" variants, specifically). That'll
* make it safe for our caller to freeze the page later on, while
* leaving this particular xmax undisturbed.
*
* FreezeMultiXactId is _not_ responsible for the "no freeze"
* NewRelfrozenXid/NewRelminMxid trackers, though -- that's our
* job. A call to heap_tuple_should_freeze for this same tuple
* will take place below if 'freeze_required' isn't set already.
* (This repeats work from FreezeMultiXactId, but allows "no
* freeze" tracker maintenance to happen in only one place.)
*/
Assert(!MultiXactIdPrecedes(newxmax, cutoffs->MultiXactCutoff));
Assert(MultiXactIdIsValid(newxmax) && xid == newxmax);
}
else if (flags & FRM_RETURN_IS_XID)
{
/*
* xmax will become an updater Xid (original MultiXact's updater
* member Xid will be carried forward as a simple Xid in Xmax).
*/
Assert(!TransactionIdPrecedes(newxmax, cutoffs->OldestXmin));
/*
* NB -- some of these transformations are only valid because we
* know the return Xid is a tuple updater (i.e. not merely a
* locker.) Also note that the only reason we don't explicitly
* worry about HEAP_KEYS_UPDATED is because it lives in
* t_infomask2 rather than t_infomask.
*/
frz->t_infomask &= ~HEAP_XMAX_BITS;
frz->xmax = newxmax;
if (flags & FRM_MARK_COMMITTED)
frz->t_infomask |= HEAP_XMAX_COMMITTED;
replace_xmax = true;
}
else if (flags & FRM_RETURN_IS_MULTI)
{
uint16 newbits;
uint16 newbits2;
/*
* xmax is an old MultiXactId that we have to replace with a new
* MultiXactId, to carry forward two or more original member XIDs.
*/
Assert(!MultiXactIdPrecedes(newxmax, cutoffs->OldestMxact));
/*
* We can't use GetMultiXactIdHintBits directly on the new multi
* here; that routine initializes the masks to all zeroes, which
* would lose other bits we need. Doing it this way ensures all
* unrelated bits remain untouched.
*/
frz->t_infomask &= ~HEAP_XMAX_BITS;
frz->t_infomask2 &= ~HEAP_KEYS_UPDATED;
GetMultiXactIdHintBits(newxmax, &newbits, &newbits2);
frz->t_infomask |= newbits;
frz->t_infomask2 |= newbits2;
frz->xmax = newxmax;
replace_xmax = true;
}
else
{
/*
* Freeze plan for tuple "freezes xmax" in the strictest sense:
* it'll leave nothing in xmax (neither an Xid nor a MultiXactId).
*/
Assert(flags & FRM_INVALIDATE_XMAX);
Assert(!TransactionIdIsValid(newxmax));
/* Will set freeze_xmax flags in freeze plan below */
freeze_xmax = true;
}
/* MultiXactId processing forces freezing (barring FRM_NOOP case) */
Assert(pagefrz->freeze_required || (!freeze_xmax && !replace_xmax));
}
else if (TransactionIdIsNormal(xid))
{
/* Raw xmax is normal XID */
if (TransactionIdPrecedes(xid, cutoffs->relfrozenxid))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
errmsg_internal("found xmax %u from before relfrozenxid %u",
xid, cutoffs->relfrozenxid)));
/* Will set freeze_xmax flags in freeze plan below */
freeze_xmax = TransactionIdPrecedes(xid, cutoffs->OldestXmin);
/*
* Verify that xmax aborted if and when freeze plan is executed,
* provided it's from an update. (A lock-only xmax can be removed
* independent of this, since the lock is released at xact end.)
*/
if (freeze_xmax && !HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
frz->checkflags |= HEAP_FREEZE_CHECK_XMAX_ABORTED;
}
else if (!TransactionIdIsValid(xid))
{
/* Raw xmax is InvalidTransactionId XID */
Assert((tuple->t_infomask & HEAP_XMAX_IS_MULTI) == 0);
xmax_already_frozen = true;
}
else
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
errmsg_internal("found raw xmax %u (infomask 0x%04x) not invalid and not multi",
xid, tuple->t_infomask)));
if (freeze_xmin)
{
Assert(!xmin_already_frozen);
frz->t_infomask |= HEAP_XMIN_FROZEN;
}
if (replace_xvac)
{
/*
* If a MOVED_OFF tuple is not dead, the xvac transaction must have
* failed; whereas a non-dead MOVED_IN tuple must mean the xvac
* transaction succeeded.
*/
Assert(pagefrz->freeze_required);
if (tuple->t_infomask & HEAP_MOVED_OFF)
frz->frzflags |= XLH_INVALID_XVAC;
else
frz->frzflags |= XLH_FREEZE_XVAC;
}
if (replace_xmax)
{
Assert(!xmax_already_frozen && !freeze_xmax);
Assert(pagefrz->freeze_required);
/* Already set replace_xmax flags in freeze plan earlier */
}
if (freeze_xmax)
{
Assert(!xmax_already_frozen && !replace_xmax);
frz->xmax = InvalidTransactionId;
/*
* The tuple might be marked either XMAX_INVALID or XMAX_COMMITTED +
* LOCKED. Normalize to INVALID just to be sure no one gets confused.
* Also get rid of the HEAP_KEYS_UPDATED bit.
*/
frz->t_infomask &= ~HEAP_XMAX_BITS;
frz->t_infomask |= HEAP_XMAX_INVALID;
frz->t_infomask2 &= ~HEAP_HOT_UPDATED;
frz->t_infomask2 &= ~HEAP_KEYS_UPDATED;
}
/*
* Determine if this tuple is already totally frozen, or will become
* totally frozen (provided caller executes freeze plans for the page)
*/
*totally_frozen = ((freeze_xmin || xmin_already_frozen) &&
(freeze_xmax || xmax_already_frozen));
if (!pagefrz->freeze_required && !(xmin_already_frozen &&
xmax_already_frozen))
{
/*
* So far no previous tuple from the page made freezing mandatory.
* Does this tuple force caller to freeze the entire page?
*/
pagefrz->freeze_required =
heap_tuple_should_freeze(tuple, cutoffs,
&pagefrz->NoFreezePageRelfrozenXid,
&pagefrz->NoFreezePageRelminMxid);
}
/* Tell caller if this tuple has a usable freeze plan set in *frz */
return freeze_xmin || replace_xvac || replace_xmax || freeze_xmax;
}
/*
* Perform xmin/xmax XID status sanity checks before actually executing freeze
* plans.
*
* heap_prepare_freeze_tuple doesn't perform these checks directly because
* pg_xact lookups are relatively expensive. They shouldn't be repeated by
* successive VACUUMs that each decide against freezing the same page.
*/
void
heap_pre_freeze_checks(Buffer buffer,
HeapTupleFreeze *tuples, int ntuples)
{
Page page = BufferGetPage(buffer);
for (int i = 0; i < ntuples; i++)
{
HeapTupleFreeze *frz = tuples + i;
ItemId itemid = PageGetItemId(page, frz->offset);
HeapTupleHeader htup;
/* Deliberately avoid relying on tuple hint bits here */
if (frz->checkflags & HEAP_FREEZE_CHECK_XMIN_COMMITTED)
{
TransactionId xmin = HeapTupleHeaderGetRawXmin(htup);
Assert(!HeapTupleHeaderXminFrozen(htup));
if (unlikely(!TransactionIdDidCommit(xmin)))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
errmsg_internal("uncommitted xmin %u needs to be frozen",
xmin)));
}
/*
* TransactionIdDidAbort won't work reliably in the presence of XIDs
* left behind by transactions that were in progress during a crash,
* so we can only check that xmax didn't commit
*/
if (frz->checkflags & HEAP_FREEZE_CHECK_XMAX_ABORTED)
{
TransactionId xmax = HeapTupleHeaderGetRawXmax(htup);
/*
* Helper which executes freezing of one or more heap tuples on a page on
* behalf of caller. Caller passes an array of tuple plans from
* heap_prepare_freeze_tuple. Caller must set 'offset' in each plan for us.
* Must be called in a critical section that also marks the buffer dirty and,
* if needed, emits WAL.
*/
void
heap_freeze_prepared_tuples(Buffer buffer, HeapTupleFreeze *tuples, int ntuples)
{
Page page = BufferGetPage(buffer);
for (int i = 0; i < ntuples; i++)
{
HeapTupleFreeze *frz = tuples + i;
ItemId itemid = PageGetItemId(page, frz->offset);
HeapTupleHeader htup;
/*
* Note that because this is not a WAL-logged operation, we don't need to
* fill in the offset in the freeze record.
*/
if (do_freeze)
heap_execute_freeze_tuple(tuple, &frz);
return do_freeze;
}
/*
* For a given MultiXactId, return the hint bits that should be set in the
* tuple's infomask.
*
* Normally this should be called for a multixact that was just created, and
* so is on our local cache, so the GetMembers call is fast.
*/
static void
GetMultiXactIdHintBits(MultiXactId multi, uint16 *new_infomask,
uint16 *new_infomask2)
{
int nmembers;
MultiXactMember *members;
int i;
uint16 bits = HEAP_XMAX_IS_MULTI;
uint16 bits2 = 0;
bool has_update = false;
LockTupleMode strongest = LockTupleKeyShare;
/*
* We only use this in multis we just created, so they cannot be values
* pre-pg_upgrade.
*/
nmembers = GetMultiXactIdMembers(multi, &members, false, false);
for (i = 0; i < nmembers; i++)
{
LockTupleMode mode;
/*
* Remember the strongest lock mode held by any member of the
* multixact.
*/
mode = TUPLOCK_from_mxstatus(members[i].status);
if (mode > strongest)
strongest = mode;
/* See what other bits we need */
switch (members[i].status)
{
case MultiXactStatusForKeyShare:
case MultiXactStatusForShare:
case MultiXactStatusForNoKeyUpdate:
break;
case MultiXactStatusForUpdate:
bits2 |= HEAP_KEYS_UPDATED;
break;
case MultiXactStatusNoKeyUpdate:
has_update = true;
break;
if (strongest == LockTupleExclusive ||
strongest == LockTupleNoKeyExclusive)
bits |= HEAP_XMAX_EXCL_LOCK;
else if (strongest == LockTupleShare)
bits |= HEAP_XMAX_SHR_LOCK;
else if (strongest == LockTupleKeyShare)
bits |= HEAP_XMAX_KEYSHR_LOCK;
if (!has_update)
bits |= HEAP_XMAX_LOCK_ONLY;
if (nmembers > 0)
pfree(members);
*new_infomask = bits;
*new_infomask2 = bits2;
}
/*
* MultiXactIdGetUpdateXid
*
* Given a multixact Xmax and corresponding infomask, which does not have the
* HEAP_XMAX_LOCK_ONLY bit set, obtain and return the Xid of the updating
* transaction.
*
* Caller is expected to check the status of the updating transaction, if
* necessary.
*/
static TransactionId
MultiXactIdGetUpdateXid(TransactionId xmax, uint16 t_infomask)
{
TransactionId update_xact = InvalidTransactionId;
MultiXactMember *members;
int nmembers;
/*
* Since we know the LOCK_ONLY bit is not set, this cannot be a multi from
* pre-pg_upgrade.
*/
nmembers = GetMultiXactIdMembers(xmax, &members, false, false);
if (nmembers > 0)
{
int i;
for (i = 0; i < nmembers; i++)
{
/* Ignore lockers */
if (!ISUPDATE_from_mxstatus(members[i].status))
continue;
/* there can be at most one updater */
Assert(update_xact == InvalidTransactionId);
update_xact = members[i].xid;
#ifndef USE_ASSERT_CHECKING
/*
* in an assert-enabled build, walk the whole array to ensure
* there's no other updater.
*/
break;
#endif
}
pfree(members);
}
return update_xact;
}
/*
* HeapTupleGetUpdateXid
* As above, but use a HeapTupleHeader
*
* See also HeapTupleHeaderGetUpdateXid, which can be used without previously
* checking the hint bits.
*/
TransactionId
HeapTupleGetUpdateXid(const HeapTupleHeaderData *tup)
{
return MultiXactIdGetUpdateXid(HeapTupleHeaderGetRawXmax(tup),
tup->t_infomask);
}
/*
* Does the given multixact conflict with the current transaction grabbing a
* tuple lock of the given strength?
*
* The passed infomask pairs up with the given multixact in the tuple header.
*
* If current_is_member is not NULL, it is set to 'true' if the current
* transaction is a member of the given multixact.
*/
static bool
DoesMultiXactIdConflict(MultiXactId multi, uint16 infomask,
LockTupleMode lockmode, bool *current_is_member)
{
int nmembers;
MultiXactMember *members;
bool result = false;
LOCKMODE wanted = tupleLockExtraInfo[lockmode].hwlock;
if (HEAP_LOCKED_UPGRADED(infomask))
return false;
nmembers = GetMultiXactIdMembers(multi, &members, false,
HEAP_XMAX_IS_LOCKED_ONLY(infomask));
if (nmembers >= 0)
{
int i;
for (i = 0; i < nmembers; i++)
{
TransactionId memxid;
LOCKMODE memlockmode;
if (result && (current_is_member == NULL || *current_is_member))
break;
/* ignore members from current xact (but track their presence) */
memxid = members[i].xid;
if (TransactionIdIsCurrentTransactionId(memxid))
{
if (current_is_member != NULL)
*current_is_member = true;
continue;
}
else if (result)
continue;
/* ignore members that don't conflict with the lock we want */
if (!DoLockModesConflict(memlockmode, wanted))
continue;
if (ISUPDATE_from_mxstatus(members[i].status))
{
/* ignore aborted updaters */
if (TransactionIdDidAbort(memxid))
continue;
}
else
{
/* ignore lockers-only that are no longer in progress */
if (!TransactionIdIsInProgress(memxid))
continue;
}
/*
* Whatever remains are either live lockers that conflict with our
* wanted lock, and updaters that are not aborted. Those conflict
* with what we want. Set up to return true, but keep going to
* look for the current transaction among the multixact members,
* if needed.
*/
result = true;
}
pfree(members);
}
return result;
}
/*
* 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 '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. 'logLockFailure' indicates whether
* to log details when a lock acquisition fails with 'nowait' enabled.
*
* 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
* XactLockTableWait. By the time this returns, it is certain that all
* transactions *of other backends* that were members of the MultiXactId
* that conflict with the requested status are dead (and no new ones can have
* been added, since it is not legal to add members to an existing
* MultiXactId).
*
* But by the time we finish sleeping, someone else may have changed the Xmax
* of the containing tuple, so the caller needs to iterate on us somehow.
*
* Note that in case we return false, the number of remaining members is
* not to be trusted.
*/
static bool
Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status,
uint16 infomask, bool nowait,
Relation rel, ItemPointer ctid, XLTW_Oper oper,
int *remaining, bool logLockFailure)
{
bool result = true;
MultiXactMember *members;
int nmembers;
int remain = 0;
/* for pre-pg_upgrade tuples, no need to sleep at all */
nmembers = HEAP_LOCKED_UPGRADED(infomask) ? -1 :
GetMultiXactIdMembers(multi, &members, false,
HEAP_XMAX_IS_LOCKED_ONLY(infomask));
if (nmembers >= 0)
{
int i;
for (i = 0; i < nmembers; i++)
{
TransactionId memxid = members[i].xid;
MultiXactStatus memstatus = members[i].status;
if (TransactionIdIsCurrentTransactionId(memxid))
{
remain++;
continue;
}
if (!DoLockModesConflict(LOCKMODE_from_mxstatus(memstatus),
LOCKMODE_from_mxstatus(status)))
{
if (remaining && TransactionIdIsInProgress(memxid))
remain++;
continue;
}
/*
* 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)
{
result = ConditionalXactLockTableWait(memxid, logLockFailure);
if (!result)
break;
}
else
XactLockTableWait(memxid, rel, ctid, oper);
}
pfree(members);
}
if (remaining)
*remaining = remain;
return result;
}
/*
* MultiXactIdWait
* Sleep on a MultiXactId.
*
* By the time we finish sleeping, someone else may have changed the Xmax
* of the containing tuple, so the caller needs to iterate on us somehow.
*
* 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, XLTW_Oper oper,
int *remaining)
{
(void) Do_MultiXactIdWait(multi, status, infomask, false,
rel, ctid, oper, remaining, false);
}
/*
* ConditionalMultiXactIdWait
* As above, but only lock if we can get the lock without blocking.
*
* By the time we finish sleeping, someone else may have changed the Xmax
* of the containing tuple, so the caller needs to iterate on us somehow.
*
* If the multixact is now all gone, return true. Returns false if some
* transactions might still be running.
*
* 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 bool
ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status,
uint16 infomask, Relation rel, int *remaining,
bool logLockFailure)
{
return Do_MultiXactIdWait(multi, status, infomask, true,
rel, NULL, XLTW_None, remaining, logLockFailure);
}
/*
* heap_tuple_needs_eventual_freeze
*
* Check to see whether any of the XID fields of a tuple (xmin, xmax, xvac)
* will eventually require freezing (if tuple isn't removed by pruning first).
*/
bool
heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple)
{
TransactionId xid;
/*
* If xmin is a normal transactionID, this tuple is definitely not
* frozen.
*/
xid = HeapTupleHeaderGetXmin(tuple);
if (TransactionIdIsNormal(xid))
return true;
/*
* If xmax is a valid xact or multixact, this tuple is also not frozen.
*/
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
MultiXactId multi;
multi = HeapTupleHeaderGetRawXmax(tuple);
if (MultiXactIdIsValid(multi))
return true;
}
else
{
xid = HeapTupleHeaderGetRawXmax(tuple);
if (TransactionIdIsNormal(xid))
return true;
}
if (tuple->t_infomask & HEAP_MOVED)
{
xid = HeapTupleHeaderGetXvac(tuple);
if (TransactionIdIsNormal(xid))
return true;
}
return false;
}
/*
* heap_tuple_should_freeze
*
* Return value indicates if heap_prepare_freeze_tuple sibling function would
* (or should) force freezing of the heap page that contains caller's tuple.
* Tuple header XIDs/MXIDs < FreezeLimit/MultiXactCutoff trigger freezing.
* This includes (xmin, xmax, xvac) fields, as well as MultiXact member XIDs.
*
* The *NoFreezePageRelfrozenXid and *NoFreezePageRelminMxid input/output
* arguments help VACUUM track the oldest extant XID/MXID remaining in rel.
* Our working assumption is that caller won't decide to freeze this tuple.
* It's up to caller to only ratchet back its own top-level trackers after the
* point that it fully commits to not freezing the tuple/page in question.
*/
bool
heap_tuple_should_freeze(HeapTupleHeader tuple,
const struct VacuumCutoffs *cutoffs,
TransactionId *NoFreezePageRelfrozenXid,
MultiXactId *NoFreezePageRelminMxid)
{
TransactionId xid;
MultiXactId multi;
bool freeze = false;
/* First deal with xmin */
xid = HeapTupleHeaderGetXmin(tuple);
if (TransactionIdIsNormal(xid))
{
Assert(TransactionIdPrecedesOrEquals(cutoffs->relfrozenxid, xid));
if (TransactionIdPrecedes(xid, *NoFreezePageRelfrozenXid))
*NoFreezePageRelfrozenXid = xid;
if (TransactionIdPrecedes(xid, cutoffs->FreezeLimit))
freeze = true;
}
/* Now deal with xmax */
xid = InvalidTransactionId;
multi = InvalidMultiXactId;
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
multi = HeapTupleHeaderGetRawXmax(tuple);
else
xid = HeapTupleHeaderGetRawXmax(tuple);
if (TransactionIdIsNormal(xid))
{
Assert(TransactionIdPrecedesOrEquals(cutoffs->relfrozenxid, xid));
/* xmax is a non-permanent XID */
if (TransactionIdPrecedes(xid, *NoFreezePageRelfrozenXid))
*NoFreezePageRelfrozenXid = xid;
if (TransactionIdPrecedes(xid, cutoffs->FreezeLimit))
freeze = true;
}
else if (!MultiXactIdIsValid(multi))
{
/* xmax is a permanent XID or invalid MultiXactId/XID */
}
else if (HEAP_LOCKED_UPGRADED(tuple->t_infomask))
{
/* xmax is a pg_upgrade'd MultiXact, which can't have updater XID */
if (MultiXactIdPrecedes(multi, *NoFreezePageRelminMxid))
*NoFreezePageRelminMxid = multi;
/* heap_prepare_freeze_tuple always freezes pg_upgrade'd xmax */
freeze = true;
}
else
{
/* xmax is a MultiXactId that may have an updater XID */
MultiXactMember *members;
int nmembers;
Assert(MultiXactIdPrecedesOrEquals(cutoffs->relminmxid, multi));
if (MultiXactIdPrecedes(multi, *NoFreezePageRelminMxid))
*NoFreezePageRelminMxid = multi;
if (MultiXactIdPrecedes(multi, cutoffs->MultiXactCutoff))
freeze = true;
/* need to check whether any member of the mxact is old */
nmembers = GetMultiXactIdMembers(multi, &members, false,
HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
for (int i = 0; i < nmembers; i++)
{
xid = members[i].xid;
Assert(TransactionIdPrecedesOrEquals(cutoffs->relfrozenxid, xid));
if (TransactionIdPrecedes(xid, *NoFreezePageRelfrozenXid))
*NoFreezePageRelfrozenXid = xid;
if (TransactionIdPrecedes(xid, cutoffs->FreezeLimit))
freeze = true;
}
if (nmembers > 0)
pfree(members);
}
if (tuple->t_infomask & HEAP_MOVED)
{
xid = HeapTupleHeaderGetXvac(tuple);
if (TransactionIdIsNormal(xid))
{
Assert(TransactionIdPrecedesOrEquals(cutoffs->relfrozenxid, xid));
if (TransactionIdPrecedes(xid, *NoFreezePageRelfrozenXid))
*NoFreezePageRelfrozenXid = xid;
/* heap_prepare_freeze_tuple forces xvac freezing */
freeze = true;
}
}
return freeze;
}
/*
* Maintain snapshotConflictHorizon for caller by ratcheting forward its value
* using any committed XIDs contained in 'tuple', an obsolescent heap tuple
* that caller is in the process of physically removing, e.g. via HOT pruning
* or index deletion.
*
* Caller must initialize its value to InvalidTransactionId, which is
* generally interpreted as "definitely no need for a recovery conflict".
* Final value must reflect all heap tuples that caller will physically remove
* (or remove TID references to) via its ongoing pruning/deletion operation.
* ResolveRecoveryConflictWithSnapshot() is passed the final value (taken from
* caller's WAL record) by REDO routine when it replays caller's operation.
*/
void
HeapTupleHeaderAdvanceConflictHorizon(HeapTupleHeader tuple,
TransactionId *snapshotConflictHorizon)
{
TransactionId xmin = HeapTupleHeaderGetXmin(tuple);
TransactionId xmax = HeapTupleHeaderGetUpdateXid(tuple);
TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
if (tuple->t_infomask & HEAP_MOVED)
{
if (TransactionIdPrecedes(*snapshotConflictHorizon, xvac))
*snapshotConflictHorizon = xvac;
}
/*
* Ignore tuples inserted by an aborted transaction or if the tuple was
* updated/deleted by the inserting transaction.
*
* Look for a committed hint bit, or if no xmin bit is set, check clog.
*/
if (HeapTupleHeaderXminCommitted(tuple) ||
(!HeapTupleHeaderXminInvalid(tuple) && TransactionIdDidCommit(xmin)))
{
if (xmax != xmin &&
TransactionIdFollows(xmax, *snapshotConflictHorizon))
*snapshotConflictHorizon = xmax;
}
}
#ifdef USE_PREFETCH
/*
* Helper function for heap_index_delete_tuples. Issues prefetch requests for
* prefetch_count buffers. The prefetch_state keeps track of all the buffers
* we can prefetch, and which have already been prefetched; each call to this
* function picks up where the previous call left off.
*
* Note: we expect the deltids array to be sorted in an order that groups TIDs
* by heap block, with all TIDs for each block appearing together in exactly
* one group.
*/
static void
index_delete_prefetch_buffer(Relation rel,
IndexDeletePrefetchState *prefetch_state,
int prefetch_count)
{
BlockNumber cur_hblkno = prefetch_state->cur_hblkno;
int count = 0;
int i;
int ndeltids = prefetch_state->ndeltids;
TM_IndexDelete *deltids = prefetch_state->deltids;
for (i = prefetch_state->next_item;
i < ndeltids && count < prefetch_count;
i++)
{
ItemPointer htid = &deltids[i].tid;
/*
* Save the prefetch position so that next time we can continue from that
* position.
*/
prefetch_state->next_item = i;
prefetch_state->cur_hblkno = cur_hblkno;
}
#endif
/*
* Helper function for heap_index_delete_tuples. Checks for index corruption
* involving an invalid TID in index AM caller's index page.
*
* This is an ideal place for these checks. The index AM must hold a buffer
* lock on the index page containing the TIDs we examine here, so we don't
* have to worry about concurrent VACUUMs at all. We can be sure that the
* index is corrupt when htid points directly to an LP_UNUSED item or
* heap-only tuple, which is not the case during standard index scans.
*/
static inline void
index_delete_check_htid(TM_IndexDeleteOp *delstate,
Page page, OffsetNumber maxoff,
ItemPointer htid, TM_IndexStatus *istatus)
{
OffsetNumber indexpagehoffnum = ItemPointerGetOffsetNumber(htid);
ItemId iid;
Assert(OffsetNumberIsValid(istatus->idxoffnum));
if (unlikely(indexpagehoffnum > maxoff))
ereport(ERROR,
(errcode(ERRCODE_INDEX_CORRUPTED),
errmsg_internal("heap tid from index tuple (%u,%u) points past end of heap page line pointer array at offset %u of block %u in index \"%s\"",
ItemPointerGetBlockNumber(htid),
indexpagehoffnum,
istatus->idxoffnum, delstate->iblknum,
RelationGetRelationName(delstate->irel))));
iid = PageGetItemId(page, indexpagehoffnum);
if (unlikely(!ItemIdIsUsed(iid)))
ereport(ERROR,
(errcode(ERRCODE_INDEX_CORRUPTED),
errmsg_internal("heap tid from index tuple (%u,%u) points to unused heap page item at offset %u of block %u in index \"%s\"",
ItemPointerGetBlockNumber(htid),
indexpagehoffnum,
istatus->idxoffnum, delstate->iblknum,
RelationGetRelationName(delstate->irel))));
if (ItemIdHasStorage(iid))
{
HeapTupleHeader htup;
if (unlikely(HeapTupleHeaderIsHeapOnly(htup)))
ereport(ERROR,
(errcode(ERRCODE_INDEX_CORRUPTED),
errmsg_internal("heap tid from index tuple (%u,%u) points to heap-only tuple at offset %u of block %u in index \"%s\"",
ItemPointerGetBlockNumber(htid),
indexpagehoffnum,
istatus->idxoffnum, delstate->iblknum,
RelationGetRelationName(delstate->irel))));
}
}
/*
* heapam implementation of tableam's index_delete_tuples interface.
*
* This helper function is called by index AMs during index tuple deletion.
* See tableam header comments for an explanation of the interface implemented
* here and a general theory of operation. Note that each call here is either
* a simple index deletion call, or a bottom-up index deletion call.
*
* It's possible for this to generate a fair amount of I/O, since we may be
* deleting hundreds of tuples from a single index block. To amortize that
* cost to some degree, this uses prefetching and combines repeat accesses to
* the same heap block.
*/
TransactionId
heap_index_delete_tuples(Relation rel, TM_IndexDeleteOp *delstate)
{
/* Initial assumption is that earlier pruning took care of conflict */
TransactionId snapshotConflictHorizon = InvalidTransactionId;
BlockNumber blkno = InvalidBlockNumber;
Buffer buf = InvalidBuffer;
Page page = NULL;
OffsetNumber maxoff = InvalidOffsetNumber;
TransactionId priorXmax;
#ifdef USE_PREFETCH
IndexDeletePrefetchState prefetch_state;
int prefetch_distance;
#endif
SnapshotData SnapshotNonVacuumable;
int finalndeltids = 0,
nblocksaccessed = 0;
/* State that's only used in bottom-up index deletion case */
int nblocksfavorable = 0;
int curtargetfreespace = delstate->bottomupfreespace,
lastfreespace = 0,
actualfreespace = 0;
bool bottomup_final_block = false;
/* Sort caller's deltids array by TID for further processing */
index_delete_sort(delstate);
/*
* Bottom-up case: resort deltids array in an order attuned to where the
* greatest number of promising TIDs are to be found, and determine how
* many blocks from the start of sorted array should be considered
* favorable. This will also shrink the deltids array in order to
* eliminate completely unfavorable blocks up front.
*/
if (delstate->bottomup)
nblocksfavorable = bottomup_sort_and_shrink(delstate);
/*
* Determine the prefetch distance that we will attempt to maintain.
*
* Since the caller holds a buffer lock somewhere in rel, we'd better make
* sure that isn't a catalog relation before we call code that does
* syscache lookups, to avoid risk of deadlock.
*/
if (IsCatalogRelation(rel))
prefetch_distance = maintenance_io_concurrency;
else
prefetch_distance =
get_tablespace_maintenance_io_concurrency(rel->rd_rel->reltablespace);
/* Cap initial prefetch distance for bottom-up deletion caller */
if (delstate->bottomup)
{
Assert(nblocksfavorable >= 1);
Assert(nblocksfavorable <= BOTTOMUP_MAX_NBLOCKS);
prefetch_distance = Min(prefetch_distance, nblocksfavorable);
}
/* Iterate over deltids, determine which to delete, check their horizon */
Assert(delstate->ndeltids > 0);
for (int i = 0; i < delstate->ndeltids; i++)
{
TM_IndexDelete *ideltid = &delstate->deltids[i];
TM_IndexStatus *istatus = delstate->status + ideltid->id;
ItemPointer htid = &ideltid->tid;
OffsetNumber offnum;
/*
* Read buffer, and perform required extra steps each time a new block
* is encountered. Avoid refetching if it's the same block as the one
* from the last htid.
*/
if (blkno == InvalidBlockNumber ||
ItemPointerGetBlockNumber(htid) != blkno)
{
/*
* Consider giving up early for bottom-up index deletion caller
* first. (Only prefetch next-next block afterwards, when it
* becomes clear that we're at least going to access the next
* block in line.)
*
* Sometimes the first block frees so much space for bottom-up
* caller that the deletion process can end without accessing any
* more blocks. It is usually necessary to access 2 or 3 blocks
* per bottom-up deletion operation, though.
*/
if (delstate->bottomup)
{
/*
* We often allow caller to delete a few additional items
* whose entries we reached after the point that space target
* from caller was satisfied. The cost of accessing the page
* was already paid at that point, so it made sense to finish
* it off. When that happened, we finalize everything here
* (by finishing off the whole bottom-up deletion operation
* without needlessly paying the cost of accessing any more
* blocks).
*/
if (bottomup_final_block)
break;
/*
* Give up when we didn't enable our caller to free any
* additional space as a result of processing the page that we
* just finished up with. This rule is the main way in which
* we keep the cost of bottom-up deletion under control.
*/
if (nblocksaccessed >= 1 && actualfreespace == lastfreespace)
break;
lastfreespace = actualfreespace; /* for next time */
/*
* Deletion operation (which is bottom-up) will definitely
* access the next block in line. Prepare for that now.
*
* Decay target free space so that we don't hang on for too
* long with a marginal case. (Space target is only truly
* helpful when it allows us to recognize that we don't need
* to access more than 1 or 2 blocks to satisfy caller due to
* agreeable workload characteristics.)
*
* We are a bit more patient when we encounter contiguous
* blocks, though: these are treated as favorable blocks. The
* decay process is only applied when the next block in line
* is not a favorable/contiguous block. This is not an
* exception to the general rule; we still insist on finding
* at least one deletable item per block accessed. See
* bottomup_nblocksfavorable() for full details of the theory
* behind favorable blocks and heap block locality in general.
*
* Note: The first block in line is always treated as a
* favorable block, so the earliest possible point that the
* decay can be applied is just before we access the second
* block in line. The Assert() verifies this for us.
*/
Assert(nblocksaccessed > 0 || nblocksfavorable > 0);
if (nblocksfavorable > 0)
nblocksfavorable--;
else
curtargetfreespace /= 2;
}
/* release old buffer */
if (BufferIsValid(buf))
UnlockReleaseBuffer(buf);
/*
* To maintain the prefetch distance, prefetch one more page for
* each page we read.
*/
index_delete_prefetch_buffer(rel, &prefetch_state, 1);
#endif
/*
* In passing, detect index corruption involving an index page with a
* TID that points to a location in the heap that couldn't possibly be
* correct. We only do this with actual TIDs from caller's index page
* (not items reached by traversing through a HOT chain).
*/
index_delete_check_htid(delstate, page, maxoff, htid, istatus);
/* Are any tuples from this HOT chain non-vacuumable? */
if (heap_hot_search_buffer(&tmp, rel, buf, &SnapshotNonVacuumable,
&heapTuple, NULL, true))
continue; /* can't delete entry */
/* Caller will delete, since whole HOT chain is vacuumable */
istatus->knowndeletable = true;
/* Maintain index free space info for bottom-up deletion case */
if (delstate->bottomup)
{
Assert(istatus->freespace > 0);
actualfreespace += istatus->freespace;
if (actualfreespace >= curtargetfreespace)
bottomup_final_block = true;
}
}
/*
* Maintain snapshotConflictHorizon value for deletion operation as a
* whole by advancing current value using heap tuple headers. This is
* loosely based on the logic for pruning a HOT chain.
*/
offnum = ItemPointerGetOffsetNumber(htid);
priorXmax = InvalidTransactionId; /* cannot check first XMIN */
for (;;)
{
ItemId lp;
HeapTupleHeader htup;
/*
* We'll often encounter LP_DEAD line pointers (especially with an
* entry marked knowndeletable by our caller up front). No heap
* tuple headers get examined for an htid that leads us to an
* LP_DEAD item. This is okay because the earlier pruning
* operation that made the line pointer LP_DEAD in the first place
* must have considered the original tuple header as part of
* generating its own snapshotConflictHorizon value.
*
* Relying on XLOG_HEAP2_PRUNE_VACUUM_SCAN records like this is
* the same strategy that index vacuuming uses in all cases. Index
* VACUUM WAL records don't even have a snapshotConflictHorizon
* field of their own for this reason.
*/
if (!ItemIdIsNormal(lp))
break;
htup = (HeapTupleHeader) PageGetItem(page, lp);
/*
* Check the tuple XMIN against prior XMAX, if any
*/
if (TransactionIdIsValid(priorXmax) &&
!TransactionIdEquals(HeapTupleHeaderGetXmin(htup), priorXmax))
break;
/*
* If the tuple is not HOT-updated, then we are at the end of this
* HOT-chain. No need to visit later tuples from the same update
* chain (they get their own index entries) -- just move on to
* next htid from index AM caller.
*/
if (!HeapTupleHeaderIsHotUpdated(htup))
break;
/* Advance to next HOT chain member */
Assert(ItemPointerGetBlockNumber(&htup->t_ctid) == blkno);
offnum = ItemPointerGetOffsetNumber(&htup->t_ctid);
priorXmax = HeapTupleHeaderGetUpdateXid(htup);
}
/* Enable further/final shrinking of deltids for caller */
finalndeltids = i + 1;
}
UnlockReleaseBuffer(buf);
/*
* Shrink deltids array to exclude non-deletable entries at the end. This
* is not just a minor optimization. Final deltids array size might be
* zero for a bottom-up caller. Index AM is explicitly allowed to rely on
* ndeltids being zero in all cases with zero total deletable entries.
*/
Assert(finalndeltids > 0 || delstate->bottomup);
delstate->ndeltids = finalndeltids;
return snapshotConflictHorizon;
}
/*
* Specialized inlineable comparison function for index_delete_sort()
*/
static inline int
index_delete_sort_cmp(TM_IndexDelete *deltid1, TM_IndexDelete *deltid2)
{
ItemPointer tid1 = &deltid1->tid;
ItemPointer tid2 = &deltid2->tid;
/*
* Sort deltids array from delstate by TID. This prepares it for further
* processing by heap_index_delete_tuples().
*
* This operation becomes a noticeable consumer of CPU cycles with some
* workloads, so we go to the trouble of specialization/micro optimization.
* We use shellsort for this because it's easy to specialize, compiles to
* relatively few instructions, and is adaptive to presorted inputs/subsets
* (which are typical here).
*/
static void
index_delete_sort(TM_IndexDeleteOp *delstate)
{
TM_IndexDelete *deltids = delstate->deltids;
int ndeltids = delstate->ndeltids;
/*
* Shellsort gap sequence (taken from Sedgewick-Incerpi paper).
*
* This implementation is fast with array sizes up to ~4500. This covers
* all supported BLCKSZ values.
*/
const int gaps[9] = {1968, 861, 336, 112, 48, 21, 7, 3, 1};
/* Think carefully before changing anything here -- keep swaps cheap */
StaticAssertDecl(sizeof(TM_IndexDelete) <= 8, "element size exceeds 8 bytes");
for (int g = 0; g < lengthof(gaps); g++)
{
for (int hi = gaps[g], i = hi; i < ndeltids; i++)
{
TM_IndexDelete d = deltids[i];
int j = i;
/*
* Returns how many blocks should be considered favorable/contiguous for a
* bottom-up index deletion pass. This is a number of heap blocks that starts
* from and includes the first block in line.
*
* There is always at least one favorable block during bottom-up index
* deletion. In the worst case (i.e. with totally random heap blocks) the
* first block in line (the only favorable block) can be thought of as a
* degenerate array of contiguous blocks that consists of a single block.
* heap_index_delete_tuples() will expect this.
*
* Caller passes blockgroups, a description of the final order that deltids
* will be sorted in for heap_index_delete_tuples() bottom-up index deletion
* processing. Note that deltids need not actually be sorted just yet (caller
* only passes deltids to us so that we can interpret blockgroups).
*
* You might guess that the existence of contiguous blocks cannot matter much,
* since in general the main factor that determines which blocks we visit is
* the number of promising TIDs, which is a fixed hint from the index AM.
* We're not really targeting the general case, though -- the actual goal is
* to adapt our behavior to a wide variety of naturally occurring conditions.
* The effects of most of the heuristics we apply are only noticeable in the
* aggregate, over time and across many _related_ bottom-up index deletion
* passes.
*
* Deeming certain blocks favorable allows heapam to recognize and adapt to
* workloads where heap blocks visited during bottom-up index deletion can be
* accessed contiguously, in the sense that each newly visited block is the
* neighbor of the block that bottom-up deletion just finished processing (or
* close enough to it). It will likely be cheaper to access more favorable
* blocks sooner rather than later (e.g. in this pass, not across a series of
* related bottom-up passes). Either way it is probably only a matter of time
* (or a matter of further correlated version churn) before all blocks that
* appear together as a single large batch of favorable blocks get accessed by
* _some_ bottom-up pass. Large batches of favorable blocks tend to either
* appear almost constantly or not even once (it all depends on per-index
* workload characteristics).
*
* Note that the blockgroups sort order applies a power-of-two bucketing
* scheme that creates opportunities for contiguous groups of blocks to get
* batched together, at least with workloads that are naturally amenable to
* being driven by heap block locality. This doesn't just enhance the spatial
* locality of bottom-up heap block processing in the obvious way. It also
* enables temporal locality of access, since sorting by heap block number
* naturally tends to make the bottom-up processing order deterministic.
*
* Consider the following example to get a sense of how temporal locality
* might matter: There is a heap relation with several indexes, each of which
* is low to medium cardinality. It is subject to constant non-HOT updates.
* The updates are skewed (in one part of the primary key, perhaps). None of
* the indexes are logically modified by the UPDATE statements (if they were
* then bottom-up index deletion would not be triggered in the first place).
* Naturally, each new round of index tuples (for each heap tuple that gets a
* heap_update() call) will have the same heap TID in each and every index.
* Since these indexes are low cardinality and never get logically modified,
* heapam processing during bottom-up deletion passes will access heap blocks
* in approximately sequential order. Temporal locality of access occurs due
* to bottom-up deletion passes behaving very similarly across each of the
* indexes at any given moment. This keeps the number of buffer misses needed
* to visit heap blocks to a minimum.
*/
static int
bottomup_nblocksfavorable(IndexDeleteCounts *blockgroups, int nblockgroups,
TM_IndexDelete *deltids)
{
int64 lastblock = -1;
int nblocksfavorable = 0;
/*
* We tolerate heap blocks that will be accessed only slightly out of
* physical order. Small blips occur when a pair of almost-contiguous
* blocks happen to fall into different buckets (perhaps due only to a
* small difference in npromisingtids that the bucketing scheme didn't
* quite manage to ignore). We effectively ignore these blips by applying
* a small tolerance. The precise tolerance we use is a little arbitrary,
* but it works well enough in practice.
*/
for (int b = 0; b < nblockgroups; b++)
{
IndexDeleteCounts *group = blockgroups + b;
TM_IndexDelete *firstdtid = deltids + group->ifirsttid;
BlockNumber block = ItemPointerGetBlockNumber(&firstdtid->tid);
/*
* Most significant field is npromisingtids (which we invert the order of
* so as to sort in desc order).
*
* Caller should have already normalized npromisingtids fields into
* power-of-two values (buckets).
*/
if (group1->npromisingtids > group2->npromisingtids)
return -1;
if (group1->npromisingtids < group2->npromisingtids)
return 1;
/*
* Tiebreak: desc ntids sort order.
*
* We cannot expect power-of-two values for ntids fields. We should
* behave as if they were already rounded up for us instead.
*/
if (group1->ntids != group2->ntids)
{
uint32 ntids1 = pg_nextpower2_32((uint32) group1->ntids);
uint32 ntids2 = pg_nextpower2_32((uint32) group2->ntids);
if (ntids1 > ntids2)
return -1;
if (ntids1 < ntids2)
return 1;
}
/*
* Tiebreak: asc offset-into-deltids-for-block (offset to first TID for
* block in deltids array) order.
*
* This is equivalent to sorting in ascending heap block number order
* (among otherwise equal subsets of the array). This approach allows us
* to avoid accessing the out-of-line TID. (We rely on the assumption
* that the deltids array was sorted in ascending heap TID order when
* these offsets to the first TID from each heap block group were formed.)
*/
if (group1->ifirsttid > group2->ifirsttid)
return 1;
if (group1->ifirsttid < group2->ifirsttid)
return -1;
pg_unreachable();
return 0;
}
/*
* heap_index_delete_tuples() helper function for bottom-up deletion callers.
*
* Sorts deltids array in the order needed for useful processing by bottom-up
* deletion. The array should already be sorted in TID order when we're
* called. The sort process groups heap TIDs from deltids into heap block
* groupings. Earlier/more-promising groups/blocks are usually those that are
* known to have the most "promising" TIDs.
*
* Sets new size of deltids array (ndeltids) in state. deltids will only have
* TIDs from the BOTTOMUP_MAX_NBLOCKS most promising heap blocks when we
* return. This often means that deltids will be shrunk to a small fraction
* of its original size (we eliminate many heap blocks from consideration for
* caller up front).
*
* Returns the number of "favorable" blocks. See bottomup_nblocksfavorable()
* for a definition and full details.
*/
static int
bottomup_sort_and_shrink(TM_IndexDeleteOp *delstate)
{
IndexDeleteCounts *blockgroups;
TM_IndexDelete *reordereddeltids;
BlockNumber curblock = InvalidBlockNumber;
int nblockgroups = 0;
int ncopied = 0;
int nblocksfavorable = 0;
if (promising)
blockgroups[nblockgroups - 1].npromisingtids++;
}
/*
* We're about ready to sort block groups to determine the optimal order
* for visiting heap blocks. But before we do, round the number of
* promising tuples for each block group up to the next power-of-two,
* unless it is very low (less than 4), in which case we round up to 4.
* npromisingtids is far too noisy to trust when choosing between a pair
* of block groups that both have very low values.
*
* This scheme divides heap blocks/block groups into buckets. Each bucket
* contains blocks that have _approximately_ the same number of promising
* TIDs as each other. The goal is to ignore relatively small differences
* in the total number of promising entries, so that the whole process can
* give a little weight to heapam factors (like heap block locality)
* instead. This isn't a trade-off, really -- we have nothing to lose. It
* would be foolish to interpret small differences in npromisingtids
* values as anything more than noise.
*
* We tiebreak on nhtids when sorting block group subsets that have the
* same npromisingtids, but this has the same issues as npromisingtids,
* and so nhtids is subject to the same power-of-two bucketing scheme. The
* only reason that we don't fix nhtids in the same way here too is that
* we'll need accurate nhtids values after the sort. We handle nhtids
* bucketization dynamically instead (in the sort comparator).
*
* See bottomup_nblocksfavorable() for a full explanation of when and how
* heap locality/favorable blocks can significantly influence when and how
* heap blocks are accessed.
*/
for (int b = 0; b < nblockgroups; b++)
{
IndexDeleteCounts *group = blockgroups + b;
/* Better off falling back on nhtids with low npromisingtids */
if (group->npromisingtids <= 4)
group->npromisingtids = 4;
else
group->npromisingtids =
pg_nextpower2_32((uint32) group->npromisingtids);
}
/* Sort groups and rearrange caller's deltids array */
qsort(blockgroups, nblockgroups, sizeof(IndexDeleteCounts),
bottomup_sort_and_shrink_cmp);
reordereddeltids = palloc(delstate->ndeltids * sizeof(TM_IndexDelete));
nblockgroups = Min(BOTTOMUP_MAX_NBLOCKS, nblockgroups);
/* Determine number of favorable blocks at the start of final deltids */
nblocksfavorable = bottomup_nblocksfavorable(blockgroups, nblockgroups,
delstate->deltids);
for (int b = 0; b < nblockgroups; b++)
{
IndexDeleteCounts *group = blockgroups + b;
TM_IndexDelete *firstdtid = delstate->deltids + group->ifirsttid;
/* Copy final grouped and sorted TIDs back into start of caller's array */
memcpy(delstate->deltids, reordereddeltids,
sizeof(TM_IndexDelete) * ncopied);
delstate->ndeltids = ncopied;
pfree(reordereddeltids);
pfree(blockgroups);
return nblocksfavorable;
}
/*
* Perform XLogInsert for a heap-visible operation. 'block' is the block
* being marked all-visible, and vm_buffer is the buffer containing the
* corresponding visibility map block. Both should have already been modified
* and dirtied.
*
* snapshotConflictHorizon comes from the largest xmin on the page being
* marked all-visible. REDO routine uses it to generate recovery conflicts.
*
* If checksums or wal_log_hints are enabled, we may also generate a full-page
* image of heap_buffer. Otherwise, we optimize away the FPI (by specifying
* REGBUF_NO_IMAGE for the heap buffer), in which case the caller should *not*
* update the heap page's LSN.
*/
XLogRecPtr
log_heap_visible(Relation rel, Buffer heap_buffer, Buffer vm_buffer,
TransactionId snapshotConflictHorizon, uint8 vmflags)
{
xl_heap_visible xlrec;
XLogRecPtr recptr;
uint8 flags;
/*
* Perform XLogInsert for a heap-update operation. Caller must already
* have modified the buffer(s) and marked them dirty.
*/
static XLogRecPtr
log_heap_update(Relation reln, Buffer oldbuf,
Buffer newbuf, HeapTuple oldtup, HeapTuple newtup,
HeapTuple old_key_tuple,
bool all_visible_cleared, bool new_all_visible_cleared)
{
xl_heap_update xlrec;
xl_heap_header xlhdr;
xl_heap_header xlhdr_idx;
uint8 info;
uint16 prefix_suffix[2];
uint16 prefixlen = 0,
suffixlen = 0;
XLogRecPtr recptr;
Page page = BufferGetPage(newbuf);
bool need_tuple_data = RelationIsLogicallyLogged(reln);
bool init;
int bufflags;
/* Caller should not call me on a non-WAL-logged relation */
Assert(RelationNeedsWAL(reln));
XLogBeginInsert();
if (HeapTupleIsHeapOnly(newtup))
info = XLOG_HEAP_HOT_UPDATE;
else
info = XLOG_HEAP_UPDATE;
/*
* If the old and new tuple are on the same page, we only need to log the
* parts of the new tuple that were changed. That saves on the amount of
* WAL we need to write. Currently, we just count any unchanged bytes in
* the beginning and end of the tuple. That's quick to check, and
* perfectly covers the common case that only one field is updated.
*
* We could do this even if the old and new tuple are on different pages,
* but only if we don't make a full-page image of the old page, which is
* difficult to know in advance. Also, if the old tuple is corrupt for
* some reason, it would allow the corruption to propagate the new page,
* so it seems best to avoid. Under the general assumption that most
* updates tend to create the new tuple version on the same page, there
* isn't much to be gained by doing this across pages anyway.
*
* Skip this if we're taking a full-page image of the new page, as we
* don't include the new tuple in the WAL record in that case. Also
* disable if wal_level='logical', as logical decoding needs to be able to
* read the new tuple in whole from the WAL record alone.
*/
if (oldbuf == newbuf && !need_tuple_data &&
!XLogCheckBufferNeedsBackup(newbuf))
{
char *oldp = (char *) oldtup->t_data + oldtup->t_data->t_hoff;
char *newp = (char *) newtup->t_data + newtup->t_data->t_hoff;
int oldlen = oldtup->t_len - oldtup->t_data->t_hoff;
int newlen = newtup->t_len - newtup->t_data->t_hoff;
/* Check for common prefix between old and new tuple */
for (prefixlen = 0; prefixlen < Min(oldlen, newlen); prefixlen++)
{
if (newp[prefixlen] != oldp[prefixlen])
break;
}
/*
* Storing the length of the prefix takes 2 bytes, so we need to save
* at least 3 bytes or there's no point.
*/
if (prefixlen < 3)
prefixlen = 0;
/* Same for suffix */
for (suffixlen = 0; suffixlen < Min(oldlen, newlen) - prefixlen; suffixlen++)
{
if (newp[newlen - suffixlen - 1] != oldp[oldlen - suffixlen - 1])
break;
}
if (suffixlen < 3)
suffixlen = 0;
}
/* Prepare main WAL data chain */
xlrec.flags = 0;
if (all_visible_cleared)
xlrec.flags |= XLH_UPDATE_OLD_ALL_VISIBLE_CLEARED;
if (new_all_visible_cleared)
xlrec.flags |= XLH_UPDATE_NEW_ALL_VISIBLE_CLEARED;
if (prefixlen > 0)
xlrec.flags |= XLH_UPDATE_PREFIX_FROM_OLD;
if (suffixlen > 0)
xlrec.flags |= XLH_UPDATE_SUFFIX_FROM_OLD;
if (need_tuple_data)
{
xlrec.flags |= XLH_UPDATE_CONTAINS_NEW_TUPLE; if (old_key_tuple)
{ if (reln->rd_rel->relreplident == REPLICA_IDENTITY_FULL)
xlrec.flags |= XLH_UPDATE_CONTAINS_OLD_TUPLE; else
xlrec.flags |= XLH_UPDATE_CONTAINS_OLD_KEY;
}
}
/* If new tuple is the single and first tuple on page... */ if (ItemPointerGetOffsetNumber(&(newtup->t_self)) == FirstOffsetNumber &&
PageGetMaxOffsetNumber(page) == FirstOffsetNumber)
{
info |= XLOG_HEAP_INIT_PAGE;
init = true;
} else
init = false;
/* Prepare WAL data for the old page */
xlrec.old_offnum = ItemPointerGetOffsetNumber(&oldtup->t_self);
xlrec.old_xmax = HeapTupleHeaderGetRawXmax(oldtup->t_data);
xlrec.old_infobits_set = compute_infobits(oldtup->t_data->t_infomask,
oldtup->t_data->t_infomask2);
/* Prepare WAL data for the new page */
xlrec.new_offnum = ItemPointerGetOffsetNumber(&newtup->t_self);
xlrec.new_xmax = HeapTupleHeaderGetRawXmax(newtup->t_data);
bufflags = REGBUF_STANDARD; if (init)
bufflags |= REGBUF_WILL_INIT; if (need_tuple_data)
bufflags |= REGBUF_KEEP_DATA;
XLogRegisterBuffer(0, newbuf, bufflags); if (oldbuf != newbuf)
XLogRegisterBuffer(1, oldbuf, REGBUF_STANDARD);
XLogRegisterData(&xlrec, SizeOfHeapUpdate);
/*
* Prepare WAL data for the new tuple.
*/ if (prefixlen > 0 || suffixlen > 0)
{ if (prefixlen > 0 && suffixlen > 0)
{
prefix_suffix[0] = prefixlen;
prefix_suffix[1] = suffixlen;
XLogRegisterBufData(0, &prefix_suffix, sizeof(uint16) * 2);
} elseif (prefixlen > 0)
{
XLogRegisterBufData(0, &prefixlen, sizeof(uint16));
} else
{
XLogRegisterBufData(0, &suffixlen, sizeof(uint16));
}
}
/*
* PG73FORMAT: write bitmap [+ padding] [+ oid] + data
*
* The 'data' doesn't include the common prefix or suffix.
*/
XLogRegisterBufData(0, &xlhdr, SizeOfHeapHeader); if (prefixlen == 0)
{
XLogRegisterBufData(0,
(char *) newtup->t_data + SizeofHeapTupleHeader,
newtup->t_len - SizeofHeapTupleHeader - suffixlen);
} else
{
/*
* Have to write the null bitmap and data after the common prefix as
* two separate rdata entries.
*/
/* bitmap [+ padding] [+ oid] */ if (newtup->t_data->t_hoff - SizeofHeapTupleHeader > 0)
{
XLogRegisterBufData(0,
(char *) newtup->t_data + SizeofHeapTupleHeader,
newtup->t_data->t_hoff - SizeofHeapTupleHeader);
}
/* data after common prefix */
XLogRegisterBufData(0,
(char *) newtup->t_data + newtup->t_data->t_hoff + prefixlen,
newtup->t_len - newtup->t_data->t_hoff - prefixlen - suffixlen);
}
/* We need to log a tuple identity */ if (need_tuple_data && old_key_tuple)
{
/* don't really need this, but its more comfy to decode */
xlhdr_idx.t_infomask2 = old_key_tuple->t_data->t_infomask2;
xlhdr_idx.t_infomask = old_key_tuple->t_data->t_infomask;
xlhdr_idx.t_hoff = old_key_tuple->t_data->t_hoff;
/* filtering by origin on a row level is much more efficient */
XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
recptr = XLogInsert(RM_HEAP_ID, info);
return recptr;
}
/*
* Perform XLogInsert of an XLOG_HEAP2_NEW_CID record
*
* This is only used in wal_level >= WAL_LEVEL_LOGICAL, and only for catalog
* tuples.
*/
static XLogRecPtr
log_heap_new_cid(Relation relation, HeapTuple tup)
{
xl_heap_new_cid xlrec;
/*
* If the tuple got inserted & deleted in the same TX we definitely have a
* combo CID, set cmin and cmax.
*/ if (hdr->t_infomask & HEAP_COMBOCID)
{
Assert(!(hdr->t_infomask & HEAP_XMAX_INVALID));
Assert(!HeapTupleHeaderXminInvalid(hdr));
xlrec.cmin = HeapTupleHeaderGetCmin(hdr);
xlrec.cmax = HeapTupleHeaderGetCmax(hdr);
xlrec.combocid = HeapTupleHeaderGetRawCommandId(hdr);
}
/* No combo CID, so only cmin or cmax can be set by this TX */ else
{
/*
* Tuple inserted.
*
* We need to check for LOCK ONLY because multixacts might be
* transferred to the new tuple in case of FOR KEY SHARE updates in
* which case there will be an xmax, although the tuple just got
* inserted.
*/ if (hdr->t_infomask & HEAP_XMAX_INVALID ||
HEAP_XMAX_IS_LOCKED_ONLY(hdr->t_infomask))
{
xlrec.cmin = HeapTupleHeaderGetRawCommandId(hdr);
xlrec.cmax = InvalidCommandId;
}
/* Tuple from a different tx updated or deleted. */ else
{
xlrec.cmin = InvalidCommandId;
xlrec.cmax = HeapTupleHeaderGetRawCommandId(hdr);
}
xlrec.combocid = InvalidCommandId;
}
/*
* Note that we don't need to register the buffer here, because this
* operation does not modify the page. The insert/update/delete that
* called us certainly did, but that's WAL-logged separately.
*/
XLogBeginInsert();
XLogRegisterData(&xlrec, SizeOfHeapNewCid);
java.lang.StringIndexOutOfBoundsException: Index 2 out of bounds for length 2
* Build a heap tuple representing the configured REPLICA IDENTITY to represent
* the old tuple in an UPDATE or DELETE.
*
* Returns NULL if there's no need to log an identity or if there's no suitable
* key defined.
*
* Pass key_required true if any replica identity columns changed value, or if
* any of them have any external data. Delete must always pass true.
*
* *copy is set to true if the returned tuple is a modified copy rather than
* the same tuple that was passed in.
*/
static HeapTuple
ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
bool *copy)
{
TupleDesc desc = RelationGetDescr(relation);
char replident = relation->rd_rel->relreplident;
Bitmapset *idattrs;
HeapTuple key_tuple;
bool nulls[MaxHeapAttributeNumber];
Datum values[MaxHeapAttributeNumber];
*copy = false;
if (!RelationIsLogicallyLogged(relation))
return NULL;
if (replident == REPLICA_IDENTITY_NOTHING)
return NULL;
if (replident == REPLICA_IDENTITY_FULL)
{
/*
* When logging the entire old tuple, it very well could contain
* toasted columns. If so, force them to be inlined.
*/ if (HeapTupleHasExternal(tp))
{
*copy = true;
tp = toast_flatten_tuple(tp, desc);
}
return tp;
}
/* if the key isn't required and we're only logging the key, we're done */ if (!key_required)
return NULL;
/* find out the replica identity columns */
idattrs = RelationGetIndexAttrBitmap(relation,
INDEX_ATTR_BITMAP_IDENTITY_KEY);
/*
* If there's no defined replica identity columns, treat as !key_required.
* (This case should not be reachable from heap_update, since that should
* calculate key_required accurately. But heap_delete just passes
* constant true for key_required, so we can hit this case in deletes.)
*/ if (bms_is_empty(idattrs))
return NULL;
/*
* Construct a new tuple containing only the replica identity columns,
* with nulls elsewhere. While we're at it, assert that the replica
* identity columns aren't null.
*/
heap_deform_tuple(tp, desc, values, nulls);
for (int i = 0; i < desc->natts; i++)
{ if (bms_is_member(i + 1 - FirstLowInvalidHeapAttributeNumber,
idattrs))
Assert(!nulls[i]); else
nulls[i] = true;
}
/*
* If the tuple, which by here only contains indexed columns, still has
* toasted columns, force them to be inlined. This is somewhat unlikely
* since there's limits on the size of indexed columns, so we don't
* duplicate toast_flatten_tuple()s functionality in the above loop over
* the indexed columns, even if it would be more efficient.
*/ if (HeapTupleHasExternal(key_tuple))
{
HeapTuple oldtup = key_tuple;
/*
* HeapCheckForSerializableConflictOut
* We are reading a tuple. If it's not visible, there may be a
* rw-conflict out with the inserter. Otherwise, if it is visible to us
* but has been deleted, there may be a rw-conflict out with the deleter.
*
* We will determine the top level xid of the writing transaction with which
* we may be in conflict, and ask CheckForSerializableConflictOut() to check
* for overlap with our own transaction.
*
* This function should be called just about anywhere in heapam.c where a
* tuple has been read. The caller must hold at least a shared lock on the
* buffer, because this function might set hint bits on the tuple. There is
* currently no known reason to call this function from an index AM.
*/
void
HeapCheckForSerializableConflictOut(bool visible, Relation relation,
HeapTuple tuple, Buffer buffer,
Snapshot snapshot)
{
TransactionId xid;
HTSV_Result htsvResult;
if (!CheckForSerializableConflictOutNeeded(relation, snapshot))
return;
/*
* Check to see whether the tuple has been written to by a concurrent
* transaction, either to create it not visible to us, or to delete it
* while it is visible to us. The "visible" bool indicates whether the
* tuple is visible to us, while HeapTupleSatisfiesVacuum checks what else
* is going on with it.
*
* In the event of a concurrently inserted tuple that also happens to have
* been concurrently updated (by a separate transaction), the xmin of the
* tuple will be used -- not the updater's xid.
*/
htsvResult = HeapTupleSatisfiesVacuum(tuple, TransactionXmin, buffer);
switch (htsvResult)
{
case HEAPTUPLE_LIVE: if (visible)
return;
xid = HeapTupleHeaderGetXmin(tuple->t_data);
break;
case HEAPTUPLE_RECENTLY_DEAD:
case HEAPTUPLE_DELETE_IN_PROGRESS: if (visible)
xid = HeapTupleHeaderGetUpdateXid(tuple->t_data); else
xid = HeapTupleHeaderGetXmin(tuple->t_data);
if (TransactionIdPrecedes(xid, TransactionXmin))
{
/* This is like the HEAPTUPLE_DEAD case */
Assert(!visible);
return;
}
break;
case HEAPTUPLE_INSERT_IN_PROGRESS:
xid = HeapTupleHeaderGetXmin(tuple->t_data);
break;
case HEAPTUPLE_DEAD:
Assert(!visible);
return;
default:
/*
* The only way to get to this default clause is if a new value is
* added to the enum type without adding it to this switch
* statement. That's a bug, so elog.
*/
elog(ERROR, "unrecognized return value from HeapTupleSatisfiesVacuum: %u", htsvResult);
/*
* In spite of having all enum values covered and calling elog on
* this default, some compilers think this is a code path which
* allows xid to be used below without initialization. Silence
* that warning.
*/
xid = InvalidTransactionId;
}
/*
* Find top level xid. Bail out if xid is too early to be a conflict, or
* if it's our own xid.
*/ if (TransactionIdEquals(xid, GetTopTransactionIdIfAny()))
return;
xid = SubTransGetTopmostTransaction(xid); if (TransactionIdPrecedes(xid, TransactionXmin))
return;
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.1.147Bemerkung:
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.