cern.entwined.Memory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of entwined-stm Show documentation
Show all versions of entwined-stm Show documentation
Implements various transactional collections with semantic concurrency control
The newest version!
/*
* Entwined STM
*
* (c) Copyright 2013 CERN. This software is distributed under the terms of the Apache License Version 2.0, copied
* verbatim in the file "COPYING". In applying this licence, CERN does not waive the privileges and immunities granted
* to it by virtue of its status as an Intergovernmental Organization or submit itself to any jurisdiction.
*/
package cern.entwined;
import java.util.LinkedList;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import cern.entwined.exception.ConflictException;
import cern.entwined.exception.InvocationException;
import cern.entwined.exception.MemoryException;
import cern.entwined.exception.NoTransactionException;
/**
* Software transactional memory root class. It manages global memory snapshot and concurrent execution of transactions.
*
* @param The client {@link SemiPersistent} type.
* @author Ivan Koblik
*/
public class Memory> {
/**
* Current thread will wait for a notification from the other threads currently processing committed blocks, if it
* happens that other threads die unexpectedly this timeout will revive the current thread. The value is in
* milliseconds.
*/
private static final int THEAD_DEATH_DELAY = 3000;
/**
* Number of retries of a transaction in case of conflicts.
*/
protected static final int NUM_RETRIES = 10000;
/**
* Globally shared memory.
*/
private volatile BaseSnapshot globalSnapshot;
/**
* Sequence of unique identifiers, e.g. every instance of {@link GlobalReference} takes one id.
*/
private final AtomicLong idSequence = new AtomicLong(0);
/**
* This lock is used to serialize updates of the global state.
*/
private final ReadWriteLock accessLock = new ReentrantReadWriteLock();
/**
* Thread local variable storing stack of snapshots starting with outer transaction through all the inner
* transactions.
*/
private final ThreadLocal>> threadLocalSnapshots = new ThreadLocal>>();
/**
* Thread local variable pointing to the current node with an I/O call back as its value.
*/
private final ThreadLocal>> currentNode = new ThreadLocal>>();
/**
* Queue of snapshots in commit order, needed for synchronous I/O after commit.
*/
private final ConcurrentLinkedQueue> commitQueue = new ConcurrentLinkedQueue>();
/**
* Flag is set to true only after a transaction has been committed and its committed block is being executed.
*/
private final ThreadLocal isCommitting = new ThreadLocal();
/**
* Creates transactional memory with given initial state.
*
* @param initialState The initial state of transactional memory. It takes a clean copy of it.
*/
public Memory(T initialState) {
Utils.checkNull("Initial State", initialState);
this.globalSnapshot = new BaseSnapshot(0l, initialState.cleanCopy());
}
/**
* Executes a transaction and calls the given transactional user code.
*
* @param transaction The transaction interface implementation.
* @return the commit state true
if committed, false
if rolled back.
*/
public boolean runTransaction(Transaction transaction) {
Utils.checkNull("Transaction callback", transaction);
if (Boolean.TRUE == this.isCommitting.get()) {
throw new MemoryException("Cannot run transaction within committed block.");
}
if (null == this.currentNode.get()) {
return execOuterTransaction(transaction);
} else {
return execInnerTransaction(transaction);
}
}
/**
* Checks if there is a running transaction and if there is returns its snapshot.
*
* @return The currently running transaction's snapshot.
* @throws MemoryException if there is no running transaction.
*/
protected BaseSnapshot getBaseSnapshot() {
LinkedList> stack = this.getSnapshotStack();
if (stack.isEmpty()) {
throw new NoTransactionException("There is no running transaction, cannot access the base snapshot");
}
return stack.peek();
}
/**
* Returns the next unique ID that can be used in {@link BaseSnapshot}.
*
* @return Unique ID for this memory instance.
*/
protected Long getNextId() {
return idSequence.getAndIncrement();
}
/**
* Invoked when an outer transaction needs to be executed.
*
* @param transaction The in-transactional user code.
* @return the commit state true
if committed, false
if rolled back.
*/
private boolean execOuterTransaction(Transaction transaction) {
int retryIdx = 0;
while (true) {
// Getting copy of the global snapshot for the transaction
BaseSnapshot transactionSnapshot = this.cleanCopyGlobalSnapshot();
// Saving transaction's starting point
Node> transactionNode = new Node>(transaction);
this.currentNode.set(transactionNode);
// Invoking transactional user code
try {
if (!invokeUserCode(transaction, transactionSnapshot, this.getSnapshotStack())) {
return false;
}
} finally {
this.currentNode.set(null);
this.threadLocalSnapshots.get().clear();
}
// Committing
BaseSnapshot newGlobalState;
try {
newGlobalState = this.commitSnapshot(transactionSnapshot);
} catch (ConflictException ex) {
// Too many retries, transaction has failed.
if (++retryIdx > NUM_RETRIES) {
throw ex;
}
continue;
}
// Invoking the post-transactional I/O callbacks
try {
/*
* Waiting for the new snapshot to appear in the head of the queue and invoking the callback in
* postorder.
*
* This code adds up to contention, if performance issues detected its the first thing to change.
*/
waitItsTurn(newGlobalState);
isCommitting.set(true);
this.postorder(transactionNode, newGlobalState);
} finally {
this.commitQueue.poll();
isCommitting.set(false);
notifyNextInQueue();
}
break;
}
return true;
}
/**
* Invokes after-transactional callback methods in commit order.
*
* @param node The callbacks root node.
* @param snapshot The commit time snapshot.
*/
private void postorder(Node> node, BaseSnapshot snapshot) {
for (Node> child : node.getChildren()) {
this.postorder(child, snapshot);
}
// It is crucial to copy the global state or transactional memory will get broken.
LinkedList> stack = getSnapshotStack();
try {
BaseSnapshot cleanCopy = snapshot.cleanCopy();
stack.push(cleanCopy);
node.getValue().committed(cleanCopy.getClientData());
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new InvocationException("Exception in committed block", e);
} finally {
stack.pop();
}
}
/**
* Invoked when an inner transaction need to be executed.
*
* @param transaction The user in-transactional code.
* @return the commit state true
if committed, false
if rolled back.
*/
private boolean execInnerTransaction(Transaction transaction) {
LinkedList> snapshotStack = getSnapshotStack();
Node> childNode = new Node>(transaction);
BaseSnapshot innerSnapshot = snapshotStack.peek().dirtyCopy();
Node> parentNode = currentNode.get();
parentNode.addChild(childNode);
currentNode.set(childNode);
boolean success = false; // Set to true if the user code is executed successfully
try {
// Called method either returns true or throws an exception
success = this.invokeUserCode(transaction, innerSnapshot, snapshotStack);
} finally {
currentNode.set(parentNode);
BaseSnapshot outerSnapshot = snapshotStack.peek();
if (!success) {
// If we're here an exception was thrown in inner transaction
parentNode.removeChild();
// Preserving only the read logs
outerSnapshot.update(innerSnapshot, true);
} else {
// Preserving the inner transaction's updates and read logs
outerSnapshot.update(innerSnapshot, false);
}
}
return success;
}
/**
* Invokes user in-transactional code.
*
* @param transaction The user in-transactional code.
* @param transactionSnapshot The snapshot to be used by the transaction.
* @param snapshotStack The thread local stack of transactions snapshots.
* @return the commit request, true
to commit, false
to rollback.
*/
private boolean invokeUserCode(Transaction transaction, BaseSnapshot transactionSnapshot,
LinkedList> snapshotStack) {
// === Added to the stack ===
snapshotStack.push(transactionSnapshot);
try {
return transaction.run(transactionSnapshot.getClientData());
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new InvocationException("Exception in the transactional code", e);
} finally {
snapshotStack.poll();
// === Removed from the stack ===
}
}
/**
* Returns the thread local snapshot stack or if null, creates new one.
*
* @return The thread local snapshot stack.
*/
private LinkedList> getSnapshotStack() {
// Creating thread local transaction stack and transaction log.
LinkedList> snapshotStack = threadLocalSnapshots.get();
if (null == snapshotStack) {
snapshotStack = new LinkedList>();
threadLocalSnapshots.set(snapshotStack);
}
return snapshotStack;
}
/**
* Attempts to commit local changes and update global state.
*
* @param transactionSnapshot The local changes.
* @return New global state if commit successful.
* @throws ConflictException if a conflicting changes detected.
*/
private BaseSnapshot commitSnapshot(BaseSnapshot transactionSnapshot) {
accessLock.writeLock().lock();
try {
BaseSnapshot committedSnapshot = transactionSnapshot.commit(this.globalSnapshot);
this.globalSnapshot = committedSnapshot;
// Adding new snapshot to the tail of the post-transactional callbacks queue
commitQueue.add(committedSnapshot);
return committedSnapshot;
} finally {
accessLock.writeLock().unlock();
}
}
/**
* Returns a clean copy of the global snapshot.
*
* @return The global snapshot clean copy.
*/
private BaseSnapshot cleanCopyGlobalSnapshot() {
BaseSnapshot snapshotCopy;
// Not needed for a single instance. May be replaced with RMI&transient.
// accessLock.readLock().lock();
// try {
snapshotCopy = this.globalSnapshot.cleanCopy();
// } finally {
// accessLock.readLock().unlock();
// }
return snapshotCopy;
}
/**
* Waits for in the queue for its turn to execute post transactional callback.
*
* @param newGlobalState The snapshot associated with transaction.
*/
private void waitItsTurn(BaseSnapshot newGlobalState) {
boolean interrupted = false;
// Waiting for argument to appear in the head of queue.
while (this.commitQueue.peek() != newGlobalState) {
synchronized (newGlobalState) {
if (this.commitQueue.peek() != newGlobalState) {
try {
newGlobalState.wait(THEAD_DEATH_DELAY);
} catch (InterruptedException e) {
// Remembering the flag, but continuing waiting for the queue.
interrupted = true;
}
}
}
}
// Preserving the interrupted flag
if (interrupted) {
Thread.currentThread().interrupt();
}
}
/**
* Checks if there's another {@link BaseSnapshot} in the commitQueue and if there is notifies all the threads
* waiting on it.
*/
private void notifyNextInQueue() {
BaseSnapshot next = this.commitQueue.peek();
if (null != next) {
synchronized (next) {
next.notifyAll();
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy