/*
* 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.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import jvstm.util.Cons;
/**
* Parallel Nested Transaction is used to represent a part of a transaction that is
* running (potentially) in parallel with other subparts of the same
* transaction. The programmer is responsible for identifying the parts of a
* transaction that he wants to run concurrently. Consequently, those parts may
* not run in program order. The only guarantee is that their execution will be
* equivalent to some sequential order (plus the properties of opacity). If that
* guarantee is already provided by the disjoint accesses of each subpart,
* consider using UnsafeParallelTransaction.
*
* @author nmld
*
*/
public class ParallelNestedTransaction extends ReadWriteTransaction {
protected static final ExecuteParallelNestedTxSequentiallyException EXECUTE_SEQUENTIALLY_EXCEPTION = new ExecuteParallelNestedTxSequentiallyException();
protected ThreadLocal<AtomicInteger> blocksFree = new ThreadLocal<AtomicInteger>() {
@Override
protected AtomicInteger initialValue() {
return new AtomicInteger(0);
}
};
protected ThreadLocal<Cons<ReadBlock>> blocksPool = new ThreadLocal<Cons<ReadBlock>>() {
@Override
protected Cons<ReadBlock> initialValue() {
return Cons.empty();
}
};
protected Cons<ReadBlock> globalReads;
protected Map<VBox, InplaceWrite> nestedReads;
public ParallelNestedTransaction(ReadWriteTransaction parent) {
super(parent);
int[] parentVers = parent.ancVersions;
super.ancVersions = new int[parentVers.length + 1];
super.ancVersions[0] = parent.nestedCommitQueue.commitNumber;
for (int i = 0; i < parentVers.length; i++) {
this.ancVersions[i + 1] = parentVers[i];
}
this.nestedReads = new HashMap<VBox, InplaceWrite>();
this.globalReads = Cons.empty();
this.boxesWritten = parent.boxesWritten;
}
public ParallelNestedTransaction(ReadWriteTransaction parent, boolean multithreaded) {
super(parent);
super.ancVersions = EMPTY_VERSIONS;
this.nestedReads = ReadWriteTransaction.EMPTY_MAP;
this.globalReads = Cons.empty();
}
@Override
public Transaction makeUnsafeMultithreaded() {
throw new Error("An Unsafe Parallel Transaction may only be spawned by another Unsafe or a Top-Level transaction");
}
@Override
public Transaction makeNestedTransaction(boolean readOnly) {
throw new Error(
"A Parallel Nested Transaction cannot spawn a Linear Nested Transaction yet. Consider using a single Parallel Nested Transaction instead.");
}
@Override
protected Transaction commitAndBeginTx(boolean readOnly) {
commitTx(true);
return beginWithActiveRecord(readOnly, null);
}
// Returns -2 if self; -1 if not anc; >= 0 as version on anc otherwise
protected int retrieveAncestorVersion(Transaction tx) {
if (tx == this)
return -2;
int i = 0;
Transaction nextParent = parent;
while (nextParent != null) {
if (nextParent == tx) {
return ancVersions[i];
}
nextParent = nextParent.parent;
i++;
}
return -1;
}
private Transaction retrieveLowestCommonAncestor(Transaction tx) {
Transaction current = tx;
while (current != null) {
if (retrieveAncestorVersion(current) >= 0) {
return current;
}
current = current.parent;
}
return null;
}
@Override
public void abortTx() {
if (this.orec.version != OwnershipRecord.ABORTED) {
manualAbort();
}
Transaction.current.set(parent);
}
/* Removes the inplace writes of the transaction aborting if they
overwrote a previous inplace write of an ancestor. */
private void manualAbort() {
ReadWriteTransaction parent = getRWParent();
while (parent != null) {
for (ParallelNestedTransaction mergedIntoParent : parent.mergedTxs) {
for (VBox vboxMergedIntoParent : mergedIntoParent.boxesWrittenInPlace) {
revertOverwrite(vboxMergedIntoParent);
}
}
for (VBox vboxMergedIntoParent : parent.boxesWrittenInPlace) {
revertOverwrite(vboxMergedIntoParent);
}
parent = parent.getRWParent();
}
this.orec.version = OwnershipRecord.ABORTED;
for (ReadWriteTransaction child : mergedTxs) {
child.orec.version = OwnershipRecord.ABORTED;
}
super.boxesWritten = null;
int i = 0;
for (ReadBlock block : globalReads) {
block.free = true;
i++;
}
blocksFree.get().addAndGet(i);
this.globalReads = null;
this.nestedReads = null;
super.mergedTxs = null;
}
protected void revertOverwrite(VBox vboxWritten) {
InplaceWrite write = vboxWritten.inplace;
if (write.orec.owner != this) {
return;
}
InplaceWrite overwritten = write;
while (overwritten.next != null) {
overwritten = overwritten.next;
if (overwritten.orec.owner != this && overwritten.orec.version == OwnershipRecord.RUNNING) {
write.tempValue = overwritten.tempValue;
write.next = overwritten.next;
overwritten.orec.owner = overwritten.orec.owner; // enforce
// visibility
write.orec = overwritten.orec;
return;
}
}
}
protected <T> T readGlobal(VBox<T> vbox) {
VBoxBody<T> body = vbox.body;
if (body.version > number) {
TransactionSignaller.SIGNALLER.signalEarlyAbort();
}
ReadBlock readBlock = null;
if (next < 0) {
if (blocksFree.get().get() > 0) {
for (ReadBlock poolBlock : blocksPool.get()) {
if (poolBlock.free) {
poolBlock.free = false;
readBlock = poolBlock;
blocksFree.get().decrementAndGet();
break;
}
}
} else {
readBlock = new ReadBlock(blocksFree.get());
}
next = 999;
globalReads = globalReads.cons(readBlock);
} else {
readBlock = globalReads.first();
}
readBlock.entries[next--] = vbox;
return body.value;
}
@Override
public <T> T getBoxValue(VBox<T> vbox) {
InplaceWrite<T> inplaceWrite = vbox.inplace;
T value = inplaceWrite.tempValue;
OwnershipRecord inplaceOrec = inplaceWrite.orec;
if (inplaceOrec.version > 0 && inplaceOrec.version <= number) {
value = readGlobal(vbox);
return value;
}
do {
int entryNestedVersion = inplaceOrec.nestedVersion;
int versionOnAnc = retrieveAncestorVersion(inplaceOrec.owner);
if (versionOnAnc >= 0) {
if (entryNestedVersion > versionOnAnc) {
// eager w-r conflict, may restart immediately
manualAbort();
TransactionSignaller.SIGNALLER.signalCommitFail(inplaceOrec.owner);
}
nestedReads.put(vbox, inplaceWrite);
return (value == NULL_VALUE) ? null : value;
}
if (versionOnAnc == -2) {
return (value == NULL_VALUE) ? null : value;
}
inplaceWrite = inplaceWrite.next;
if (inplaceWrite == null) {
break;
}
value = inplaceWrite.tempValue;
inplaceOrec = inplaceWrite.orec;
} while (true);
if (boxesWritten != EMPTY_MAP) {
value = (T) boxesWritten.get(vbox);
if (value != null) {
return (value == NULL_VALUE) ? null : value;
}
}
value = readGlobal(vbox);
return value;
}
@Override
public <T> void setBoxValue(jvstm.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;
}
while (true) {
if (currentOwner.version != 0) {
if (currentOwner.version <= this.number) {
if (inplaceWrite.CASowner(currentOwner, this.orec)) {
inplaceWrite.tempValue = (value == null ? (T) NULL_VALUE : value);
boxesWrittenInPlace = boxesWrittenInPlace.cons(vbox);
return;
}
currentOwner = inplaceWrite.orec;
continue;
}
// more recent than my number
break;
} else {
if (retrieveAncestorVersion(currentOwner.owner) >= 0) {
if (vbox.CASinplace(inplaceWrite, new InplaceWrite<T>(this.orec, (value == null ? (T) NULL_VALUE : value),
inplaceWrite))) {
return;
}
inplaceWrite = vbox.inplace;
currentOwner = inplaceWrite.orec;
continue;
}
break;
}
}
manualAbort();
throw EXECUTE_SEQUENTIALLY_EXCEPTION;
}
/*
* VArrays:
* Here we ensure that the local array read over ancestors is consistent with concurrent nested commits
* This procedure is blocking, accordingly to the support provided to VArrays.
*/
@Override
protected <T> T getLocalArrayValue(VArrayEntry<T> entry) {
if (this.arrayWrites != EMPTY_MAP) {
VArrayEntry<T> wsEntry = (VArrayEntry<T>) this.arrayWrites.get(entry);
if (wsEntry != null) {
return (wsEntry.getWriteValue() == null ? (T) NULL_VALUE : wsEntry.getWriteValue());
}
}
ReadWriteTransaction iter = getRWParent();
while (iter != null) {
synchronized (iter) {
if (iter.arrayWrites != EMPTY_MAP) {
VArrayEntry<T> wsEntry = (VArrayEntry<T>) iter.arrayWrites.get(entry);
if (wsEntry == null) {
iter = iter.getRWParent();
continue;
}
if (wsEntry.nestedVersion <= retrieveAncestorVersion(iter)) {
this.arraysRead = this.arraysRead.cons(entry);
entry.setReadOwner(iter);
return (wsEntry.getWriteValue() == null ? (T) NULL_VALUE : wsEntry.getWriteValue());
} else {
TransactionSignaller.SIGNALLER.signalCommitFail(iter);
}
}
}
iter = iter.getRWParent();
}
return null;
}
/*
* Both parallel nested transactions and perTxBoxes may be seen as alternatives to work
* around inherently-conflicting workloads. An important question may be posed if we put
* them together: when should a perTxBox be committed, if write by a parallel nested
* transaction? Is it solving a conflict at top-level, or nested level of parallelism?
*
* Should the need for perTxBoxes arise in parNesting, that question shall have to be addressed.
*/
@Override
public <T> T getPerTxValue(PerTxBox<T> box, T initial) {
throw new RuntimeException("Parallel Nested Transactions do not support PerTxBoxes");
}
@Override
public <T> void setPerTxValue(PerTxBox<T> box, T value) {
throw new RuntimeException("Parallel Nested Transactions do not support PerTxBoxes");
}
@Override
protected void finish() {
boxesWritten = null;
perTxValues = null;
mergedTxs = null;
}
@Override
protected void doCommit() {
tryCommit();
boxesWritten = null;
perTxValues = EMPTY_MAP;
mergedTxs = null;
}
@Override
protected void cleanUp() {
boxesWrittenInPlace = null;
nestedReads = null;
for (ReadBlock block : globalReads) {
block.free = true;
block.freeBlocks.incrementAndGet();
}
globalReads = null;
}
protected NestedCommitRecord helpCommitAll(NestedCommitRecord start) {
NestedCommitRecord lastSeen = start;
NestedCommitRecord current = lastSeen.next.get();
while (current != null) {
if (!current.recordCommitted) {
current.helpCommit();
}
lastSeen = current;
current = current.next.get();
}
return lastSeen;
}
@Override
protected void tryCommit() {
ReadWriteTransaction parent = getRWParent();
NestedCommitRecord lastSeen;
NestedCommitRecord newCommit;
do {
lastSeen = helpCommitAll(parent.nestedCommitQueue);
snapshotValidation(lastSeen.commitNumber);
Cons<VArrayEntry<?>> varrayReadsToPropagate = validateNestedArrayReads();
newCommit = new NestedCommitRecord(this, this.mergedTxs, parent.mergedTxs, varrayReadsToPropagate, arrayWrites, arrayWritesCount, lastSeen.commitNumber + 1);
} while (!lastSeen.next.compareAndSet(null, newCommit));
lastSeen = parent.nestedCommitQueue;
while ((lastSeen != null) && (lastSeen.commitNumber <= newCommit.commitNumber)) {
if (!lastSeen.recordCommitted) {
lastSeen.helpCommit();
parent.nestedCommitQueue = lastSeen;
}
lastSeen = lastSeen.next.get();
}
}
@Override
protected void snapshotValidation(int lastSeenNumber) {
if (retrieveAncestorVersion(parent) == lastSeenNumber) {
return;
}
for (Map.Entry<VBox, InplaceWrite> read : nestedReads.entrySet()) {
validateNestedRead(read);
}
for (ParallelNestedTransaction mergedTx : mergedTxs) {
for (Map.Entry<VBox, InplaceWrite> read : mergedTx.nestedReads.entrySet()) {
validateNestedRead(read);
}
}
if (!this.globalReads.isEmpty()) {
validateGlobalReads(globalReads, next);
}
for (ParallelNestedTransaction mergedTx : mergedTxs) {
if (!mergedTx.globalReads.isEmpty()) {
validateGlobalReads(mergedTx.globalReads, mergedTx.next);
}
}
}
/*
* Validate a single read that was a read-after-write over some ancestor
* write. Iterate over the inplace writes of that VBox: if an entry is found
* belonging to an ancestor, it must be the one that it was read, in which
* case the search stops.
*/
protected void validateNestedRead(Map.Entry<VBox, InplaceWrite> read) {
InplaceWrite inplaceRead = read.getValue();
InplaceWrite iter = read.getKey().inplace;
do {
if (iter == inplaceRead) {
break;
}
int maxVersion = retrieveAncestorVersion(iter.orec.owner);
if (maxVersion >= 0) {
manualAbort();
TransactionSignaller.SIGNALLER.signalCommitFail(iter.orec.owner);
}
iter = iter.next;
} while (iter != null);
}
/*
* Validate a single read that obtained a VBoxBody Iterate over the inplace
* writes of that VBox: no entry may be found that belonged to an ancestor
*/
protected void validateGlobalReads(Cons<ReadBlock> reads, int startIdx) {
VBox[] array = reads.first().entries;
// the first may not be full
for (int i = startIdx + 1; i < array.length; i++) {
InplaceWrite iter = array[i].inplace;
do {
int maxVersion = retrieveAncestorVersion(iter.orec.owner);
if (maxVersion >= 0) {
manualAbort();
TransactionSignaller.SIGNALLER.signalCommitFail(iter.orec.owner);
}
iter = iter.next;
} while (iter != null);
}
// the rest are full
for (ReadBlock block : reads.rest()) {
array = block.entries;
for (int i = 0; i < array.length; i++) {
InplaceWrite iter = array[i].inplace;
do {
int maxVersion = retrieveAncestorVersion(iter.orec.owner);
if (maxVersion >= 0) {
manualAbort();
TransactionSignaller.SIGNALLER.signalCommitFail(iter.orec.owner);
}
iter = iter.next;
} while (iter != null);
}
}
}
protected Cons<VArrayEntry<?>> validateNestedArrayReads() {
Map<VArrayEntry<?>, VArrayEntry<?>> parentArrayWrites = getRWParent().arrayWrites;
Cons<VArrayEntry<?>> parentArrayReads = getRWParent().arraysRead;
int maxVersionOnParent = retrieveAncestorVersion(parent);
for (VArrayEntry<?> entry : arraysRead) {
// If the read was performed on an ancestor of the parent, then
// propagate it for further validation
if (entry.owner != parent) {
parentArrayReads = parentArrayReads.cons(entry);
}
synchronized (parent) {
if (parentArrayWrites != EMPTY_MAP) {
// Verify if the parent contains a more recent write for the
// read that we performed somewhere in our ancestors
VArrayEntry<?> parentWrite = parentArrayWrites.get(entry);
if (parentWrite == null) {
continue;
}
if (parentWrite.nestedVersion > maxVersionOnParent) {
TransactionSignaller.SIGNALLER.signalCommitFail(parent);
}
}
}
}
return parentArrayReads;
}
}