TransactionManager.java :  » GWT » jstm4gwt » jstm4gwt » core » Java Open Source

Java Open Source » GWT » jstm4gwt 
jstm4gwt » jstm4gwt » core » TransactionManager.java
/**
 * JSTM (http://xstm.net)
 * Distributed under the Apache License Version 2.0
 * Copyright  xstm.net
 */

package jstm4gwt.core;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import jstm4gwt.misc.ConcurrentHashMap;
import jstm4gwt.misc.ConcurrentLinkedQueue;
import jstm4gwt.misc.AtomicLong;
import jstm4gwt.misc.AtomicReference;

import jstm4gwt.core.Connection.Mode;
import jstm4gwt.core.TObject.Descriptor;
import jstm4gwt.core.Transaction.AbortedException;
import jstm4gwt.core.Transaction.Status;
import jstm4gwt.core.Transport.InitiatorInfo;
import jstm4gwt.extensions.Notifier;
import jstm4gwt.extensions.ThreadedNotifier;
import com.google.gwt.user.client.rpc.AsyncCallback;
import jstm4gwt.misc.Debug;
import jstm4gwt.misc.GWTAdapter;
import jstm4gwt.misc.Log;

final class TransactionManager {

    public static final int OBJECTS_VERSIONS_INDEX = 0;

    public static final TransactionEntry[] OBJECTS_DELTA = new TransactionEntry[0];

    // 

    private final AtomicReference<Snapshot> _snapshot = new AtomicReference<Snapshot>();

    private final ConcurrentLinkedQueue<TransactionCommit> _toMerge = new ConcurrentLinkedQueue<TransactionCommit>();

    private final Object _threadSwitchLock = new Object();

    // 

    private final ConcurrentHashMap<String, TObject> _objectsWithUID = new ConcurrentHashMap<String, TObject>();

    private final ArrayList<TObject.Id> _freeObjectIds = new ArrayList<TObject.Id>();

    private int _nextObjectId;

    /**
     * Negative for objects with UID.
     */
    private int _nextUidId = -1;

    //
    private volatile Connection[] _fullWatchers;

    private final Object _connectionsLock = new Object();

    private volatile Notifier _defaultNotifier;

    private final Object _defaultNotifierLock = new Object();

    //

    private final TransactionHelper _helper;

    protected final AtomicLong _committedCount = new AtomicLong();

    protected final AtomicLong _retriedCount = new AtomicLong();

    protected final AtomicLong _abortedCount = new AtomicLong();

    protected final AtomicLong _mergedCount = new AtomicLong();

    //

    public TransactionManager() {
        if (Debug.ENABLED) {
            _helper = new TransactionHelper(this);
            _helper.allowThread();

            if (Debug.DUMPS)
                _dumpMap = new HashMap<Object, String>();
        } else
            _helper = null;

        _snapshot.set(Snapshot.createInitial(this));
    }

    public TransactionHelper getHelper() {
        return _helper;
    }

    public Notifier getDefaultNotifier() {
        return _defaultNotifier;
    }

    public Notifier getOrCreateDefaultNotifier() {
        Notifier notifier = _defaultNotifier;

        if (notifier == null) {
            synchronized (_defaultNotifierLock) {
                notifier = _defaultNotifier;

                if (notifier == null)
                    notifier = _defaultNotifier = new ThreadedNotifier();
            }
        }

        return notifier;
    }

    public void setDefaultNotifier(Notifier value) {
        _defaultNotifier = value;
    }

    // Connections

    public Connection[] getFullWatchers() {
        return _fullWatchers;
    }

    public void register(Connection connection) {
        if (Debug.ENABLED)
            Debug.assertion(connection.getMode() == Connection.Mode.ALL_CHANGES);

        synchronized (_connectionsLock) {
            Connection[] connections = _fullWatchers;

            if (connections != null && Arrays.asList(connections).contains(connection))
                throw new IllegalArgumentException("Already registered");

            ArrayList<Connection> list = new ArrayList<Connection>();

            if (connections != null)
                list.addAll(Arrays.asList(connections));

            list.add(connection);

            _fullWatchers = list.toArray(new Connection[list.size()]);
        }
    }

    public void unregister(Connection connection) {
        if (Debug.ENABLED)
            Debug.assertion(connection.getMode() == Connection.Mode.ALL_CHANGES);

        synchronized (_connectionsLock) {
            Connection[] connections = _fullWatchers;

            if (connections == null || !Arrays.asList(connections).contains(connection))
                throw new IllegalArgumentException("Not registered");

            ArrayList<Connection> list = new ArrayList<Connection>();

            if (connections != null)
                list.addAll(Arrays.asList(connections));

            list.remove(connection);

            if (list.size() > 0)
                _fullWatchers = list.toArray(new Connection[list.size()]);
            else
                _fullWatchers = null;
        }
    }

    public boolean registered(Connection connection) {
        if (Debug.ENABLED)
            Debug.assertion(connection.getMode() == Connection.Mode.ALL_CHANGES);

        Connection[] connections = _fullWatchers;

        if (connections != null && Arrays.asList(connections).contains(connection))
            return true;

        return false;
    }

    /**
     * Only for GWT
     */
    public Site updateSiteKey(String previous, String current) {
        Debug.assertion(GWTAdapter.GWT);
        Site site = (Site) _objectsWithUID.remove(previous);
        _objectsWithUID.put(current, site);
        return site;
    }

