diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 8975191..175c656 100644 *** a/src/backend/access/heap/heapam.c --- b/src/backend/access/heap/heapam.c *************** static XLogRecPtr log_heap_update(Relati *** 83,88 **** --- 83,89 ---- bool all_visible_cleared, bool new_all_visible_cleared); static bool HeapSatisfiesHOTUpdate(Relation relation, Bitmapset *hot_attrs, HeapTuple oldtup, HeapTuple newtup); + static bool HeapSatisfiesLockersVisible(HeapTupleHeader tuple, Snapshot snapshot); /* ---------------------------------------------------------------- *************** simple_heap_insert(Relation relation, He *** 2033,2040 **** * 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 * * Normal, successful return value is HeapTupleMayBeUpdated, which * actually means we did delete it. Failure return codes are --- 2034,2042 ---- * 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) * 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 *************** simple_heap_insert(Relation relation, He *** 2049,2055 **** HTSU_Result heap_delete(Relation relation, ItemPointer tid, ItemPointer ctid, TransactionId *update_xmax, ! CommandId cid, Snapshot crosscheck, bool wait) { HTSU_Result result; TransactionId xid = GetCurrentTransactionId(); --- 2051,2057 ---- HTSU_Result heap_delete(Relation relation, ItemPointer tid, ItemPointer ctid, TransactionId *update_xmax, ! CommandId cid, bool wait, Snapshot lockcheck_snapshot) { HTSU_Result result; TransactionId xid = GetCurrentTransactionId(); *************** l1: *** 2170,2181 **** else result = HeapTupleUpdated; } ! ! if (crosscheck != InvalidSnapshot && result == HeapTupleMayBeUpdated) { ! /* Perform additional check for serializable RI updates */ ! if (!HeapTupleSatisfiesVisibility(&tp, crosscheck, buffer)) ! result = HeapTupleUpdated; } if (result != HeapTupleMayBeUpdated) --- 2172,2183 ---- else result = HeapTupleUpdated; } ! ! /* Verify visibility of locking transactions. */ ! if ((result == HeapTupleMayBeUpdated) && ! !HeapSatisfiesLockersVisible(tp.t_data, lockcheck_snapshot)) { ! result = HeapTupleUpdated; } if (result != HeapTupleMayBeUpdated) *************** l1: *** 2183,2189 **** Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated || result == HeapTupleBeingUpdated); ! Assert(!(tp.t_data->t_infomask & HEAP_XMAX_INVALID)); *ctid = tp.t_data->t_ctid; *update_xmax = HeapTupleHeaderGetXmax(tp.t_data); UnlockReleaseBuffer(buffer); --- 2185,2192 ---- Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated || result == HeapTupleBeingUpdated); ! 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); *************** simple_heap_delete(Relation relation, It *** 2313,2320 **** result = heap_delete(relation, tid, &update_ctid, &update_xmax, ! GetCurrentCommandId(true), InvalidSnapshot, ! true /* wait for commit */ ); switch (result) { case HeapTupleSelfUpdated: --- 2316,2324 ---- result = heap_delete(relation, tid, &update_ctid, &update_xmax, ! GetCurrentCommandId(true), ! true /* wait for commit */ , ! InvalidSnapshot); switch (result) { case HeapTupleSelfUpdated: *************** simple_heap_delete(Relation relation, It *** 2349,2355 **** * 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 * wait - true if should wait for any conflicting update to commit/abort * * Normal, successful return value is HeapTupleMayBeUpdated, which --- 2353,2360 ---- * 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) ! * 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 *************** simple_heap_delete(Relation relation, It *** 2371,2377 **** HTSU_Result heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, ItemPointer ctid, TransactionId *update_xmax, ! CommandId cid, Snapshot crosscheck, bool wait) { HTSU_Result result; TransactionId xid = GetCurrentTransactionId(); --- 2376,2382 ---- HTSU_Result heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, ItemPointer ctid, TransactionId *update_xmax, ! CommandId cid, bool wait, Snapshot lockcheck_snapshot) { HTSU_Result result; TransactionId xid = GetCurrentTransactionId(); *************** l2: *** 2523,2541 **** result = HeapTupleUpdated; } ! if (crosscheck != InvalidSnapshot && result == HeapTupleMayBeUpdated) { ! /* Perform additional check for serializable RI updates */ ! if (!HeapTupleSatisfiesVisibility(&oldtup, crosscheck, buffer)) ! result = HeapTupleUpdated; } if (result != HeapTupleMayBeUpdated) { Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated || result == HeapTupleBeingUpdated); ! Assert(!(oldtup.t_data->t_infomask & HEAP_XMAX_INVALID)); *ctid = oldtup.t_data->t_ctid; *update_xmax = HeapTupleHeaderGetXmax(oldtup.t_data); UnlockReleaseBuffer(buffer); --- 2528,2548 ---- result = HeapTupleUpdated; } ! /* Verify visibility of locking transactions. */ ! if ((result == HeapTupleMayBeUpdated) && ! !HeapSatisfiesLockersVisible(oldtup.t_data, lockcheck_snapshot)) { ! result = HeapTupleUpdated; } + if (result != HeapTupleMayBeUpdated) { Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated || result == HeapTupleBeingUpdated); ! 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); *************** HeapSatisfiesHOTUpdate(Relation relation *** 2961,2966 **** --- 2968,3057 ---- return true; } + + /* + * Returns false if one of the tuple's lockers committed but + * aren't visible according to lockcheck_snapshot, exclusing subtransactions + * of the current transaction. + * Assumes that all locking transaction either committed or aborted, + * but aren't still in progress. + */ + static bool + HeapSatisfiesLockersVisible(HeapTupleHeader tuple, Snapshot lockcheck_snapshot) + { + if (lockcheck_snapshot == InvalidSnapshot) + return true; + + 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) + { + TransactionId* xids; + int xids_l = GetMultiXactIdMembers(HeapTupleHeaderGetXmax(tuple), &xids); + + if (xids_l < 1) { + /* The multi xact either is too old to be inspected or doesn't contain members. + * The second case is probably impossible, but even if not it doesn't pose + * any problem. + * In the first case, we have to trust that all xids that were contained in + * the xact are in fact visible under lockcheck_snapshot. Currently this + * is always the case, since lockcheck_snapshot is always the transaction's + * serializable snapshot, and we call MultiXactIdSetOldestVisible() before + * acquireing that snapshot. + */ + return true; + } + else + { + int i; + for (i = 0; i < xids_l; ++i) + { + /* Ignore our own subtransactions */ + if (TransactionIdIsCurrentTransactionId(xids[i])) + continue; + + /* We expect to be called after the locking transactions' fates have been decided */ + Assert(!TransactionIdIsInProgress(xids[i])); + + if (!TransactionIdDidAbort(xids[i]) && + XidInMVCCSnapshot(xids[i], lockcheck_snapshot)) + { + /* Ups. Non-aborted, invisible locker */ + return false; + } + } + return true; + } + } + else + { + TransactionId xid = HeapTupleHeaderGetXmax(tuple); + + /* Ignore our own subtransactions */ + if (TransactionIdIsCurrentTransactionId(xid)) + return true; + + /* We expect to be called after the locking transaction's fate has been decided, excluding + ourself. */ + Assert(!TransactionIdIsInProgress(xid)); + + /* Locker must either be visible or have aborted */ + return TransactionIdDidAbort(xid) || + !XidInMVCCSnapshot(xid, lockcheck_snapshot); + } + } + else + { + /* Tuple wasn't locked */ + return true; + } + } + /* * simple_heap_update - replace a tuple * *************** simple_heap_update(Relation relation, It *** 2978,2985 **** result = heap_update(relation, otid, tup, &update_ctid, &update_xmax, ! GetCurrentCommandId(true), InvalidSnapshot, ! true /* wait for commit */ ); switch (result) { case HeapTupleSelfUpdated: --- 3069,3076 ---- result = heap_update(relation, otid, tup, &update_ctid, &update_xmax, ! GetCurrentCommandId(true), ! true /* wait for commit */, InvalidSnapshot); switch (result) { case HeapTupleSelfUpdated: *************** simple_heap_update(Relation relation, It *** 3013,3018 **** --- 3104,3112 ---- * 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 *************** simple_heap_update(Relation relation, It *** 3066,3072 **** HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple, Buffer *buffer, ItemPointer ctid, TransactionId *update_xmax, ! CommandId cid, LockTupleMode mode, bool nowait) { HTSU_Result result; ItemPointer tid = &(tuple->t_self); --- 3160,3167 ---- HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple, Buffer *buffer, ItemPointer ctid, TransactionId *update_xmax, ! CommandId cid, LockTupleMode mode, bool nowait, ! Snapshot lockcheck_snapshot) { HTSU_Result result; ItemPointer tid = &(tuple->t_self); *************** l3: *** 3246,3256 **** else result = HeapTupleUpdated; } if (result != HeapTupleMayBeUpdated) { Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated); ! Assert(!(tuple->t_data->t_infomask & HEAP_XMAX_INVALID)); *ctid = tuple->t_data->t_ctid; *update_xmax = HeapTupleHeaderGetXmax(tuple->t_data); LockBuffer(*buffer, BUFFER_LOCK_UNLOCK); --- 3341,3359 ---- else result = HeapTupleUpdated; } + + /* Verify visibility of locking transactions */ + if ((result == HeapTupleMayBeUpdated) && + !HeapSatisfiesLockersVisible(tuple->t_data, lockcheck_snapshot)) + { + result = HeapTupleUpdated; + } if (result != HeapTupleMayBeUpdated) { Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated); ! 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/access/transam/multixact.c b/src/backend/access/transam/multixact.c index b5bb311..47ff5de 100644 *** a/src/backend/access/transam/multixact.c --- b/src/backend/access/transam/multixact.c *************** static MemoryContext MXactContext = NULL *** 211,217 **** #endif /* internal MultiXactId management */ - static void MultiXactIdSetOldestVisible(void); static MultiXactId CreateMultiXactId(int nxids, TransactionId *xids); static void RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset, int nxids, TransactionId *xids); --- 211,216 ---- *************** MultiXactIdSetOldestMember(void) *** 531,537 **** * there is no live transaction, now or later, that can be a member of any * MultiXactId older than the OldestVisibleMXactId we compute here. */ ! static void MultiXactIdSetOldestVisible(void) { if (!MultiXactIdIsValid(OldestVisibleMXactId[MyBackendId])) --- 530,536 ---- * there is no live transaction, now or later, that can be a member of any * MultiXactId older than the OldestVisibleMXactId we compute here. */ ! void MultiXactIdSetOldestVisible(void) { if (!MultiXactIdIsValid(OldestVisibleMXactId[MyBackendId])) 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 *************** ltrmark:; *** 2347,2353 **** test = heap_lock_tuple(relation, &tuple, &buffer, &update_ctid, &update_xmax, estate->es_output_cid, ! LockTupleExclusive, false); switch (test) { case HeapTupleSelfUpdated: --- 2347,2355 ---- test = heap_lock_tuple(relation, &tuple, &buffer, &update_ctid, &update_xmax, estate->es_output_cid, ! 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 *************** EvalPlanQualFetch(EState *estate, Relati *** 1518,1527 **** /* * This is a live tuple, so now try to lock it. */ test = heap_lock_tuple(relation, &tuple, &buffer, &update_ctid, &update_xmax, estate->es_output_cid, ! lockmode, false); /* We now have two pins on the buffer, get rid of one */ ReleaseBuffer(buffer); --- 1518,1530 ---- /* * 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, ! 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..b771b19 100644 *** a/src/backend/executor/nodeLockRows.c --- b/src/backend/executor/nodeLockRows.c *************** lnext: *** 71,76 **** --- 71,77 ---- ItemPointerData update_ctid; TransactionId update_xmax; LockTupleMode lockmode; + Snapshot lockcheck_snapshot = InvalidSnapshot; HTSU_Result test; HeapTuple copyTuple; *************** lnext: *** 110,123 **** /* okay, try to lock the tuple */ if (erm->markType == ROW_MARK_EXCLUSIVE) lockmode = LockTupleExclusive; else lockmode = LockTupleShared; test = heap_lock_tuple(erm->relation, &tuple, &buffer, &update_ctid, &update_xmax, estate->es_output_cid, ! lockmode, erm->noWait); ReleaseBuffer(buffer); switch (test) { --- 111,134 ---- /* okay, try to lock the tuple */ if (erm->markType == ROW_MARK_EXCLUSIVE) + { lockmode = LockTupleExclusive; + /* In serializable mode, acquireing an exclusive lock + * requires seeing all other lockers. + */ + if (IsXactIsoLevelSerializable) + lockcheck_snapshot = estate->es_snapshot; + } else + { lockmode = LockTupleShared; + } test = heap_lock_tuple(erm->relation, &tuple, &buffer, &update_ctid, &update_xmax, estate->es_output_cid, ! lockmode, erm->noWait, ! lockcheck_snapshot); 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 *************** ldelete:; *** 316,323 **** result = heap_delete(resultRelationDesc, tupleid, &update_ctid, &update_xmax, estate->es_output_cid, ! estate->es_crosscheck_snapshot, ! true /* wait for commit */ ); switch (result) { case HeapTupleSelfUpdated: --- 316,324 ---- result = heap_delete(resultRelationDesc, tupleid, &update_ctid, &update_xmax, estate->es_output_cid, ! true, /* wait for commit */ ! IsXactIsoLevelSerializable ? estate->es_snapshot : ! InvalidSnapshot); switch (result) { case HeapTupleSelfUpdated: *************** lreplace:; *** 504,511 **** result = heap_update(resultRelationDesc, tupleid, tuple, &update_ctid, &update_xmax, estate->es_output_cid, ! estate->es_crosscheck_snapshot, ! true /* wait for commit */ ); switch (result) { case HeapTupleSelfUpdated: --- 505,513 ---- result = heap_update(resultRelationDesc, tupleid, tuple, &update_ctid, &update_xmax, estate->es_output_cid, ! true, /* wait for commit */ ! IsXactIsoLevelSerializable ? estate->es_snapshot : ! InvalidSnapshot); switch (result) { case HeapTupleSelfUpdated: diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c index 59a27de..d4dc5ca 100644 *** a/src/backend/utils/time/snapmgr.c --- b/src/backend/utils/time/snapmgr.c *************** *** 27,32 **** --- 27,33 ---- #include "access/transam.h" #include "access/xact.h" + #include "access/multixact.h" #include "storage/proc.h" #include "storage/procarray.h" #include "utils/memutils.h" *************** GetTransactionSnapshot(void) *** 125,130 **** --- 126,139 ---- if (!FirstSnapshotSet) { Assert(RegisteredSnapshots == 0); + + /* Must store the oldest visible multi xact *before* taking the + * serializable snapshot. Otherwise HeapSatisfiesLockersVisible in + * heapam.c will fail, since it'll be unable to inspect a multi xact + * that might contains xids invisible to the serializable snapshot. + */ + if (IsXactIsoLevelSerializable) + MultiXactIdSetOldestVisible(); CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData); FirstSnapshotSet = true; 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 *************** SnapshotData SnapshotSelfData = {HeapTup *** 72,80 **** SnapshotData SnapshotAnyData = {HeapTupleSatisfiesAny}; SnapshotData SnapshotToastData = {HeapTupleSatisfiesToast}; - /* local functions */ - static bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot); - /* * SetHintBits() --- 72,77 ---- *************** HeapTupleSatisfiesVacuum(HeapTupleHeader *** 1253,1259 **** * by this function. This is OK for current uses, because we actually only * apply this for known-committed XIDs. */ ! static bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot) { uint32 i; --- 1250,1256 ---- * by this function. This is OK for current uses, because we actually only * apply this for known-committed XIDs. */ ! 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 *************** extern Oid heap_insert(Relation relation *** 98,112 **** int options, BulkInsertState bistate); extern HTSU_Result heap_delete(Relation relation, ItemPointer tid, ItemPointer ctid, TransactionId *update_xmax, ! CommandId cid, Snapshot crosscheck, bool wait); extern HTSU_Result heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, ItemPointer ctid, TransactionId *update_xmax, ! CommandId cid, Snapshot crosscheck, bool wait); extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple, Buffer *buffer, ItemPointer ctid, TransactionId *update_xmax, CommandId cid, ! LockTupleMode mode, bool nowait); extern void heap_inplace_update(Relation relation, HeapTuple tuple); extern bool heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid, Buffer buf); --- 98,113 ---- int options, BulkInsertState bistate); extern HTSU_Result heap_delete(Relation relation, ItemPointer tid, ItemPointer ctid, TransactionId *update_xmax, ! CommandId cid, bool wait, Snapshot crosscheck); extern HTSU_Result heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, ItemPointer ctid, TransactionId *update_xmax, ! 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, ! 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/access/multixact.h b/src/include/access/multixact.h index 5910465..4769bca 100644 *** a/src/include/access/multixact.h --- b/src/include/access/multixact.h *************** extern bool MultiXactIdIsCurrent(MultiXa *** 49,54 **** --- 49,55 ---- extern void MultiXactIdWait(MultiXactId multi); extern bool ConditionalMultiXactIdWait(MultiXactId multi); extern void MultiXactIdSetOldestMember(void); + extern void MultiXactIdSetOldestVisible(void); extern int GetMultiXactIdMembers(MultiXactId multi, TransactionId **xids); extern void AtEOXact_MultiXact(void); 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 *************** extern PGDLLIMPORT SnapshotData Snapshot *** 41,46 **** --- 41,48 ---- #define IsMVCCSnapshot(snapshot) \ ((snapshot)->satisfies == HeapTupleSatisfiesMVCC) + bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot); + /* * HeapTupleSatisfiesVisibility * True iff heap tuple satisfies a time qual.