/*
* 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.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.logging.Logger;
import jvstm.gc.GCTask;
import jvstm.gc.TxContext;
public abstract class Transaction {
// static part starts here
/*
* The mostRecentCommittedRecord static field is volatile to ensure correct
* synchronization among different threads:
*
* - A newly created transaction reads the value of this field at
* the very beginning of its existence, before trying to
* access any box.
*
* - A write transaction writes to this field at the very end,
* after commiting all the boxes to their new values.
*
* This way, because of the new semantics of the Java Memory
* Model, as specified by JSR133 (which is incorporated in the
* newest Java Language Specification), we know that all the
* values written previously in the commit of write transaction
* will be visible to any other transaction that is created with
* the new value of the committed field.
*
* This change is sufficient to ensure the correct synchronization
* guarantees, even if we remove all the remaining volatile
* declarations from the VBox and VBoxBody classes.
*/
public static volatile ActiveTransactionsRecord mostRecentCommittedRecord = ActiveTransactionsRecord.makeSentinelRecord();
protected static final ThreadLocal<Transaction> current = new ThreadLocal<Transaction>();
// a per thread TxContext
private static final ThreadLocal<TxContext> threadTxContext = new ThreadLocal<TxContext>() {
@Override protected TxContext initialValue() {
return Transaction.allTxContexts.enqueue(new TxContext(Thread.currentThread()));
}
};
// List of all tx contexts. The GC thread will iterate this list to GC any unused ActiveTxRecords.
public static TxContext allTxContexts = null;
public static final GCTask gcTask; // added by FMC for unit test purpose
static final String GC_PROP = "jvstm.gc.disabled";
static {
// initialize the allTxContexts
Transaction.allTxContexts = new TxContext(null);
// start the GC thread.
boolean gcDisabled = Boolean.getBoolean(GC_PROP);
Logger logger = Logger.getLogger("jvstm");
logger.info(String.format(
"********** GC vbodies = %b (disable/enable it in property %s)",
!gcDisabled,
GC_PROP));
gcTask = new GCTask(mostRecentCommittedRecord);
if(!gcDisabled){
Thread gc = new Thread(gcTask);
gc.setDaemon(true);
gc.start();
}
}
private static TransactionFactory TRANSACTION_FACTORY = new DefaultTransactionFactory();
public static void setTransactionFactory(TransactionFactory factory) {
TRANSACTION_FACTORY = factory;
}
public static Transaction current() {
return current.get();
}
public static TxContext context() {
return Transaction.threadTxContext.get();
}
// This method is called during the commit of a write transaction. Even though it is possible
// for more than one method to write to this slot at the same time, this could only cause a new
// transaction to see some record that might not be the most recent one. However, this is ok,
// because when a transactio begin it will check for another more recent record.
public static void setMostRecentCommittedRecord(ActiveTransactionsRecord record) {
mostRecentCommittedRecord = record;
}
public static void addTxQueueListener(TxQueueListener listener) {
ActiveTransactionsRecord.addListener(listener);
}
public static boolean isInTransaction() {
return current.get() != null;
}
public static ActiveTransactionsRecord getRecordForNewTransaction() {
ActiveTransactionsRecord rec = Transaction.mostRecentCommittedRecord;
TxContext ctx = threadTxContext.get();
ctx.oldestRequiredVersion = rec; // volatile write
while (true) {
while ((rec.getNext() != null) && (rec.getNext().isCommitted())) {
rec = rec.getNext();
}
if (rec != ctx.oldestRequiredVersion) {
// a more recent record exists, so backoff and try again with the new one
ctx.oldestRequiredVersion = rec; // volatile write
} else {
return rec;
}
}
}
/** Warning: this method has limited usability. See the UnsafeSingleThreaded class for
* details */
public static Transaction beginUnsafeSingleThreaded() {
Transaction parent = current.get();
if (parent != null) {
throw new Error("Unsafe single-threaded transactions cannot be nested");
}
ActiveTransactionsRecord activeRecord = getRecordForNewTransaction();
Transaction tx = new UnsafeSingleThreadedTransaction(activeRecord);
tx.start();
return tx;
}
public static Transaction beginInevitable() {
Transaction parent = current.get();
if (parent != null) {
throw new Error("Inevitable transactions cannot be nested");
}
ActiveTransactionsRecord activeRecord = getRecordForNewTransaction();
Transaction tx = new InevitableTransaction(activeRecord);
tx.start();
return tx;
}
public static Transaction begin() {
return begin(false);
}
public static Transaction begin(boolean readOnly) {
ActiveTransactionsRecord activeRecord = null;
Transaction parent = current();
if (TRANSACTION_FACTORY.reuseTopLevelReadOnlyTransactions() && parent == null && readOnly) {
Transaction tx = getRecordForNewTransaction().tx;
tx.start();
return tx;
}
if (parent == null) {
activeRecord = getRecordForNewTransaction();
}
return beginWithActiveRecord(readOnly, activeRecord);
}
// activeRecord may be null, iff the parent is also null, in which case activeRecord is not used, so it's ok!
protected static Transaction beginWithActiveRecord(boolean readOnly, ActiveTransactionsRecord activeRecord) {
Transaction parent = current.get();
Transaction tx = null;
if (parent == null) {
if (readOnly) {
tx = TRANSACTION_FACTORY.makeReadOnlyTopLevelTransaction(activeRecord);
} else {
tx = TRANSACTION_FACTORY.makeTopLevelTransaction(activeRecord);
}
} else {
// passing the readOnly parameter to makeNestedTransaction is a temporary solution to
// support the correct semantics in the composition of @Atomic annotations. Ideally, we
// should adjust the code generation of @Atomic to let WriteOnReadExceptions pass to the
// parent
tx = parent.makeNestedTransaction(readOnly);
}
tx.start();
return tx;
}
/* When this method succeeds, it commits the current transaction and begins another in a consistent state with the
* previous transaction, i.e., if the committing transaction is read-only, then the next transaction begins in the
* same version that the read-only transaction used; if the committing transaction is read-write and the commit
* succeeds, the new transaction begins in the version that the read-write transaction produced.
*/
public static Transaction commitAndBegin(boolean readOnly) {
Transaction tx = current.get();
return tx.commitAndBeginTx(readOnly);
}
public static Transaction beginParallelNested(boolean readOnly) {
Transaction parent = current.get();
Transaction tx = parent.makeParallelNestedTransaction(readOnly);
tx.start();
return tx;
}
public static Transaction beginUnsafeMultithreaded() {
Transaction parent = current.get();
Transaction tx = parent.makeUnsafeMultithreaded();
tx.start();
return tx;
}
public static void initThreadPool(int numberThreads) {
nestedParPool = Executors.newFixedThreadPool(numberThreads, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}
});
}
public static void initCachedThreadPool() {
nestedParPool = Executors.newCachedThreadPool(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}
});
}
protected static ExecutorService nestedParPool = Executors.newFixedThreadPool(Runtime.getRuntime()
.availableProcessors() * 2, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}
});
public <E> List<E> manageNestedParallelTxs(List<? extends TransactionalTask<E>> callables) {
return manageNestedParallelTxs(callables, nestedParPool);
}
protected boolean flattenNested = false;
public <E> List<E> manageNestedParallelTxs(List<? extends TransactionalTask<E>> callables, ExecutorService threadPool) {
List<E> results = new ArrayList<E>();
if (flattenNested) {
try {
for (TransactionalTask<E> callable : callables) {
results.add((callable).execute());
}
} catch (Throwable t) {
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
t.printStackTrace();
System.exit(0);
return null;
}
}
return results;
}
List<Future<E>> futures = null;
List<TransactionalTask<E>> repeatList = null;
boolean finishedCorrectly = false;
try {
futures = threadPool.invokeAll(callables);
int futureLength = futures.size();
for (int i = 0; i < futureLength; i++) {
try {
results.add(futures.get(i).get());
} catch (ExecutionException ee) {
Throwable t = ee.getCause();
if (t instanceof ExecuteParallelNestedTxSequentiallyException) {
if (repeatList == null) {
repeatList = new ArrayList<TransactionalTask<E>>();
}
repeatList.add(callables.get(i));
} else if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new RuntimeException(ee);
}
}
}
finishedCorrectly = true;
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
if (!finishedCorrectly) {
for (Future<E> future : futures) {
try {
future.get();
} catch (InterruptedException e) {
} catch (ExecutionException e) {
}
}
} else if (repeatList != null) {
flattenNested = true;
try {
for (TransactionalTask<E> callable : repeatList) {
results.add(callable.execute());
}
} catch (Throwable t) {
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new RuntimeException(t);
}
}
flattenNested = false;
}
}
return results;
}
public static void abort() {
Transaction tx = current.get();
tx.abortTx();
}
public static void commit() {
Transaction tx = current.get();
tx.commitTx(true);
}
public static void checkpoint() {
Transaction tx = current.get();
tx.commitTx(false);
}
public static SuspendedTransaction suspend() {
return current.get().suspendTx();
}
public static Transaction resume(SuspendedTransaction suspendedTx) {
if (current.get() != null) {
throw new ResumeException("Can't resume a transaction into a thread with an active transaction already");
}
// In the previous lines I'm checking that the current thread
// has no transaction, because, otherwise, we would lose the
// current transaction.
//
// Likewise, I should not allow that the same transaction is
// associated with more than one thread. For that, however, I
// would have to keep track of which thread owns each
// transaction and change that atomically. I recall having
// the thread in each transaction but I removed sometime ago.
// So, until I investigate this further, whoever is using this
// resume stuff must be carefull, because the system will not
// detect that the same transaction is being used in two
// different threads.
TxContext currentTxContext = context();
currentTxContext.oldestRequiredVersion = suspendedTx.txContext.oldestRequiredVersion;
/* NOTE: we CANNOT set
* suspendedTx.txContext.oldestRequiredVersion = null;
*
* Doing so would allow the GCTask to miss out on this version, by seeing null in the oldestRequiredVersion of
* both the currentTxContext and the suspendedTx.txContext. The TxContext in suspendedTx will be removed from
* the list when the now resumed transaction becomes GCed.
*/
// set the transaction in this thread
current.set(suspendedTx.theTx);
// return the resumed transaction
return suspendedTx.theTx;
}
// the transaction version that is used to read boxes. Must always represent a consistent state
// of the world
protected int number;
protected final Transaction parent;
/*
* This orec identifies newly created objects by this transaction.
* We say these objects are in captured memory corresponding to memory
* allocated inside a transaction that cannot escape (i.e., is captured
* by) its allocating transaction.
* Later and before performing an STM barrier we can check if an object is
* in captured memory and in that case we can read or update it in place
* avoiding the STM barrier.
*/
public final OwnershipRecord orecForNewObjects = new OwnershipRecord();
public Transaction(Transaction parent, int number) {
this.parent = parent;
this.number = number;
}
public Transaction(int number) {
this(null, number);
}
public Transaction(Transaction parent) {
this(parent, parent.getNumber());
}
public void start() {
current.set(this);
}
protected Transaction getParent() {
return parent;
}
public int getNumber() {
return number;
}
protected void setNumber(int number) {
this.number = number;
}
public void abortTx() {
finishTx();
}
public void commitTx(boolean finishAlso) {
doCommit();
if (finishAlso) {
finishTx();
}
}
private void finishTx() {
finish();
current.set(this.getParent());
}
protected void finish() {
// intentionally empty
}
public SuspendedTransaction suspendTx() {
// remove the transaction from the thread
current.set(null);
TxContext newTxContext = new TxContext(this);
// create a new SuspendedTransaction holding the transaction and its context.
SuspendedTransaction suspendedTx = new SuspendedTransaction(this, newTxContext);
// enqueue the new TxContext to hold the transaction's required record
Transaction.allTxContexts.enqueue(newTxContext);
TxContext currentTxContext = context();
// the order is important! We must not let go of the required version, so we set it ahead before clearing it in
// the current context
newTxContext.oldestRequiredVersion = currentTxContext.oldestRequiredVersion;
currentTxContext.oldestRequiredVersion = null;
return suspendedTx;
// the currentTxContext is left to be reused by this thread
}
protected abstract Transaction commitAndBeginTx(boolean readOnly);
public abstract Transaction makeNestedTransaction(boolean readOnly);
public abstract <T> T getBoxValue(VBox<T> vbox);
public abstract <T> void setBoxValue(VBox<T> vbox, T value);
public abstract <T> T getPerTxValue(PerTxBox<T> box, T initial);
public abstract <T> void setPerTxValue(PerTxBox<T> box, T value);
public abstract <T> T getArrayValue(VArrayEntry<T> entry);
public abstract <T> void setArrayValue(VArrayEntry<T> entry, T value);
protected abstract void doCommit();
public abstract Transaction makeUnsafeMultithreaded();
public abstract Transaction makeParallelNestedTransaction(boolean readOnly);
public abstract boolean isWriteTransaction();
public static void transactionallyDo(TransactionalCommand command) {
while (true) {
Transaction tx = Transaction.begin();
try {
command.doIt();
tx.commit();
tx = null;
return;
} catch (CommitException ce) {
tx.abort();
tx = null;
} finally {
if (tx != null) {
tx.abort();
}
}
}
}
public static <T> T doIt(Callable<T> xaction) throws Exception {
return doIt(xaction, false);
}
public static <T> T doIt(Callable<T> xaction, boolean tryReadOnly) throws Exception {
T result = null;
while (true) {
Transaction.begin(tryReadOnly);
boolean finished = false;
try {
result = xaction.call();
Transaction.commit();
finished = true;
return result;
} catch (CommitException ce) {
Transaction.abort();
finished = true;
} catch (WriteOnReadException wore) {
Transaction.abort();
finished = true;
tryReadOnly = false;
} finally {
if (! finished) {
Transaction.abort();
}
}
}
}
}