    // Object Models

    public ConcurrentHashMap<String, TObject> getObjectsWithUID() {
        return _objectsWithUID;
    }

    //

    public Snapshot getSnapshot() {
        return _snapshot.get();
    }

    public int getPendingCommitCount() {
        Snapshot snapshot = _snapshot.get();
        return snapshot.getCommits().length;
    }

    //

    public void startTransaction(Transaction transaction, boolean seeOnlyAcknowledgedData) {
        if (Transaction.getCurrent() != null)
            throw new RuntimeException(Strings.ALREADY_RUNNING);

        if (Debug.STM)
            Log.write("Starting " + transaction);

        Snapshot snapshot;

        while (true) {
            /*
             * volatile read ensures sync with shared memory.
             */
            snapshot = _snapshot.get();

            /*
             * Increment watchers count to prevent the commit we are using as
             * our snapshot to be modified by merges.
             */
            TransactionCommit commit;

            if (seeOnlyAcknowledgedData)
                commit = snapshot.getLastAcknowledgedCommit();
            else
                commit = snapshot.getLast();

            int current = commit.getWatcherCount();

            /*
             * If the commit has no watcher it is going to be merged. This can
             * occur only if _snapshot has been updated since we read it because
             * commits have watcherCount at 1 by default. In this case, retry
             * with the new snapshot.
             */
            if (current > 0) {
                /*
                 * Otherwise try to add this transaction as new watcher.
                 */
                int next = current + 1;

                if (commit.compareAndSetWatcherCount(current, next)) {
                    // Add succeeded, the snapshot is valid so go

                    if (Debug.ENABLED)
                        getHelper().addWatcher(commit, transaction, current, next, "Start");

                    break;
                }
            }
        }

        transaction.setSnapshot(snapshot);
        transaction.setSnapshotCommits(snapshot.getCommits());
        transaction.setSnapshotDeltas(snapshot.getDeltas());

        if (seeOnlyAcknowledgedData)
            transaction.setStartIndex(snapshot.getLastAcknowledgedIndex());
        else
            transaction.setStartIndex(snapshot.getCommits().length - 1);

        Transaction.setCurrentInternal(transaction);

        if (Debug.ENABLED)
            Debug.assertion(transaction.getStatus() == Status.SUSPENDED);

        transaction.setStatus(Status.RUNNING);
    }

    public void continueTransaction(Transaction transaction, Transaction model) {
        if (Transaction.getCurrent() != null)
            throw new RuntimeException(Strings.ALREADY_RUNNING);

        if (Debug.STM)
            Log.write("Continuing " + transaction);

        int count = model.getStart().addAndGetWatcherCount(1);

        if (Debug.ENABLED)
            getHelper().addWatcher(model.getStart(), transaction, count - 1, count, "Continue");

        if (Debug.ENABLED) {
            // Assert model was not disposed
            Debug.assertion(count > 1);
        }

        transaction.setSnapshot(model.getSnapshot());
        transaction.setSnapshotCommits(model.getSnapshotCommits());
        transaction.setSnapshotDeltas(model.getSnapshotDeltas());
        transaction.setStartIndex(model.getStartIndex());

        Transaction.setCurrentInternal(transaction);

        if (Debug.ENABLED)
            Debug.assertion(transaction.getStatus() == Status.SUSPENDED);

        transaction.setStatus(Status.RUNNING);
    }

    public void beginCommit(Transaction transaction, AsyncCallback<Void> callback) {
        Notifier notifier = null;

        if (callback != null)
            notifier = getOrCreateDefaultNotifier();

        beginCommit(transaction, callback, notifier);
    }

    public void beginCommit(Transaction transaction, AsyncCallback<Void> callback, Notifier notifier) {
        if (transaction != Transaction.getCurrent())
            throw new RuntimeException(Strings.NOT_RUNNING);

        if (transaction.getStatus() != Transaction.Status.RUNNING)
            throw new RuntimeException(Strings.NOT_RUNNING);

        transaction.setCallback(notifier, callback);

        // If no read and no write, skip actual commit

        if (Debug.ENABLED)
            Debug.assertion((transaction.getCommit() != null) == (transaction.getDelta() != null));

        if (transaction.getCommit() != null)
            commit(transaction, null);
        else {
            /*
             * If notifier is fullWatcher, it will get the event in order
             * through the snapshot.
             */
            if (notifier != null && notifier.getMode() == Mode.LAST_VALUES) {
                Connection connection = notifier;
                connection.run(transaction.getCallback(), null, null, transaction);
            }
        }

        // Transaction does not watch its start commit anymore
        stopWatching(transaction.getStart(), transaction, "No more watching start");

        Transaction.setCurrentInternal(null);
    }

    public void propagate(Transaction transaction, Connection propagator) {
        if (Debug.ENABLED)
            Debug.assertion(transaction == Transaction.getCurrent());

        if (transaction.getCommit() != null) {
            commit(transaction, propagator);

            if (Debug.ENABLED)
                Debug.assertion(transaction.getStatus() == Transaction.Status.COMMITTED);

            // Transaction does not watch its start commit anymore
            stopWatching(transaction.getStart(), transaction, "No more watching start");

            Transaction.setCurrentInternal(null);
        } else {
            commitEmpty(transaction, propagator);
            dispose(transaction, false);
            Transaction.setCurrent(null);
        }
    }

