/*
* 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 static jvstm.UtilUnsafe.UNSAFE;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Map;
import jvstm.util.Cons;
public abstract class ReadWriteTransaction extends Transaction {
protected static final Object NULL_VALUE = new Object();
protected static final int[] EMPTY_VERSIONS = new int[0];
protected static final VBox[] EMPTY_WRITE_SET = new VBox[0];
protected static final Map EMPTY_MAP = Collections.emptyMap();
protected static final ThreadLocal<Cons<VBox[]>> pool = new ThreadLocal<Cons<VBox[]>>() {
@Override
public Cons<VBox[]> initialValue() {
return Cons.empty();
}
};
protected static void returnToPool(VBox[] array) {
pool.set(pool.get().cons(array));
}
private static VBox[] borrowFromPool() {
Cons<VBox[]> available = pool.get();
if (available.isEmpty()) {
VBox[] newArray = new VBox[1000];
return newArray;
} else {
pool.set(available.rest());
return available.first();
}
}
protected Cons<VBox[]> bodiesRead = Cons.empty();
protected Cons<VArrayEntry<?>> arraysRead = Cons.empty();
protected int next = -1;
protected Map<VBox, Object> boxesWritten = EMPTY_MAP;
protected Cons<VBox> boxesWrittenInPlace = Cons.empty();
protected Map<PerTxBox, Object> perTxValues = EMPTY_MAP;
protected Map<VArrayEntry<?>, VArrayEntry<?>> arrayWrites = EMPTY_MAP;
protected Map<VArray<?>, Integer> arrayWritesCount = EMPTY_MAP;
protected OwnershipRecord orec = new OwnershipRecord(this);
public Cons<ParallelNestedTransaction> mergedTxs = Cons.empty();
protected Cons<OwnershipRecord> linearNestedOrecs = Cons.empty();
protected int[] ancVersions;
protected volatile NestedCommitRecord nestedCommitQueue = new NestedCommitRecord();
public ReadWriteTransaction(int number) {
super(number);
this.ancVersions = EMPTY_VERSIONS;
}
public ReadWriteTransaction(ReadWriteTransaction parent) {
super(parent);
}
@Override
public Transaction makeNestedTransaction(boolean readOnly) {
// always create a RW nested transaction, because we need its read-set
return new NestedTransaction(this);
}
@Override
public Transaction makeParallelNestedTransaction(boolean readOnly) {
if (readOnly) {
return new ParallelNestedReadOnlyTransaction(this);
} else {
return new ParallelNestedTransaction(this);
}
}
ReadWriteTransaction getRWParent() {
return (ReadWriteTransaction) getParent();
}
@Override
public void abortTx() {
this.orec.version = OwnershipRecord.ABORTED;
for (OwnershipRecord linearMergedOrec : linearNestedOrecs) {
linearMergedOrec.version = OwnershipRecord.ABORTED;
}
for (ParallelNestedTransaction mergedTx : mergedTxs) {
mergedTx.orec.version = OwnershipRecord.ABORTED;
}
super.abortTx();
}
@Override
protected void finish() {
super.finish();
for (VBox[] array : bodiesRead) {
returnToPool(array);
}
// to allow garbage collecting the collections
bodiesRead = null;
arraysRead = null;
boxesWritten = null;
boxesWrittenInPlace = null;
perTxValues = null;
arrayWrites = null;
arrayWritesCount = null;
cleanUp();
}
protected void cleanUp() {
if (mergedTxs != Cons.<ParallelNestedTransaction> empty()) {
for (ParallelNestedTransaction mergedTx : mergedTxs) {
mergedTx.cleanUp();
}
mergedTxs = null;
}
}
@Override
protected void doCommit() {
tryCommit();
// if commit is successful, then reset transaction to a clean state
for (VBox[] array : bodiesRead) {
returnToPool(array);
}
bodiesRead = Cons.empty();
arraysRead = Cons.empty();
boxesWritten = EMPTY_MAP;
boxesWrittenInPlace = Cons.empty();
perTxValues = EMPTY_MAP;
arrayWrites = EMPTY_MAP;
arrayWritesCount = EMPTY_MAP;
}
protected abstract void tryCommit();
protected <T> T getLocalValue(VBox<T> vbox) {
InplaceWrite<T> inplace = vbox.inplace;
if (inplace.orec.owner == this) {
return inplace.tempValue;
}
T value = null;
if (boxesWritten != EMPTY_MAP) {
value = (T)boxesWritten.get(vbox);
}
if ((value == null) && (parent != null)) {
value = getRWParent().getLocalValue(vbox);
}
return value;
}
protected <T> T readFromBody(VBox<T> vbox) {
VBoxBody<T> body = vbox.body;
if (body!= null && body.version > number) {
body = newerVersionDetected(body);
}
addToReadSet(vbox);
if (body == null) {
/*
* The object (vbox) is in the compact layout (of the AOM) and the
* vbox itself corresponds to the most recent committed version.
*/
return (T) vbox;
} else {
return getValueFromBody(vbox, body);
}
}
protected <T> VBoxBody<T> newerVersionDetected(VBoxBody<T> body) {
// signal early transaction abort
TransactionSignaller.SIGNALLER.signalEarlyAbort();
throw new AssertionError("Impossible condition - Commit fail signalled!");
}
protected <T> void addToReadSet(VBox<T> vbox) {
VBox[] readset = null;
if (next < 0) {
readset = borrowFromPool();
next = readset.length - 1;
bodiesRead = bodiesRead.cons(readset);
} else {
readset = bodiesRead.first();
}
readset[next--] = vbox;
}
// The vbox argument is needed in subclasses
protected <T> T getValueFromBody(VBox<T> vbox, VBoxBody<T> body) {
return body.value;
}
@Override
public <T> T getBoxValue(VBox<T> vbox) {
/*
* When either no one has written to this vbox or any committed writer
* is not older than my version we know that this transaction (as well
* as any parent) does not have a local value. In this case we read
* directly from the vbox's body.
*/
OwnershipRecord currentOwner = vbox.inplace.orec;
if (currentOwner.version > 0 && currentOwner.version <= this.number) {
return readFromBody(vbox);
} else {
T value = getLocalValue(vbox);
if (value == null) { // no local value exists
return readFromBody(vbox);
}
// else
return (value == NULL_VALUE) ? null : value;
}
}
@Override
public <T> void setBoxValue(VBox<T> vbox, T value) {
InplaceWrite<T> inplaceWrite = vbox.inplace;
OwnershipRecord currentOwner = inplaceWrite.orec;
if (currentOwner.owner == this) { // we are already the current writer
inplaceWrite.tempValue = (value == null ? (T) NULL_VALUE : value);
return;
}
// the next loop ends either when we succeed in writing directly to the
// vbox or fallback to using the standard
// write-set
do {
/*
* When there is no previous writer or the previous writer either is
* aborted or committed with a version not greater than ours we try
* to gain ownership of the vbox. If we succeed we write to the vbox
* otherwise we retry.
*
* Otherwise (there is a previous writer still running or committed
* in a version greater than ours) we fallback to the standard
* write-set
*/
if (currentOwner.version != 0 && currentOwner.version <= this.number) {
if (inplaceWrite.CASowner(currentOwner, this.orec)) {
// note: it is possible that a second invocation of
// setBoxValue in the same transaction will end up
// here after writing to the normal write-set. This case is
// accounted for when creating the
// WriteSet at commit time
inplaceWrite.tempValue = (value == null ? (T) NULL_VALUE : value);
boxesWrittenInPlace = boxesWrittenInPlace.cons(vbox);
return; // break
} else {
// update the current owner and retry
currentOwner = inplaceWrite.orec;
continue;
}
} else { // fallback to the standard write-set
// note: here we could consider the special case when the
// other writer is committed with a version
// greater than ours and either abort or try to upgrade the
// transaction
if (boxesWritten == EMPTY_MAP) {
boxesWritten = new IdentityHashMap<VBox, Object>();
}
boxesWritten.put(vbox, value == null ? NULL_VALUE : value);
return; // break
}
} while (true);
}
protected <T> T getPerTxValue(PerTxBox<T> box) {
T value = null;
if (perTxValues != EMPTY_MAP) {
value = (T) perTxValues.get(box);
}
if ((value == null) && (parent != null)) {
value = getRWParent().getPerTxValue(box);
}
return value;
}
@Override
public <T> T getPerTxValue(PerTxBox<T> box, T initial) {
T value = getPerTxValue(box);
if (value == null) {
value = initial;
}
return value;
}
@Override
public <T> void setPerTxValue(PerTxBox<T> box, T value) {
if (perTxValues == EMPTY_MAP) {
perTxValues = new IdentityHashMap<PerTxBox, Object>();
}
perTxValues.put(box, value);
}
protected <T> T getLocalArrayValue(VArrayEntry<T> entry) {
T value = null;
if (arrayWrites != EMPTY_MAP) {
VArrayEntry<T> wsEntry = (VArrayEntry<T>) arrayWrites.get(entry);
if (wsEntry != null) {
value = (wsEntry.getWriteValue() == null ? (T) NULL_VALUE : wsEntry.getWriteValue());
}
}
if ((value == null) && (parent != null)) {
value = getRWParent().getLocalArrayValue(entry);
}
return value;
}
@Override
public <T> T getArrayValue(VArrayEntry<T> entry) {
T value = getLocalArrayValue(entry);
if (value == null) {
value = entry.getValue(number);
arraysRead = arraysRead.cons(entry);
}
return (value == NULL_VALUE) ? null : value;
}
@Override
public <T> void setArrayValue(VArrayEntry<T> entry, T value) {
if (arrayWrites == EMPTY_MAP) {
arrayWrites = new IdentityHashMap<VArrayEntry<?>, VArrayEntry<?>>();
arrayWritesCount = new IdentityHashMap<VArray<?>, Integer>();
}
entry.setWriteValue(value, this.nestedCommitQueue.commitNumber);
if (arrayWrites.put(entry, entry) != null) {
return;
}
// Count number of writes to the array
Integer writeCount = arrayWritesCount.get(entry.array);
if (writeCount == null) {
writeCount = 0;
}
arrayWritesCount.put(entry.array, writeCount + 1);
}
protected void snapshotValidation(int lastSeenCommittedTxNumber) {
if (lastSeenCommittedTxNumber == getNumber()) {
return;
}
int myNumber = getNumber();
if (!this.bodiesRead.isEmpty()) {
// the first may not be full
VBox[] array = bodiesRead.first();
for (int i = next + 1; i < array.length; i++) {
VBoxBody body = array[i].body;
if (body != null && body.version > myNumber) {
TransactionSignaller.SIGNALLER.signalCommitFail();
}
}
// the rest are full
for (VBox[] ar : bodiesRead.rest()) {
for (int i = 0; i < ar.length; i++) {
VBoxBody body = ar[i].body;
if (body != null && body.version > myNumber) {
TransactionSignaller.SIGNALLER.signalCommitFail();
}
}
}
}
for (ParallelNestedTransaction mergedTx : mergedTxs) {
if (!mergedTx.globalReads.isEmpty()) {
// the first may not be full
VBox[] array = mergedTx.globalReads.first().entries;
for (int i = mergedTx.next + 1; i < array.length; i++) {
VBoxBody body = array[i].body;
if (body != null && body.version > myNumber) {
TransactionSignaller.SIGNALLER.signalCommitFail();
}
}
// the rest are full
for (ReadBlock block : mergedTx.globalReads.rest()) {
array = block.entries;
for (int i = 0; i < array.length; i++) {
VBoxBody body = array[i].body;
if (body != null && body.version > myNumber) {
TransactionSignaller.SIGNALLER.signalCommitFail();
}
}
}
}
}
// VArray
for (VArrayEntry<?> entry : arraysRead) {
if (!entry.validate()) {
TransactionSignaller.SIGNALLER.signalCommitFail();
}
}
}
@Override
public boolean isWriteTransaction() {
Cons<ParallelNestedTransaction> emptyCons = Cons.<ParallelNestedTransaction> empty();
return (mergedTxs != emptyCons) || (!boxesWritten.isEmpty()) || (!boxesWrittenInPlace.isEmpty())
|| (!arrayWrites.isEmpty()) || (perTxValues != null && !perTxValues.isEmpty());
}
/**
* Due to the JVSTM integration in Deuce, we must keep in a separate
* class all constants initialized with Unasfe operations, otherwise
* the JVM may crash on the bootstrap when it loads a transactional
* class.
*/
private static class Offsets {
// --- Setup to use Unsafe
private static final long mergedTxsOffset = UtilUnsafe.objectFieldOffset(ReadWriteTransaction.class, "mergedTxs");
}
protected boolean CASmergedTxs(Cons<ParallelNestedTransaction> expectedMergedTxs, Cons<ParallelNestedTransaction> newMergedTxs) {
return UNSAFE.compareAndSwapObject(this, Offsets.mergedTxsOffset, expectedMergedTxs, newMergedTxs);
}
}