diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 8975191..bf6c77e 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -83,6 +83,7 @@ static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf, bool all_visible_cleared, bool new_all_visible_cleared); static bool HeapSatisfiesHOTUpdate(Relation relation, Bitmapset *hot_attrs, HeapTuple oldtup, HeapTuple newtup); +static bool HeapSatisfiedLockCheckSnapshot(HeapTupleHeader tuple, Snapshot snapshot); /* ---------------------------------------------------------------- @@ -2033,8 +2034,9 @@ simple_heap_insert(Relation relation, HeapTuple tup) * update_xmax - output parameter, used only for failure case (see below) * cid - delete command ID (used for visibility test, and stored into * cmax if successful) - * crosscheck - if not InvalidSnapshot, also check tuple against this * wait - true if should wait for any conflicting update to commit/abort + * lockcheck_snapshot - if not NULL, report the tuple as updated if it + * was locked by a transaction not visible under this snapshot * * Normal, successful return value is HeapTupleMayBeUpdated, which * actually means we did delete it. Failure return codes are @@ -2049,7 +2051,7 @@ simple_heap_insert(Relation relation, HeapTuple tup) HTSU_Result heap_delete(Relation relation, ItemPointer tid, ItemPointer ctid, TransactionId *update_xmax, - CommandId cid, Snapshot crosscheck, bool wait) + CommandId cid, bool wait, Snapshot lockcheck_snapshot) { HTSU_Result result; TransactionId xid = GetCurrentTransactionId(); @@ -2171,11 +2173,10 @@ l1: result = HeapTupleUpdated; } - if (crosscheck != InvalidSnapshot && result == HeapTupleMayBeUpdated) + if ((result == HeapTupleMayBeUpdated) && + !HeapSatisfiedLockCheckSnapshot(tp.t_data, lockcheck_snapshot)) { - /* Perform additional check for serializable RI updates */ - if (!HeapTupleSatisfiesVisibility(&tp, crosscheck, buffer)) - result = HeapTupleUpdated; + result = HeapTupleUpdated; } if (result != HeapTupleMayBeUpdated) @@ -2183,7 +2184,8 @@ l1: Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated || result == HeapTupleBeingUpdated); - Assert(!(tp.t_data->t_infomask & HEAP_XMAX_INVALID)); + Assert(!(tp.t_data->t_infomask & HEAP_XMAX_INVALID) || + (tp.t_data->t_infomask & HEAP_IS_LOCKED)); *ctid = tp.t_data->t_ctid; *update_xmax = HeapTupleHeaderGetXmax(tp.t_data); UnlockReleaseBuffer(buffer); @@ -2313,8 +2315,9 @@ simple_heap_delete(Relation relation, ItemPointer tid) result = heap_delete(relation, tid, &update_ctid, &update_xmax, - GetCurrentCommandId(true), InvalidSnapshot, - true /* wait for commit */ ); + GetCurrentCommandId(true), + true /* wait for commit */ , + InvalidSnapshot); switch (result) { case HeapTupleSelfUpdated: @@ -2349,7 +2352,8 @@ simple_heap_delete(Relation relation, ItemPointer tid) * update_xmax - output parameter, used only for failure case (see below) * cid - update command ID (used for visibility test, and stored into * cmax/cmin if successful) - * crosscheck - if not InvalidSnapshot, also check old tuple against this + * lockcheck_snapshot - if not NULL, report the tuple as updated if it + * was locked by a transaction not visible under this snapshot * wait - true if should wait for any conflicting update to commit/abort * * Normal, successful return value is HeapTupleMayBeUpdated, which @@ -2371,7 +2375,7 @@ simple_heap_delete(Relation relation, ItemPointer tid) HTSU_Result heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, ItemPointer ctid, TransactionId *update_xmax, - CommandId cid, Snapshot crosscheck, bool wait) + CommandId cid, bool wait, Snapshot lockcheck_snapshot) { HTSU_Result result; TransactionId xid = GetCurrentTransactionId(); @@ -2523,19 +2527,20 @@ l2: result = HeapTupleUpdated; } - if (crosscheck != InvalidSnapshot && result == HeapTupleMayBeUpdated) + if ((result == HeapTupleMayBeUpdated) && + !HeapSatisfiedLockCheckSnapshot(oldtup.t_data, lockcheck_snapshot)) { - /* Perform additional check for serializable RI updates */ - if (!HeapTupleSatisfiesVisibility(&oldtup, crosscheck, buffer)) - result = HeapTupleUpdated; + result = HeapTupleUpdated; } + if (result != HeapTupleMayBeUpdated) { Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated || result == HeapTupleBeingUpdated); - Assert(!(oldtup.t_data->t_infomask & HEAP_XMAX_INVALID)); + Assert(!(oldtup.t_data->t_infomask & HEAP_XMAX_INVALID) || + (oldtup.t_data->t_infomask & HEAP_IS_LOCKED)); *ctid = oldtup.t_data->t_ctid; *update_xmax = HeapTupleHeaderGetXmax(oldtup.t_data); UnlockReleaseBuffer(buffer); @@ -2961,6 +2966,56 @@ HeapSatisfiesHOTUpdate(Relation relation, Bitmapset *hot_attrs, return true; } + +/* + * Check if the tuple was locked by a transaction invisible to + * lockcheck_snapshot. Returns false if so. + */ +static bool +HeapSatisfiedLockCheckSnapshot(HeapTupleHeader tuple, Snapshot lockcheck_snapshot) +{ + if (tuple->t_infomask & HEAP_IS_LOCKED) + { + /* If the tuple was locked, we now check whether the locking + * transaction(s) are visible under lockcheck_snapshot. If + * they aren't, we pretent the tuple was updated. + */ + + if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) + { + /* XXXFGP: Do the same check as below, but for multi xacts. The + * hard part is to guarantee that the multixact will still + * be there when we need to examine it. Currently, no + * mechanism seems to exist that'd guarantee that all + * multi xacts containing members invisible to + * lockcheck_snapshot outlive lockcheck_snapshot. Especially + * if lockcheck_snapshot is a serializable snapshot, which + * unfortunately is it's designated use-case. + */ + return true; + } + else + { + if ((lockcheck_snapshot != InvalidSnapshot) && + XidInMVCCSnapshot(HeapTupleHeaderGetXmax(tuple), lockcheck_snapshot)) + { + /* Locking transaction is visible to lockcheck_snapshot */ + return false; + } + else + { + /* Locking transaction is visible to lockcheck_snapshot */ + return true; + } + } + } + else + { + /* Tuple wasn't locked */ + return true; + } +} + /* * simple_heap_update - replace a tuple * @@ -2978,8 +3033,8 @@ simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup) result = heap_update(relation, otid, tup, &update_ctid, &update_xmax, - GetCurrentCommandId(true), InvalidSnapshot, - true /* wait for commit */ ); + GetCurrentCommandId(true), + true /* wait for commit */, InvalidSnapshot); switch (result) { case HeapTupleSelfUpdated: @@ -3013,6 +3068,9 @@ simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup) * tuple's cmax if lock is successful) * mode: indicates if shared or exclusive tuple lock is desired * nowait: if true, ereport rather than blocking if lock not available + * lockcheck_snapshot: if not NULL, report the tuple as updated if it + * was locked by a transaction not visible under + * this snapshot * * Output parameters: * *tuple: all fields filled in @@ -3066,7 +3124,8 @@ simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup) HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple, Buffer *buffer, ItemPointer ctid, TransactionId *update_xmax, - CommandId cid, LockTupleMode mode, bool nowait) + CommandId cid, LockTupleMode mode, bool nowait, + Snapshot lockcheck_snapshot) { HTSU_Result result; ItemPointer tid = &(tuple->t_self); @@ -3247,10 +3306,17 @@ l3: result = HeapTupleUpdated; } + if ((result == HeapTupleMayBeUpdated) && + !HeapSatisfiedLockCheckSnapshot(tuple->t_data, lockcheck_snapshot)) + { + result = HeapTupleUpdated; + } + if (result != HeapTupleMayBeUpdated) { Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated); - Assert(!(tuple->t_data->t_infomask & HEAP_XMAX_INVALID)); + Assert(!(tuple->t_data->t_infomask & HEAP_XMAX_INVALID) || + (tuple->t_data->t_infomask & HEAP_IS_LOCKED)); *ctid = tuple->t_data->t_ctid; *update_xmax = HeapTupleHeaderGetXmax(tuple->t_data); LockBuffer(*buffer, BUFFER_LOCK_UNLOCK); diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 2cbc192..120ea60 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -2347,7 +2347,9 @@ ltrmark:; test = heap_lock_tuple(relation, &tuple, &buffer, &update_ctid, &update_xmax, estate->es_output_cid, - LockTupleExclusive, false); + LockTupleExclusive, false, + IsXactIsoLevelSerializable ? estate->es_snapshot : + InvalidSnapshot); switch (test) { case HeapTupleSelfUpdated: diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index d299310..fd5ed3c 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -1518,10 +1518,13 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode, /* * This is a live tuple, so now try to lock it. */ + Assert(!IsXactIsoLevelSerializable || (estate->es_snapshot != InvalidSnapshot)); test = heap_lock_tuple(relation, &tuple, &buffer, &update_ctid, &update_xmax, estate->es_output_cid, - lockmode, false); + lockmode, false, + IsXactIsoLevelSerializable ? estate->es_snapshot : + InvalidSnapshot); /* We now have two pins on the buffer, get rid of one */ ReleaseBuffer(buffer); diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c index 00f0562..f8baa13 100644 --- a/src/backend/executor/nodeLockRows.c +++ b/src/backend/executor/nodeLockRows.c @@ -117,7 +117,9 @@ lnext: test = heap_lock_tuple(erm->relation, &tuple, &buffer, &update_ctid, &update_xmax, estate->es_output_cid, - lockmode, erm->noWait); + lockmode, erm->noWait, + IsXactIsoLevelSerializable ? estate->es_snapshot : + InvalidSnapshot); ReleaseBuffer(buffer); switch (test) { diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 7856b66..ecc75f9 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -316,8 +316,9 @@ ldelete:; result = heap_delete(resultRelationDesc, tupleid, &update_ctid, &update_xmax, estate->es_output_cid, - estate->es_crosscheck_snapshot, - true /* wait for commit */ ); + true, /* wait for commit */ + IsXactIsoLevelSerializable ? estate->es_snapshot : + InvalidSnapshot); switch (result) { case HeapTupleSelfUpdated: @@ -504,8 +505,9 @@ lreplace:; result = heap_update(resultRelationDesc, tupleid, tuple, &update_ctid, &update_xmax, estate->es_output_cid, - estate->es_crosscheck_snapshot, - true /* wait for commit */ ); + true, /* wait for commit */ + IsXactIsoLevelSerializable ? estate->es_snapshot : + InvalidSnapshot); switch (result) { case HeapTupleSelfUpdated: diff --git a/src/backend/utils/time/tqual.c b/src/backend/utils/time/tqual.c index 4f3630c..302a571 100644 --- a/src/backend/utils/time/tqual.c +++ b/src/backend/utils/time/tqual.c @@ -72,9 +72,6 @@ SnapshotData SnapshotSelfData = {HeapTupleSatisfiesSelf}; SnapshotData SnapshotAnyData = {HeapTupleSatisfiesAny}; SnapshotData SnapshotToastData = {HeapTupleSatisfiesToast}; -/* local functions */ -static bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot); - /* * SetHintBits() @@ -1253,7 +1250,7 @@ HeapTupleSatisfiesVacuum(HeapTupleHeader tuple, TransactionId OldestXmin, * by this function. This is OK for current uses, because we actually only * apply this for known-committed XIDs. */ -static bool +bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot) { uint32 i; diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index f963146..a17d62c 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -98,15 +98,16 @@ extern Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid, int options, BulkInsertState bistate); extern HTSU_Result heap_delete(Relation relation, ItemPointer tid, ItemPointer ctid, TransactionId *update_xmax, - CommandId cid, Snapshot crosscheck, bool wait); + CommandId cid, bool wait, Snapshot crosscheck); extern HTSU_Result heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, ItemPointer ctid, TransactionId *update_xmax, - CommandId cid, Snapshot crosscheck, bool wait); + CommandId cid, bool wait, Snapshot crosscheck); extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple, Buffer *buffer, ItemPointer ctid, TransactionId *update_xmax, CommandId cid, - LockTupleMode mode, bool nowait); + LockTupleMode mode, bool nowait, + Snapshot lockcheck_snapshot); extern void heap_inplace_update(Relation relation, HeapTuple tuple); extern bool heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid, Buffer buf); diff --git a/src/include/utils/tqual.h b/src/include/utils/tqual.h index e85e820..fa386e7 100644 --- a/src/include/utils/tqual.h +++ b/src/include/utils/tqual.h @@ -41,6 +41,8 @@ extern PGDLLIMPORT SnapshotData SnapshotToastData; #define IsMVCCSnapshot(snapshot) \ ((snapshot)->satisfies == HeapTupleSatisfiesMVCC) +bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot); + /* * HeapTupleSatisfiesVisibility * True iff heap tuple satisfies a time qual.