*/
static PredTranList PredLockTranList;
+/*
+ * This provides a pool of RWConflict data elements to use in conflict lists
+ * between transactions.
+ */
+static RWConflictPoolHeader RWConflictPool;
/*
* The predicate locking hash tables are in shared memory.
}
+/*
+ * These functions manage primitive access to the RWConflict pool and lists.
+ */
+static bool
+RWConflictExists(const SERIALIZABLEXACT *reader, const SERIALIZABLEXACT *writer)
+{
+ RWConflict conflict;
+
+ Assert(reader != writer);
+
+ /* Check the ends of the purported conflict first. */
+ if ((reader->flags & SXACT_FLAG_ROLLED_BACK)
+ || (writer->flags & SXACT_FLAG_ROLLED_BACK)
+ || SHMQueueIsDetached((SHM_QUEUE *) &reader->outConflicts)
+ || SHMQueueIsDetached((SHM_QUEUE *) &writer->inConflicts))
+ return false;
+
+ /* A conflict is possible; walk the list to find out. */
+ conflict = (RWConflict)
+ SHMQueueNext((SHM_QUEUE *) &reader->outConflicts,
+ (SHM_QUEUE *) &reader->outConflicts,
+ offsetof(RWConflictData, outLink));
+ while (conflict)
+ {
+ if (conflict->sxactIn == writer)
+ return true;
+ conflict = (RWConflict)
+ SHMQueueNext((SHM_QUEUE *) &reader->outConflicts,
+ &conflict->outLink,
+ offsetof(RWConflictData, outLink));
+ }
+
+ /* No conflict found. */
+ return false;
+}
+
+static void
+SetRWConflict(SERIALIZABLEXACT *reader, SERIALIZABLEXACT *writer)
+{
+ RWConflict conflict;
+
+ Assert(reader != writer);
+ Assert(!RWConflictExists(reader, writer));
+
+ conflict = (RWConflict)
+ SHMQueueNext(&RWConflictPool->availableList,
+ &RWConflictPool->availableList,
+ offsetof(RWConflictData, outLink));
+ if (!conflict)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("not enough elements in RWConflictPool to record a rw-conflict")));
+
+ SHMQueueDelete(&conflict->outLink);
+
+ conflict->sxactOut = reader;
+ conflict->sxactIn = writer;
+ SHMQueueInsertBefore(&reader->outConflicts, &conflict->outLink);
+ SHMQueueInsertBefore(&writer->inConflicts, &conflict->inLink);
+}
+
+static void
+ReleaseRWConflict(RWConflict conflict)
+{
+ SHMQueueDelete(&conflict->inLink);
+ SHMQueueDelete(&conflict->outLink);
+ SHMQueueInsertBefore(&RWConflictPool->availableList, &conflict->outLink);
+}
+
+
/*
* InitPredicateLocks -- Initialize the predicate locking data structures.
*
errmsg("not enough shared memory for elements of data structure"
" \"%s\" (%lu bytes requested)",
"PredTranList", (unsigned long) requestSize)));
- PredLockTranList->entryLimit = max_table_size; /* get from GUC ? */
- PredLockTranList->entryCount = 0; /* starts empty */
/* Add all elements to available list, clean. */
memset(PredLockTranList->element, 0, requestSize);
for (i = 0; i < max_table_size; i++)
&info,
hash_flags);
+ /*
+ * Allocate space for tracking rw-conflicts in lists attached to the
+ * transactions.
+ *
+ * TODO SSI: Assume an average of 5 conflicts per transaction.
+ * This is likely to need to be adjusted or configured by a GUC.
+ * Gotta start somewhere....
+ */
+ max_table_size *= 5;
+
+ RWConflictPool = ShmemInitStruct("RWConflictPool",
+ RWConflictPoolHeaderDataSize,
+ &found);
+ if (!found)
+ {
+ int i;
+
+ SHMQueueInit(&RWConflictPool->availableList);
+ requestSize = mul_size((Size) max_table_size,
+ PredTranListElementDataSize);
+ RWConflictPool->element = ShmemAlloc(requestSize);
+ if (RWConflictPool->element == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("not enough shared memory for elements of data structure"
+ " \"%s\" (%lu bytes requested)",
+ "RWConflictPool", (unsigned long) requestSize)));
+ /* Add all elements to available list, clean. */
+ memset(RWConflictPool->element, 0, requestSize);
+ for (i = 0; i < max_table_size; i++)
+ {
+ SHMQueueInsertBefore(&(RWConflictPool->availableList),
+ &(RWConflictPool->element[i].outLink));
+ }
+ }
+
/*
* Create or attach to the header for the list of finished serializable
* transactions.
/* Initialize the structure. */
sxact->tag = sxacttag;
- sxact->outConflict = InvalidSerializableXact;
- sxact->inConflict = InvalidSerializableXact;
+ SHMQueueInit(&(sxact->outConflicts));
+ SHMQueueInit(&(sxact->inConflicts));
sxact->topXid = GetTopTransactionIdIfAny();
sxact->finishedBefore = InvalidTransactionId;
sxact->xmin = snapshot->xmin;
SHMQueueInit(&(sxact->predicateLocks));
SHMQueueElemInit(&(sxact->finishedLink));
- sxact->rolledBack = false;
+ sxact->flags = 0;
LWLockRelease(SerializableXactHashLock);
MySerializableXact = sxact;
ReleasePredicateLocks(const bool isCommit)
{
bool needToClear;
+ RWConflict conflict, nextConflict;
if (MySerializableXact == InvalidSerializableXact)
{
/*
* If it's not a commit it's a rollback, and we can clear our locks
- * immediately. TODO SSI: Clear the locks, but leave the sxact record.
+ * immediately.
*/
if (!isCommit)
- MySerializableXact->rolledBack = true;
+ MySerializableXact->flags |= SXACT_FLAG_ROLLED_BACK;
/*
- * Add this to the list of transactions to check for later cleanup. First
- * turn pointers to already-terminated transactions to self-references.
+ * Release all outConflicts from committed transactions, but set
+ * SXACT_FLAG_CONFLICT_OUT if any exist.
+ * If we're rolling back clear them all.
*/
- if (MySerializableXact->inConflict != InvalidSerializableXact)
+ conflict = (RWConflict)
+ SHMQueueNext((SHM_QUEUE *) &MySerializableXact->outConflicts,
+ (SHM_QUEUE *) &MySerializableXact->outConflicts,
+ offsetof(RWConflictData, outLink));
+ if (conflict && !isCommit)
+ MySerializableXact->flags |= SXACT_FLAG_CONFLICT_OUT;
+ while (conflict)
{
- if (MySerializableXact->inConflict->rolledBack)
- MySerializableXact->inConflict = InvalidSerializableXact;
- else if (SxactIsCommitted(MySerializableXact->inConflict))
- MySerializableXact->inConflict = (SERIALIZABLEXACT *) MySerializableXact;
+ nextConflict = (RWConflict)
+ SHMQueueNext((SHM_QUEUE *) &MySerializableXact->outConflicts,
+ &conflict->outLink,
+ offsetof(RWConflictData, outLink));
+
+ if (!isCommit
+ || (conflict->sxactIn->flags & SXACT_FLAG_COMMITTED))
+ ReleaseRWConflict(conflict);
+
+ conflict = nextConflict;
}
- if (MySerializableXact->outConflict != InvalidSerializableXact)
+
+ /*
+ * Release all inConflicts from committed transactions.
+ * If we're rolling back, clear them all.
+ */
+ conflict = (RWConflict)
+ SHMQueueNext((SHM_QUEUE *) &MySerializableXact->inConflicts,
+ (SHM_QUEUE *) &MySerializableXact->inConflicts,
+ offsetof(RWConflictData, inLink));
+ while (conflict)
{
- if (MySerializableXact->outConflict->rolledBack)
- MySerializableXact->outConflict = InvalidSerializableXact;
- else if (SxactIsCommitted(MySerializableXact->outConflict))
- MySerializableXact->outConflict = (SERIALIZABLEXACT *) MySerializableXact;
+ nextConflict = (RWConflict)
+ SHMQueueNext((SHM_QUEUE *) &MySerializableXact->inConflicts,
+ &conflict->inLink,
+ offsetof(RWConflictData, inLink));
+
+ if (!isCommit
+ || (conflict->sxactOut->flags & SXACT_FLAG_COMMITTED))
+ ReleaseRWConflict(conflict);
+
+ conflict = nextConflict;
}
/* Add this to the list of transactions to check for later cleanup. */
{
PREDICATELOCK *predlock;
SERIALIZABLEXIDTAG sxidtag;
+ RWConflict conflict, nextConflict;
Assert(sxact != NULL);
- Assert(sxact->rolledBack || SxactIsCommitted(sxact));
+ Assert((sxact->flags & SXACT_FLAG_ROLLED_BACK) || SxactIsCommitted(sxact));
Assert(SxactIsOnFinishedList(sxact));
LWLockAcquire(SerializablePredicateLockListLock, LW_SHARED);
SHMQueueDelete(&(sxact->finishedLink));
- /* Get rid of the xid and the record of the transaction itself. */
sxidtag.xid = sxact->topXid;
LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
+
+ /* Release all outConflicts. */
+ conflict = (RWConflict)
+ SHMQueueNext((SHM_QUEUE *) &MySerializableXact->outConflicts,
+ (SHM_QUEUE *) &MySerializableXact->outConflicts,
+ offsetof(RWConflictData, outLink));
+ while (conflict)
+ {
+ nextConflict = (RWConflict)
+ SHMQueueNext((SHM_QUEUE *) &MySerializableXact->outConflicts,
+ &conflict->outLink,
+ offsetof(RWConflictData, outLink));
+ ReleaseRWConflict(conflict);
+ conflict = nextConflict;
+ }
+
+ /* Release all inConflicts. */
+ conflict = (RWConflict)
+ SHMQueueNext((SHM_QUEUE *) &MySerializableXact->inConflicts,
+ (SHM_QUEUE *) &MySerializableXact->inConflicts,
+ offsetof(RWConflictData, inLink));
+ while (conflict)
+ {
+ nextConflict = (RWConflict)
+ SHMQueueNext((SHM_QUEUE *) &MySerializableXact->inConflicts,
+ &conflict->inLink,
+ offsetof(RWConflictData, inLink));
+ ReleaseRWConflict(conflict);
+ conflict = nextConflict;
+ }
+
+ /* Get rid of the xid and the record of the transaction itself. */
if (sxidtag.xid != InvalidTransactionId)
hash_search(SerializableXidHash, &sxidtag, HASH_REMOVE, NULL);
ReleasePredTran(sxact);
+
LWLockRelease(SerializableXactHashLock);
}
return;
}
sxact = sxid->myXact;
- if (sxact == MySerializableXact || sxact->rolledBack)
+ if (sxact == MySerializableXact
+ || RWConflictExists((SERIALIZABLEXACT *) MySerializableXact, sxact))
{
/* We can't conflict with our own transaction or one rolled back. */
LWLockRelease(SerializableXactHashLock);
return;
}
- /*
- * If this is a read-only transaction and the writing transaction has
- * committed, and it doesn't have a rw-conflict out or has a conflict out
- * to a transaction which overlaps this transaction, then no conflict.
- */
- if (XactReadOnly
- && SxactIsCommitted(sxact)
- && (!TransactionIdIsValid(sxact->outConflict)
- || (sxact != sxact->outConflict
- && (!SxactIsCommitted(sxact->outConflict)
- || XidIsConcurrent(sxact->outConflict->topXid)))))
- {
- /* Read-only transaction will appear to run first. No conflict. */
- LWLockRelease(SerializableXactHashLock);
- return;
- }
+// /*
+// * If this is a read-only transaction and the writing transaction has
+// * committed, and it doesn't have a rw-conflict out or has a conflict out
+// * to a transaction which overlaps this transaction, then no conflict.
+// */
+// if (XactReadOnly
+// && SxactIsCommitted(sxact)
+// && (!TransactionIdIsValid(sxact->outConflict)
+// || (sxact != sxact->outConflict
+// && (!SxactIsCommitted(sxact->outConflict)
+// || XidIsConcurrent(sxact->outConflict->topXid)))))
+// {
+// /* Read-only transaction will appear to run first. No conflict. */
+// LWLockRelease(SerializableXactHashLock);
+// return;
+// }
LWLockRelease(SerializableXactHashLock);
}
}
}
- else if (!(sxact->rolledBack)
+ else if (!(sxact->flags & SXACT_FLAG_ROLLED_BACK)
&& (!SxactIsCommitted(sxact)
|| TransactionIdPrecedes(GetTransactionSnapshot()->xmin,
sxact->finishedBefore))
- && sxact->outConflict != MySerializableXact
- && MySerializableXact->inConflict != sxact)
+ && !RWConflictExists(sxact, (SERIALIZABLEXACT *) MySerializableXact))
{
LWLockRelease(SerializableXactHashLock);
LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
/*
* Flag a rw-dependency between two serializable transactions.
- * If a conflict field is invalid set it to the other transaction,
- * if it's already the other transaction leave it alone, otherwise
- * use self-reference (so we don't need to keep a list).
*
* The caller is responsible for ensuring that we have a LW lock on
* the transaction hash table.
OnConflict_CheckForSerializationFailure(reader, writer);
/* Actually do the conflict flagging. */
- if (writer->inConflict == InvalidSerializableXact
- || writer->inConflict->rolledBack)
- writer->inConflict = reader;
- else if (writer->inConflict != reader)
- writer->inConflict = writer;
- if (reader->outConflict == InvalidSerializableXact
- || reader->outConflict->rolledBack)
- reader->outConflict = writer;
- else if (reader->outConflict != writer)
- reader->outConflict = reader;
+ SetRWConflict(reader, writer);
}
/*
failure = false;
- if (writer->inConflict != reader
- && writer->outConflict != InvalidSerializableXact
- && !(writer->outConflict->rolledBack))
- {
- /* The writer is or is becoming a pivot. */
- /* Self-reference prevents checking commit sequence. */
- if (writer->outConflict == writer
+ if (!SHMQueueIsDetached((SHM_QUEUE *) &reader->inConflicts)
+ && (writer->flags & SXACT_FLAG_COMMITTED)
+ /* TODO SSI: check for READ ONLY special case */)
+ failure = true;
- /*
- * TODO SSI: Resolve this performance tweak issue.
- *
- * Back-and-forth reference is write skew; thus doomed; however,
- * rolling back here increases chances that a retry will still fail.
- * It may be better to let it happen at commit time. Only performance
- * testing can determine whether the next line should be used.
- *
- * Leaving it out would be *especially* valuable if the PreCommit
- * checking could be changed to allow a commit in a situation where it
- * is leaving another transaction in a state where a commit must fail
- * -- when the doomed transaction eventually tries to commit, it would
- * probably be at a time when an immediate retry is very likely to
- * succeed.
- */
- /* || writer->outConflict == reader */
- )
- failure = true;
- else if (SxactIsCommitted(writer->outConflict))
- {
- if (SxactCommittedBefore(writer->outConflict, writer)
- && SxactCommittedBefore(writer->outConflict, reader))
- /* The out side of the pivot committed first. */
- failure = true;
- }
- else
- {
- if (writer->outConflict->inConflict == writer->outConflict)
- /* Self-reference will prevent checking at commit. */
- failure = true;
- }
- }
-
- if (reader->outConflict != writer
- && reader->inConflict != InvalidSerializableXact
- && !(reader->inConflict->rolledBack))
- {
- /* The reader is or is becoming a pivot. */
- if (SxactIsCommitted(writer))
- {
- if (SxactCommittedBefore(writer, reader)
- && (reader->inConflict == reader
- || SxactCommittedBefore(writer, reader->inConflict)))
- /* The out side committed first, as far as we can tell. */
- failure = true;
- }
- else if (writer->inConflict != InvalidSerializableXact
- && writer->inConflict != reader)
- /* Self-reference will prevent checking at commit. */
- failure = true;
- }
+ if (!SHMQueueIsDetached((SHM_QUEUE *) &writer->outConflicts)
+ /* TODO SSI: check for commit in any of the out conflicts, and READ ONLY special case */)
+ failure = true;
if (failure)
ereport(ERROR,
*
* We're checking for a dangerous structure as each conflict is recorded.
* The only way we could have a problem at commit is if this is the "out"
- * side of a pivot, and neither the "in" side or the pivot itself has yet
+ * side of a pivot, and neither the "in" side nor the pivot has yet
* committed.
*/
void
PreCommit_CheckForSerializationFailure(void)
{
+ bool failure;
+ RWConflict nearConflict;
+
if (MySerializableXact == InvalidSerializableXact)
return;
Assert(IsolationIsSerializable());
- LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
+ failure = false;
+
+ /* TODO SSI: check whether another transaction has cancelled us? */
/*
- * Checking at conflict detection should only allow self-reference in if
- * this transaction is on the on the out side of a pivot, so
- * self-reference is OK here.
+ * TODO SSI: SHARED here and EXCLUSIVE below to modify? Would require
+ * new SerializableCommitLock for exclusive use around this method?
*/
- if (MySerializableXact->inConflict != InvalidSerializableXact
- && MySerializableXact->inConflict != MySerializableXact
- && !(MySerializableXact->inConflict->rolledBack)
- && MySerializableXact->inConflict->inConflict != InvalidSerializableXact
- && !SxactIsCommitted(MySerializableXact->inConflict)
- && !SxactIsCommitted(MySerializableXact->inConflict->inConflict))
+ LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
+
+ nearConflict = (RWConflict)
+ SHMQueueNext((SHM_QUEUE *) &MySerializableXact->inConflicts,
+ (SHM_QUEUE *) &MySerializableXact->inConflicts,
+ offsetof(RWConflictData, inLink));
+ while (nearConflict && !failure)
{
+ RWConflict farConflict;
+
+ if (!(nearConflict->sxactIn->flags & SXACT_FLAG_COMMITTED))
+ {
+ farConflict = (RWConflict)
+ SHMQueueNext(&nearConflict->sxactIn->inConflicts,
+ &nearConflict->sxactIn->inConflicts,
+ offsetof(RWConflictData, inLink));
+ while (farConflict && !failure)
+ {
+ if (!(farConflict->sxactIn->flags & SXACT_FLAG_COMMITTED))
+ failure = true;
+ else
+ farConflict = (RWConflict)
+ SHMQueueNext(&nearConflict->sxactIn->inConflicts,
+ &farConflict->inLink,
+ offsetof(RWConflictData, inLink));
+ }
+ }
+
+ nearConflict = (RWConflict)
+ SHMQueueNext((SHM_QUEUE *) &MySerializableXact->inConflicts,
+ &nearConflict->inLink,
+ offsetof(RWConflictData, inLink));
+ }
+
+ if (failure)
+ {
+ /* TODO SSI: cancel some *other* transaction(s) here, instead! */
MySerializableXact->finishedBefore = ShmemVariableCache->nextXid;
ereport(ERROR,
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
}
MySerializableXact->finishedBefore = ShmemVariableCache->nextXid;
+ MySerializableXact->flags |= SXACT_FLAG_COMMITTED;
+
LWLockRelease(SerializableXactHashLock);
}