/*
* JVSTM: a Java library for Software Transactional Memory
* Copyright (C) 2005 INESC-ID Software Engineering Group
* http://www.esw.inesc-id.pt
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* Author's contact:
* INESC-ID Software Engineering Group
* Rua Alves Redol 9
* 1000 - 029 Lisboa
* Portugal
*/
package jvstm;
import java.util.Map;
public class TopLevelTransaction extends ReadWriteTransaction {
protected ActiveTransactionsRecord activeTxRecord;
// this record is created when the transaction starts to commit. It marks
// the transaction's
// commit order in the active transactions queue
protected ActiveTransactionsRecord commitTxRecord;
public TopLevelTransaction(ActiveTransactionsRecord activeRecord) {
super(activeRecord.transactionNumber);
this.activeTxRecord = activeRecord;
}
public ActiveTransactionsRecord getActiveTxRecord() {
return this.activeTxRecord;
}
public void setCommitTxRecord(ActiveTransactionsRecord record) {
this.commitTxRecord = record;
}
public ActiveTransactionsRecord getCommitTxRecord() {
return this.commitTxRecord;
}
@Override
public Transaction makeUnsafeMultithreaded() {
return new UnsafeParallelTransaction(this);
}
@Override
protected Transaction commitAndBeginTx(boolean readOnly) {
context().inCommitAndBegin = true;
try {
commitTx(true);
} finally {
context().inCommitAndBegin = false;
}
// at this point activeTxRecord and commitTxRecord are the same...
return Transaction.beginWithActiveRecord(readOnly, this.activeTxRecord);
}
@Override
protected void finish() {
super.finish();
if (!context().inCommitAndBegin) {
context().oldestRequiredVersion = null;
}
}
/*
* Upgrades this transaction's valid read state. It only makes sense to
* invoke this method with a newRecord in the committed state.
*/
protected void upgradeTx(ActiveTransactionsRecord newRecord) {
context().oldestRequiredVersion = newRecord;
this.activeTxRecord = newRecord;
setNumber(newRecord.transactionNumber);
}
public WriteSet makeWriteSet() {
return new WriteSet(this);
}
private ProcessPerTxBoxesTransaction speculatePerTxBoxes(int maxVersion) {
if (this.perTxValues == EMPTY_MAP) {
return ProcessPerTxBoxesTransaction.EMPTY_COMMIT_TX;
}
ProcessPerTxBoxesTransaction commitTx = new ProcessPerTxBoxesTransaction(maxVersion, this);
for (Map.Entry<PerTxBox, Object> entry : this.perTxValues.entrySet()) {
entry.getKey().commit(entry.getValue());
}
commitTx.finishExecution();
return commitTx;
}
/*
* validate this transaction and afterwards try to enqueue its commit
* request with a compare-and-swap. If the CAS doesn't succeed, then it's
* because other transaction(s) got ahead and entered the commit queue, so
* we also have to validate against that(those) transaction(s) before
* re-attempting the CAS.
*
* // * If validation suceeds the transaction is upgraded to the latest
* valid read state (which is // * the record previous to the
* commitTxRecord)
*/
protected void validateCommitAndEnqueue(ActiveTransactionsRecord lastCheck) {
ProcessPerTxBoxesTransaction commitTx = speculatePerTxBoxes(lastCheck.transactionNumber);
WriteSet writeSet = makeWriteSet();
writeSet.addPerTxBoxesWrites(commitTx.specWriteSet);
assignCommitRecord(lastCheck.transactionNumber + 1, writeSet);
enqueueValidCommit(lastCheck, writeSet);
// At this point we no longer need the values we wrote in the VBox's
// tempValue slot, so we update the ownership record's version to
// allow reuse of the slot.
updateOrecVersion();
// after validating, upgrade the transaction's valid read state
// upgradeTx(lastValid);
}
protected void assignCommitRecord(int txNumber, WriteSet writeSet) {
setCommitTxRecord(new ActiveTransactionsRecord(txNumber, writeSet));
}
/**
* Enqueue a valid commit (just after the record lastCheck). If enqueue fails then, revalidate, upgrade the transaction and retry to enqueue.
*
* @param lastCheck The last record up to where validation succeeded.
* @param writeSet The writeSet of this commit.
* @return
*/
/* This code was extracted from validateCommitAndEnqueue, to enable overriding
it in subclasses that wish to reuse the remainder of the algorithm coded in
validateCommitAndEnqueue. */
protected void enqueueValidCommit(ActiveTransactionsRecord lastCheck, WriteSet writeSet) {
ProcessPerTxBoxesTransaction commitTx;
while (!lastCheck.trySetNext(getCommitTxRecord())) {
// Failed enqueue, at least some other transaction succeeded in the meantime
lastCheck = helpCommitAll();
snapshotValidation(lastCheck.transactionNumber);
// Re-execute the perTxBoxes speculatively. They are supposed to be a point of contention, thus
// any validation to check if previous speculative reads are still up-to-date should most of the time
// lead to the conclusion that they are not. This way we avoid registering those reads and skip the validation.
commitTx = speculatePerTxBoxes(lastCheck.transactionNumber);
writeSet.addPerTxBoxesWrites(commitTx.specWriteSet);
assignCommitRecord(lastCheck.transactionNumber + 1, writeSet);
}
}
/**
* Update the ownership record's version
*/
public void updateOrecVersion() {
this.orec.version = getCommitTxRecord().transactionNumber;
for (OwnershipRecord mergedOrec : linearNestedOrecs) {
mergedOrec.version = getCommitTxRecord().transactionNumber;
}
for (ParallelNestedTransaction tx : mergedTxs) {
tx.orec.version = getCommitTxRecord().transactionNumber;
}
}
/**
* Validates this read-set against all active transaction records more recent that the one
* <code>lastChecked</code>.
*
* @return The last successfully validated ActiveTransactionsRecord
* @throws CommitException if the validation fails
*/
protected ActiveTransactionsRecord validate(ActiveTransactionsRecord startCheck) {
ActiveTransactionsRecord lastChecked = startCheck;
ActiveTransactionsRecord recordToCheck = lastChecked.getNext();
while (recordToCheck != null) {
lastChecked = recordToCheck;
recordToCheck = recordToCheck.getNext();
}
if (lastChecked != startCheck) {
helpCommitAll();
snapshotValidation(lastChecked.transactionNumber);
}
return lastChecked;
}
@Override
protected void tryCommit() {
if (isWriteTransaction()) {
validate();
ensureCommitStatus();
upgradeTx(getCommitTxRecord());
}
}
// this may be heavier than simply doing the new validateAndEnqueue when
// running on a small
// number of cores. We might improve by using an adaptive validation that
// only used the more
// complex solution whenever relevant. Idea for relevant: compare "the
// average write-set size
// multiplied by the distance between activeTx # and last enqueued #" with
// the read-set size.
protected void validate() {
ActiveTransactionsRecord lastSeenCommitted = helpCommitAll();
// if (isSnapshotValidationWorthIt(lastSeenCommitted)) {
// this validates up to the last seen committed at least
snapshotValidation(lastSeenCommitted.transactionNumber);
validateCommitAndEnqueue(lastSeenCommitted);
// } else {
// validateCommitAndEnqueue(this.activeTxRecord);
// }
}
// when the ratio between writes and reads (to validate) is greater than
// WR_THRESHOLD, then we
// assume that snapshotValidation is worth executing
private static float WR_THRESHOLD = 0.5f;
protected boolean isSnapshotValidationWorthIt(ActiveTransactionsRecord lastRecord) {
if (this.bodiesRead.isEmpty()) {
return false;
}
int numberOfReadsToCheck = this.bodiesRead.first().length - (next + 1);
// if there are more arrays the rest are full, for sure
for (VBox[] array : bodiesRead.rest()) {
numberOfReadsToCheck += array.length;
}
int numberOfWritesToCheck = 0;
for (ActiveTransactionsRecord rec = this.activeTxRecord.getNext(); rec != null; rec = rec.getNext()) {
numberOfWritesToCheck += rec.getWriteSet().size();
}
return ((float) numberOfWritesToCheck) / numberOfReadsToCheck > WR_THRESHOLD;
}
protected ActiveTransactionsRecord helpCommitAll() {
ActiveTransactionsRecord lastSeenCommitted = Transaction.mostRecentCommittedRecord;
ActiveTransactionsRecord recordToCommit = lastSeenCommitted.getNext();
while (recordToCommit != null) {
helpCommit(recordToCommit);
lastSeenCommitted = recordToCommit;
recordToCommit = recordToCommit.getNext();
}
return lastSeenCommitted;
}
protected void ensureCommitStatus() {
ActiveTransactionsRecord recordToCommit = Transaction.mostRecentCommittedRecord.getNext();
while ((recordToCommit != null) && (recordToCommit.transactionNumber <= getCommitTxRecord().transactionNumber)) {
helpCommit(recordToCommit);
recordToCommit = recordToCommit.getNext();
}
}
/**
* Help to commit a transaction as much as possible.
*
* @param recordToCommit
* the record to help commit
*/
protected void helpCommit(ActiveTransactionsRecord recordToCommit) {
if (!recordToCommit.isCommitted()) {
// We must check whether recordToCommit.getWriteSet() could, in the
// meanwhile, have
// become null. This occurs when this recordToCommit was already
// committed and even
// cleaned while this thread was waiting to be scheduled
WriteSet writeSet = recordToCommit.getWriteSet();
if (writeSet != null) {
writeSet.helpWriteBack(recordToCommit.transactionNumber);
// the thread that commits the last body will handle the rest of
// the commit
finishCommit(recordToCommit);
}
}
}
protected void finishCommit(ActiveTransactionsRecord recordToCommit) {
// we only advance the most recent committed record if we don't see this
// transaction already committed
if (!recordToCommit.isCommitted()) {
recordToCommit.setCommitted();
Transaction.setMostRecentCommittedRecord(recordToCommit);
}
}
}