    private void commit(Transaction transaction, Connection propagator) {
        if (Debug.ENABLED) {
            /*
             * Transaction not yet committed so cannot be watched by anyone. It
             * must have its default initial 1 watcher count.
             */
            Debug.assertion(transaction.getCommit().getWatcherCount() == 1);

            if (propagator != null)
                Debug.assertion(propagator.interceptsCommits());
        }

        /*
         * If connections need the transaction, increase commit's watching count
         * so it is not merged until those connections are done with it.
         */
        Connection[] fullWatchers = getFullWatchers();

        if (fullWatchers != null) {
            TransactionCommit commit = transaction.getCommit();
            int watchers = 1 + fullWatchers.length;
            commit.setWatcherCount(watchers);

            if (Debug.ENABLED) {
                for (int i = 0; i < fullWatchers.length; i++)
                    getHelper().addWatcher(commit, fullWatchers[i], 1, watchers, "Full watchers");
            }

            for (int i = 0; i < fullWatchers.length; i++)
                fullWatchers[i].onCommitStarted(transaction);
        }

        Snapshot newSnapshot = null;
        TransactionCommit lastValidated = transaction.getStart();
        TransactionCommit watchedDuringLastValidation = null;
        boolean done = false;

        while (true) {
            /*
             * Same mechanism as when starting a transaction, but with last
             * acknowledged commit.
             */

            Snapshot snapshot;
            int newWatchers;

            while (true) {
                while (true) {
                    snapshot = _snapshot.get();

                    /*
                     * Propagated transaction must not block. Sometimes commit
                     * acknowledgments that would unblock thread are behind on
                     * the line or the transport thats read it.
                     */
                    if (propagator != null || snapshot.getCommits().length < Site.MAX_PENDING_COMMIT_COUNT)
                        break;

                    // Wait if too much pending commits

                    if (GWTAdapter.GWT)
                        throw new IllegalStateException(Strings.TOO_MUCH_PENDING_COMMITS);
                    else {
                        try {
                            throw new jstm4gwt.misc.InterruptedException();

                            /*
                             * TODO benchmark with wait() / notify()
                             */
                            // synchronized (_lock) {
                            // _lock.wait();
                            // }
                        } catch (jstm4gwt.misc.InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }

                if (propagator != null)
                    transaction.setBeforeCommit(snapshot.getLastAcknowledgedCommit());
                else
                    transaction.setBeforeCommit(snapshot.getLast());

                /*
                 * Mark last acknowledged as watched so it stays there in case
                 * we have to retry validation. This way we can skip previous
                 * commits and only validate the transaction against new ones.
                 */
                newWatchers = 1;

                /*
                 * Also mark connections in ALL_CHANGES mode as watchers so the
                 * transaction will not be merged with last acknowledged which
                 * will be just before it until they are done with it.
                 */
                if (fullWatchers != null)
                    newWatchers += fullWatchers.length;

                /*
                 * C.f. startTransaction.
                 */
                int current = transaction.getBeforeCommit().getWatcherCount();

                if (current > 0) {
                    int next = current + newWatchers;

                    if (transaction.getBeforeCommit().compareAndSetWatcherCount(current, next)) {
                        if (Debug.ENABLED) {
                            getHelper().addWatcher(transaction.getBeforeCommit(), transaction, current, next, "Keep last");

                            if (fullWatchers != null)
                                for (int i = 0; i < fullWatchers.length; i++)
                                    getHelper().addWatcher(transaction.getBeforeCommit(), fullWatchers[i], current, next, "Keep for connection");
                        }

                        break;
                    }
                }
            }

            /*
             * Searched for last validated so we can validate transaction
             * against writes that occurred between this and last acknowledged.
             */

            int lastValidatedIndex = OBJECTS_VERSIONS_INDEX;

            for (int i = 0; i < snapshot.getCommits().length; i++) {
                if (snapshot.getCommits()[i] == lastValidated) {
                    lastValidatedIndex = i;
                    break;
                }
            }

            /*
             * If this is a retry, stop watching previous commit now that we
             * have found the index of last validated in new snapshot.
             */

            TransactionCommit toMerge = null;

            if (watchedDuringLastValidation != null) {
                int value = watchedDuringLastValidation.addAndGetWatcherCount(-newWatchers);

                if (Debug.ENABLED) {
                    int previous = value + newWatchers;
                    getHelper().removeWatcher(watchedDuringLastValidation, transaction, previous, value, "Stop keep last 1");

                    if (fullWatchers != null)
                        for (int i = 0; i < fullWatchers.length; i++)
                            getHelper().removeWatcher(watchedDuringLastValidation, fullWatchers[i], previous, value, "Stop keep for connection 1");
                }

                if (value == 0)
                    toMerge = watchedDuringLastValidation;
            }

            watchedDuringLastValidation = transaction.getBeforeCommit();

            /*
             * Validate transaction.
             */

            boolean valid = true;

            if (propagator != null) {
                if (Debug.ENABLED) {
                    Debug.assertion(transaction.getDependencies() == null);
                    Debug.assertion(transaction.validateAgainst(snapshot, lastValidatedIndex));
                    Debug.assertion(transaction.getInterception() == null);
                }
            } else {
                valid = valid && transaction.validateDependencies();
                valid = valid && transaction.validateAgainst(snapshot, lastValidatedIndex);
                valid = valid && transaction.updateInterception(fullWatchers);
            }

            if (valid && fullWatchers != null)
                for (int i = 0; i < fullWatchers.length; i++)
                    if (!fullWatchers[i].onCommitValidation(transaction))
                        valid = false;

            if (Debug.ENABLED) {
                if (TransactionHelper.AbortAlways)
                    valid = false;
                else if (TransactionHelper.AbortRandom)
                    valid = valid && GWTAdapter.getRandom().nextBoolean();
            }

            if (!valid) {
                if (Debug.STATS)
                    _abortedCount.incrementAndGet();

                transaction.setStatus(Transaction.Status.ABORTED);

                /*
                 * Notify connections.
                 */
                if (fullWatchers != null)
                    for (int i = 0; i < fullWatchers.length; i++)
                        fullWatchers[i].onCommitFailed(transaction);

                // Remove watchers

                int value = watchedDuringLastValidation.addAndGetWatcherCount(-newWatchers);

                if (Debug.ENABLED) {
                    int previous = value + newWatchers;
                    getHelper().removeWatcher(watchedDuringLastValidation, transaction, previous, value, "Stop keep last 2");

                    if (fullWatchers != null)
                        for (int i = 0; i < fullWatchers.length; i++)
                            getHelper().removeWatcher(watchedDuringLastValidation, fullWatchers[i], previous, value, "Stop keep for connection 2");
                }

                if (value == 0)
                    merge(watchedDuringLastValidation);

                if (Debug.ENABLED) {
                    TransactionCommit commit = transaction.getCommit();
                    String reason = "No more need of this commit as last";
                    getHelper().removeWatcher(commit, TransactionCommit.AmbientWatcher, 1, 0, reason);

                    if (fullWatchers != null)
                        for (int i = 0; i < fullWatchers.length; i++)
                            getHelper().removeWatcher(commit, fullWatchers[i], fullWatchers.length, 0, "Full watchers");
                }

                if (Debug.ENABLED) {
                    boolean a = transaction.getNotifier() == null;
                    boolean b = transaction.getCallback() == null;
                    Debug.assertion(a == b);
                }

                Notifier notifier = transaction.getNotifier();

                if (notifier != null) {
                    Connection connection = notifier;
                    connection.run(transaction.getCallback(), null, new AbortedException("Conflict"), null);
                }

                InitiatorInfo info = transaction.getCommit().getInitiatorInfo();

                if (info != null)
                    info.getConnection().enqueueAbort(transaction);

                done = true;
            } else {
                if (newSnapshot == null)
                    newSnapshot = new Snapshot();

                if (Debug.DUMPS)
                    Debug.log("Committed: " + getKey(transaction));

                int insertionIndex;

                /*
                 * Update transaction status before writing to volatile
                 * _snapshot field so other threads immediately see the new
                 * status.
                 */

                if (transaction.getInterception() == null) {
                    newSnapshot.setLastAcknowledgedIndex(snapshot.getLastAcknowledgedIndex() + 1);
                    insertionIndex = newSnapshot.getLastAcknowledgedIndex();

                    transaction.setStatus(Transaction.Status.COMMITTED);
                    transaction.getCommit().setStatus(TransactionCommit.Status.COMMITTED);
                } else {
                    newSnapshot.setLastAcknowledgedIndex(snapshot.getLastAcknowledgedIndex());
                    insertionIndex = snapshot.getCommits().length;

                    transaction.setStatus(Transaction.Status.WAITING_ACKNOWLEDGMENT);
                    transaction.getCommit().setStatus(TransactionCommit.Status.WAITING_ACKNOWLEDGMENT);
                }

                newSnapshot.setCommits(TransactionHelper.insertCommit(snapshot.getCommits(), transaction.getCommit(), insertionIndex));
                newSnapshot.setDeltas(TransactionHelper.insertDelta(snapshot.getDeltas(), transaction.getDelta(), insertionIndex));

                // Store last transaction for each connection

                storePropagator(transaction, snapshot, newSnapshot, propagator);

                // Store last validated if intercepted

                if (transaction.getInterception() != null || propagator != null) {
                    Connection connection = propagator;

                    if (connection == null)
                        connection = transaction.getInterception().getConnection();

                    if (snapshot.getLastReceived() != null) {
                        Transaction lastReceived = snapshot.getLastReceived().get(connection);
                        transaction.setLastValidatedAgainst(lastReceived);
                    }
                }

                //

                newSnapshot.setAbortedOnStart(snapshot.getAbortedOnStart());

                /*
                 * Try to publish the new snapshot.
                 */
                if (!_snapshot.compareAndSet(snapshot, newSnapshot)) {
                    if (Debug.STATS)
                        _retriedCount.incrementAndGet();

                    // Return to running so transaction can be manipulated.
                    transaction.setStatus(Transaction.Status.RUNNING);

                    /*
                     * If _snapshot had changed, retry from last acknowledged as
                     * we has already validated the transaction up to this
                     * commit.
                     */
                    boolean once = true;

                    if (Debug.ENABLED && !TransactionHelper.ValidateOnce)
                        once = false;

                    if (once)
                        lastValidated = snapshot.getLast();
                } else {
                    /*
                     * Success!
                     */
                    if (Debug.ENABLED) {
                        // Assert all watched commits are still there

                        for (int i = 0; i < snapshot.getCommits().length; i++) {
                            if (snapshot.getCommits()[i].getWatcherCount() > 0)
                                Debug.assertion(Arrays.asList(newSnapshot.getCommits()).contains(snapshot.getCommits()[i]));
                        }
                    }

                    if (Debug.STATS)
                        _committedCount.incrementAndGet();

                    if (transaction.getInterception() == null) {
                        /*
                         * lastAcknowledged is not the last element anymore so
                         * decrease watchers count to allow merge. When
                         * intercepted, leave it so it is merged on
                         * acknowledgment.
                         */
                        TransactionCommit previousLast = snapshot.getLastAcknowledgedCommit();
                        stopWatching(previousLast, TransactionCommit.AmbientWatcher, "No more last");

                        /*
                         * If notifier is fullWatcher, it will get the event in
                         * order through the snapshot.
                         */
                        Notifier notifier = transaction.getNotifier();

                        if (notifier != null && notifier.getMode() == Mode.LAST_VALUES) {
                            Connection connection = notifier;
                            connection.run(transaction.getCallback(), null, null, transaction);
                        }
                    }

                    /*
                     * Notify full watchers.
                     */
                    if (fullWatchers != null)
                        for (int i = 0; i < fullWatchers.length; i++)
                            fullWatchers[i].onCommitSucceeded(transaction);

                    transaction.notifyRelatedConnectionsOfChange();

                    /*
                     * No more need to find last validated commit in case of
                     * retry, but leave full watchers.
                     */
                    stopWatching(watchedDuringLastValidation, transaction, "Stop watching last ack");

                    done = true;
                }
            }

            if (toMerge != null)
                merge(toMerge);

            if (done) {
                if (Debug.ENABLED)
                    getHelper().removeValidated(transaction);

                break;
            }
        }
    }

    private void commitEmpty(Transaction transaction, Connection propagator) {
        Snapshot newSnapshot = new Snapshot();

        while (true) {
            Snapshot snapshot = _snapshot.get();

            newSnapshot.setCommits(snapshot.getCommits());
            newSnapshot.setDeltas(snapshot.getDeltas());
            newSnapshot.setLastAcknowledgedIndex(snapshot.getLastAcknowledgedIndex());
            newSnapshot.setAbortedOnStart(snapshot.getAbortedOnStart());

            storePropagator(transaction, snapshot, newSnapshot, propagator);

            if (_snapshot.compareAndSet(snapshot, newSnapshot)) {
                Connection[] fullWatchers = getFullWatchers();
                /*
                 * Notify full watchers. This might make a client acknowledged
                 * it has received a transaction from server and unblock server.
                 */
                if (fullWatchers != null)
                    for (int i = 0; i < fullWatchers.length; i++)
                        fullWatchers[i].onCommitSucceeded(transaction);

                break;
            }
        }
    }

    private void storePropagator(Transaction transaction, Snapshot snapshot, Snapshot newSnapshot, Connection propagator) {
        HashMap<Connection, Transaction> previous = snapshot.getLastReceived();

        if (previous != null)
            newSnapshot.getOrCreateLastReceived().putAll(previous);

        if (propagator != null)
            newSnapshot.getOrCreateLastReceived().put(propagator, transaction);
    }

    public void stopWatching(TransactionCommit commit, Object watcher, String reason) {
        int value = commit.decrementAndGetWatcherCount();

        if (Debug.ENABLED) {
            Debug.assertion(value >= 0);
            getHelper().removeWatcher(commit, watcher, value + 1, value, reason);
        }

        if (value == 0)
            merge(commit);
    }

    private void merge(TransactionCommit commit) {
        if (Debug.ENABLED)
            Debug.assertion(commit.getWatcherCount() == 0);

        if (Debug.STM)
            Log.write("Merge " + commit);

        if (!tryMerge(commit))
            _toMerge.add(commit);
        else {
            // Other merges could have been delayed by ours so try them again

            for (TransactionCommit delayed : _toMerge)
                if (tryMerge(delayed))
                    _toMerge.remove(delayed);
        }

        // TODO try those

        // while (!tryMerge(commit)) {
        // try {
        // Thread.sleep(0);
        // } catch (InterruptedException e) {
        // }
        // }

        // Snapshot snapshot = _snapshot.get();
        //
        // for (int i = 0; i < snapshot.getCommits().length; i++)
        // if (snapshot.getCommits()[i].getWatcherCount().get() == 0)
        // tryMerge(snapshot.getCommits()[i]);
    }

    private boolean tryMerge(TransactionCommit commit) {
        /*
         * Try to switch both merge guards to true before merge, otherwise
         * restore them as they were to merge later.
         */
        if (!commit.compareAndSetMergeInfo(TransactionCommit.MergeInfo.NONE, TransactionCommit.MergeInfo.SOURCE)) {
            return false;
        } else {
            if (commit.getStatus() == TransactionCommit.Status.COMMITTED)
                return tryMergeToNextCommit(commit);
            else {
                if (Debug.ENABLED)
                    Debug.assertion(commit.getStatus() == TransactionCommit.Status.ABORTED);

                removeCommit(commit);
                return true;
            }
        }
    }

    private boolean tryMergeToNextCommit(TransactionCommit commit) {
        /*
         * Commit will not be merged from this point, next element will always
         * exist if commit is not aborted.
         */
        Snapshot snapshot = _snapshot.get();
        int commitIndex = TransactionHelper.getIndex(snapshot, commit);
        TransactionCommit nextCommit = snapshot.getCommits()[commitIndex + 1];
        TransactionEntry[] nextDelta = snapshot.getDeltas()[commitIndex + 1];

        if (!nextCommit.compareAndSetMergeInfo(TransactionCommit.MergeInfo.NONE, TransactionCommit.MergeInfo.TARGET)) {
            commit.setMergeInfo(TransactionCommit.MergeInfo.NONE);
            return false;
        } else {
            if (Debug.ENABLED)
                Debug.assertion(commit.getStatus() == TransactionCommit.Status.COMMITTED);

            nextDelta = nextCommit.mergePrevious(nextDelta, commit, snapshot.getDeltas()[commitIndex]);

            if (Debug.ENABLED && commitIndex == OBJECTS_VERSIONS_INDEX)
                Debug.assertion(nextDelta == OBJECTS_DELTA);

            copyInitiatorInfos(commit, nextCommit);

            /*
             * Merge is done, replace _snapshot
             */
            Snapshot newSnapshot = new Snapshot();

            while (true) {
                newSnapshot.setLastAcknowledgedIndex(snapshot.getLastAcknowledgedIndex() - 1);

                int index = TransactionHelper.getIndex(snapshot, commit);

                newSnapshot.setCommits(TransactionHelper.removeCommit(snapshot.getCommits(), index));
                newSnapshot.setDeltas(TransactionHelper.removeDelta(snapshot.getDeltas(), index));
                newSnapshot.getDeltas()[index] = nextDelta;

                if (Debug.ENABLED) {
                    Debug.assertion(newSnapshot.getCommits()[index] == nextCommit);
                    Debug.assertion(newSnapshot.getDeltas()[TransactionManager.OBJECTS_VERSIONS_INDEX] == TransactionManager.OBJECTS_DELTA);
                }

                copyLastReceived(snapshot, newSnapshot);

                newSnapshot.setAbortedOnStart(snapshot.getAbortedOnStart());

                if (_snapshot.compareAndSet(snapshot, newSnapshot)) {
                    if (Debug.ENABLED) {
                        Debug.assertion(commit.getMergeInfo() == TransactionCommit.MergeInfo.SOURCE);
                        Debug.assertion(nextCommit.getMergeInfo() == TransactionCommit.MergeInfo.TARGET);
                        // InitiatorInfo info = commit.getInitiatorInfo();
                        //
                        // while (info != null) {
                        // Debug.assertion(nextCommit.initiatedBy(info.
                        // getTransaction()));
                        // info = info.getNext();
                        // }
                    }

                    if (Debug.STATS)
                        _mergedCount.incrementAndGet();

                    if (Debug.COMMUNICATIONS_LOG)
                        Debug.log("Merged");

                    if (Debug.DUMPS)
                        Debug.log("Merged: " + getKey(commit));

                    nextCommit.setMergeInfo(TransactionCommit.MergeInfo.NONE);
                    return true;
                }

                snapshot = _snapshot.get();
            }
        }
    }

    private void copyInitiatorInfos(TransactionCommit commit, TransactionCommit nextCommit) {
        InitiatorInfo info = commit.getInitiatorInfo();

        while (info != null) {
            addIfAbsentForConnection(nextCommit, info);
            info = info.getNext();
        }
    }

    private void addIfAbsentForConnection(TransactionCommit commit, InitiatorInfo previous) {
        InitiatorInfo info = commit.getInitiatorInfo();

        while (info != null) {
            if (previous.getConnection() == info.getConnection()) {
                if (Debug.ENABLED) {
                    Descriptor a = previous.getTransaction().getDescriptor();
                    Descriptor b = info.getTransaction().getDescriptor();

                    if (a != null && b != null)
                        Debug.assertion(a.getId() < b.getId());
                }

                return;
            }

            info = info.getNext();
        }

        commit.setInitiatorInfo(new InitiatorInfo(previous.getConnection(), previous.getTransaction(), commit.getInitiatorInfo()));
    }

    private void removeCommit(TransactionCommit commit) {
        Snapshot newSnapshot = new Snapshot();

        while (true) {
            Snapshot snapshot = _snapshot.get();

            int index = TransactionHelper.getIndex(snapshot, commit);

            newSnapshot.setCommits(TransactionHelper.removeCommit(snapshot.getCommits(), index));
            newSnapshot.setDeltas(TransactionHelper.removeDelta(snapshot.getDeltas(), index));
            newSnapshot.setLastAcknowledgedIndex(snapshot.getLastAcknowledgedIndex());

            copyLastReceived(snapshot, newSnapshot);

            if (snapshot.getAbortedOnStart() != null)
                for (TransactionCommit aborted : snapshot.getAbortedOnStart())
                    if (aborted != commit)
                        newSnapshot.getOrCreateAbortedOnStart().add(aborted);

            if (_snapshot.compareAndSet(snapshot, newSnapshot)) {
                if (Debug.ENABLED) {
                    Debug.assertion(commit.getMergeInfo() == TransactionCommit.MergeInfo.SOURCE);
                    Debug.assertion(newSnapshot.getCommits().length == newSnapshot.getDeltas().length);
                    Debug.assertion(!Arrays.asList(newSnapshot.getCommits()).contains(commit));
                    Debug.assertion(!Arrays.asList(newSnapshot.getDeltas()).contains(snapshot.getDeltas()[index]));
                }

                break;
            }
        }
    }

    private void copyLastReceived(Snapshot snapshot, Snapshot newSnapshot) {
        if (newSnapshot.getLastReceived() != null)
            newSnapshot.getLastReceived().clear();

        HashMap<Connection, Transaction> previousMap = snapshot.getLastReceived();

        if (previousMap != null)
            newSnapshot.getOrCreateLastReceived().putAll(previousMap);

        if (Debug.COMMUNICATIONS_LOG)
            Debug.log("Copied LastReceived " + newSnapshot.getLastReceived());
    }

    public void onInterceptionAcknowledgments(Connection connection, ArrayList<Transaction> acknowledged) {
        Snapshot newSnapshot = new Snapshot();

        // Before the snapshot for visibility

        for (int i = acknowledged.size() - 1; i >= 0; i--) {
            Transaction transaction = acknowledged.get(i);
            Transaction.setCurrent(transaction, connection);

            if (transaction.getStatus() == Status.WAITING_ACKNOWLEDGMENT) {
                transaction.setStatus(Status.COMMITTED);

                if (transaction.getCommit() != null)
                    transaction.getCommit().setStatus(TransactionCommit.Status.COMMITTED);
            } else {
                acknowledged.remove(i);

                if (Debug.ENABLED) {
                    Debug.assertion(transaction.getStatus() == Status.ABORTED);
                    Debug.assertion(transaction.getCommit().getStatus() == TransactionCommit.Status.ABORTED);
                }
            }
        }

        while (true) {
            Snapshot snapshot = _snapshot.get();

            int newIndex = snapshot.getLastAcknowledgedIndex();
            newSnapshot.setCommits(snapshot.getCommits());
            newSnapshot.setDeltas(snapshot.getDeltas());

            for (Transaction transaction : acknowledged) {
                int index = TransactionHelper.getIndex(newSnapshot, transaction.getCommit());
                newIndex++;
                newSnapshot.setCommits(TransactionHelper.moveCommit(newSnapshot.getCommits(), index, newIndex));
                newSnapshot.setDeltas(TransactionHelper.moveDelta(newSnapshot.getDeltas(), index, newIndex));
            }

            newSnapshot.setLastAcknowledgedIndex(newIndex);
            newSnapshot.setLastReceived(snapshot.getLastReceived());
            newSnapshot.setAbortedOnStart(snapshot.getAbortedOnStart());

            if (_snapshot.compareAndSet(snapshot, newSnapshot)) {
                if (Debug.ENABLED) {
                    Debug.assertion(snapshot.getCommits().length == newSnapshot.getCommits().length);
                    Debug.assertion(newSnapshot.getCommits().length == newSnapshot.getDeltas().length);
                    int first = newIndex - acknowledged.size() + 1;

                    for (int i = 0; i < acknowledged.size(); i++) {
                        Debug.assertion(TransactionHelper.getIndex(newSnapshot, acknowledged.get(i).getCommit()) == first + i);
                        Debug.assertion(newSnapshot.getDeltas()[first + i] == acknowledged.get(i).getDelta());
                    }
                }

                for (int i = snapshot.getLastAcknowledgedIndex(); i < newSnapshot.getLastAcknowledgedIndex(); i++)
                    stopWatching(newSnapshot.getCommits()[i], TransactionCommit.AmbientWatcher, "No more last");

                if (Debug.STATS)
                    _committedCount.incrementAndGet();

                for (Transaction transaction : acknowledged) {
                    Transaction.setCurrent(transaction);
                    connection.done(transaction);
                    transaction.notifyRelatedConnectionsOfChange();

                    /*
                     * If notifier is fullWatcher, it will get the event in
                     * order through the snapshot.
                     */
                    Notifier notifier = transaction.getNotifier();

                    if (notifier != null && notifier.getMode() == Mode.LAST_VALUES)
                        Connection.callback(notifier, transaction.getCallback(), null, null, transaction);

                    transaction.getInterception().onSuccess(true);
                }

                Connection[] fullWatchers = getFullWatchers();

                if (fullWatchers != null)
                    for (int i = 0; i < fullWatchers.length; i++)
                        fullWatchers[i].onChange();

                Transaction.setCurrent(null);
                break;
            }
        }
    }

    public void onInterceptionRejected(Transaction transaction) {
        if (Debug.ENABLED)
            Debug.assertion(transaction == Transaction.getCurrent());

        Snapshot newSnapshot = new Snapshot();

        while (true) {
            Snapshot snapshot = _snapshot.get();

            /*
             * Do not remove commit yet as it can be still watched, but mark it
             * as aborted in a new snapshot.
             */
            newSnapshot.setCommits(snapshot.getCommits());
            newSnapshot.setDeltas(snapshot.getDeltas());
            newSnapshot.setLastAcknowledgedIndex(snapshot.getLastAcknowledgedIndex());
            newSnapshot.setLastReceived(snapshot.getLastReceived());

            if (snapshot.getAbortedOnStart() != null)
                newSnapshot.getOrCreateAbortedOnStart().addAll(snapshot.getAbortedOnStart());

            newSnapshot.getOrCreateAbortedOnStart().add(transaction.getCommit());

            if (_snapshot.compareAndSet(snapshot, newSnapshot)) {
                if (Debug.STATS)
                    _abortedCount.incrementAndGet();

                dispose(transaction, true);

                Connection[] fullWatchers = getFullWatchers();

                if (fullWatchers != null)
                    for (int i = 0; i < fullWatchers.length; i++)
                        fullWatchers[i].onCommitRejected(transaction);

                Notifier notifier = transaction.getNotifier();

                if (notifier != null) {
                    Connection connection = notifier;
                    String reason = transaction.getAbortReason();
                    connection.run(transaction.getCallback(), null, new AbortedException(reason), null);
                }

                transaction.getInterception().onSuccess(false);
                Transaction.setCurrent(null);
                break;
            }
        }
    }

    public void dispose(Transaction transaction, boolean abort) {
        if (Debug.ENABLED)
            Debug.assertion(transaction == Transaction.getCurrent());

        Transaction.Status status = transaction.getStatus();

        if (abort) {
            if (Debug.ENABLED)
                Debug.assertion(status == Status.RUNNING || status == Status.WAITING_ACKNOWLEDGMENT);

            if (Debug.STATS)
                _abortedCount.incrementAndGet();

            transaction.setStatus(Status.ABORTED);

            if (transaction.getCommit() != null) {
                transaction.getCommit().setStatus(TransactionCommit.Status.ABORTED);
            }
        } else {
            if (Debug.ENABLED && transaction.getCommit() != null) {
                Debug.assertion(status == Status.RUNNING || status == Status.COMMITTED);
                TransactionCommit.Status s = transaction.getCommit().getStatus();
                Debug.assertion(s == null || s == TransactionCommit.Status.COMMITTED);
            }
        }

        /*
         * !! Volatile update of watcher count ensures other threads will see
         * status when they merge the commit. When aborted, the commit is not
         * merged but simply removed so it is important that threads see the
         * right status.
         */

        if (status == Status.RUNNING) {
            stopWatching(transaction.getStart(), transaction, "Dispose, start");

            if (Debug.ENABLED && transaction.getCommit() != null) {
                int watchers = transaction.getCommit().decrementAndGetWatcherCount();
                Debug.assertion(watchers == 0);
                getHelper().removeWatcher(transaction.getCommit(), TransactionCommit.AmbientWatcher, 1, 0, "Dispose, commit");
            }
        } else {
            // Already removed watcher on start when committed
            Debug.assertion(status == Status.WAITING_ACKNOWLEDGMENT);

            if (transaction.getCommit() != null)
                stopWatching(transaction.getCommit(), TransactionCommit.AmbientWatcher, "Dispose, commit rejected");
        }
    }

    /**
     * In case of suspend and resume, there is no access to _snapshot to
     * synchronize memory between two threads. Threads must communicate through
     * a lock.
     */
    public void suspend(Transaction transaction) {
        synchronized (_threadSwitchLock) {
            transaction.setStatus(Transaction.Status.SUSPENDED);
        }
    }

    /**
     * C.f. comment on suspend.
     */
    public boolean resume(Transaction transaction) {
        synchronized (_threadSwitchLock) {
            // Ensures only one thread can resume a transaction

            if (transaction.getStatus() == Status.SUSPENDED) {
                transaction.setStatus(Status.RUNNING);
                return true;
            }

            return false;
        }
    }

    // Objects IDs

    public int getNextObjectId() {
        if (_freeObjectIds.size() > 0)
            return _freeObjectIds.remove(_freeObjectIds.size() - 1).Value;

        return _nextObjectId++;
    }

    public int getNextUidId() {
        return _nextUidId--;
    }

    // TODO
    // public void onObjectDisconnected(TObject.Id id) {
    // _freeObjectIds.add(id);
    // }

    // Debug help!

    private HashMap<Object, String> _dumpMap;

    protected String getKey(Object o) {
        if (!Debug.DUMPS)
            throw new IllegalStateException();

        if (o == null)
            return null;

        synchronized (this) {
            if (!_dumpMap.containsKey(o))
                _dumpMap.put(o, GWTAdapter.getClassSimpleName(o.getClass()) + _dumpMap.size());

            return _dumpMap.get(o);
        }
    }

    protected void putKey(Object key, String value) {
        if (!Debug.DUMPS)
            throw new IllegalStateException();

        synchronized (this) {
            _dumpMap.put(key, value);
        }
    }

    protected void copyKey(Transaction transaction, TransactionCommit commit) {
        if (!Debug.DUMPS)
            throw new IllegalStateException();

        synchronized (this) {
            String value = getKey(transaction);
            _dumpMap.put(commit, value);
        }
    }

    protected void dumpSnapshot() {
        if (!Debug.DUMPS)
            throw new IllegalStateException();

        TransactionCommit[] commits = getSnapshot().getCommits();

        for (int i = 0; i < commits.length; i++) {
            ArrayList<Object> list = new ArrayList<Object>();

            for (Object o : getHelper().getWatchers(commits[i])) {
                list.add(getKey(o));
            }

            System.out.println("Watchers for " + i + " " + getKey(commits[i]) + ": " + list);
        }
    }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.