All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cern.entwined.Memory Maven / Gradle / Ivy

Go to download

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