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

org.objectfabric.VersionMap Maven / Gradle / Ivy

/**
 * This file is part of ObjectFabric (http://objectfabric.org).
 *
 * ObjectFabric is licensed under the Apache License, Version 2.0, the terms
 * of which may be found at http://www.apache.org/licenses/LICENSE-2.0.html.
 * 
 * Copyright ObjectFabric Inc.
 * 
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

package org.objectfabric;

import java.util.concurrent.atomic.AtomicInteger;

import org.objectfabric.TObject.Transaction;
import org.objectfabric.TObject.Version;
import org.objectfabric.ThreadAssert.SingleThreadedThenShared;

/**
 * Contains versions of object modified by a transaction. Versions themselves are stored
 * in an external array.
 */
@SuppressWarnings("serial")
@SingleThreadedThenShared
final class VersionMap extends AtomicInteger {

    static final VersionMap CLOSING = new VersionMap(-1);

    private Transaction _transaction;

    private boolean _remote;

    // TODO add a condensed filter of reads & write to skip validation

    //

    /**
     * Maps start with a watchers count of 1 so they are not considered disposed by
     * default. By convention the watcher is the parent transaction, and the View for the
     * initial map.
     */
    static final int DEFAULT_WATCHERS = 1;

    //

    private static final int MERGE_DEFAULT = 0;

    private static final int MERGE_A = 1;

    private static final int MERGE_B = 2;

    private static final int MERGE_DONE = 3;

    /**
     * This field prevents two threads to merge the same map with another one at the same
     * time.
     */
    private final AtomicInteger _mergeInfo = new AtomicInteger();

    static {
        if (Debug.THREADS) {
            ThreadAssert.removePrivate(CLOSING);
            ThreadAssert.addSharedDefinitively(CLOSING);
        }
    }

    /*
     * TODO Use an array of counters indexed by a kind of thread id, and only update this
     * when thread counter reaches 0.
     */
    VersionMap() {
        this(DEFAULT_WATCHERS);
    }

    private VersionMap(int value) {
        super(value);
    }

    final Transaction getTransaction() {
        return _transaction;
    }

    final void setTransaction(Transaction value) {
        if (Debug.ENABLED) {
            Debug.assertion(value == null || value.isCommitted());
            Debug.assertion(_transaction == null || _transaction == value);
        }

        _transaction = value;
    }

    final boolean isRemote() {
        return _remote;
    }

    final void setRemote() {
        if (Debug.ENABLED)
            Debug.assertion(!_remote);

        _remote = true;
    }

    //

    final boolean isNotMerging() {
        return _mergeInfo.get() == MERGE_DEFAULT;
    }

    final boolean isMergedToAnotherMap() {
        return _mergeInfo.get() == MERGE_DONE;
    }

    //

    final int getWatchers() {
        return get();
    }

    final boolean tryToAddWatchers(int count) {
        if (Debug.ENABLED)
            Debug.assertion(count > 0);

        for (;;) {
            int watchers = get();

            if (watchers == 0)
                return false;

            if (Debug.ENABLED)
                Debug.assertion(watchers > 0);

            int newWatchers = watchers + count;

            if (compareAndSet(watchers, newWatchers))
                return true;
        }
    }

    final Runnable removeWatchers(final Workspace workspace, int count, boolean delayed, final Snapshot mapSnapshot) {
        if (Debug.ENABLED)
            Debug.assertion(count > 0);

        for (;;) {
            int watchers = get();

            if (Debug.ENABLED)
                Debug.assertion(watchers > 0);

            int newWatchers = watchers - count;

            if (compareAndSet(watchers, newWatchers)) {
                if (newWatchers == 0) {
                    if (!delayed)
                        merge(workspace, mapSnapshot);
                    else {
                        return new Runnable() {

                            @Override
                            public void run() {
                                merge(workspace, mapSnapshot);
                            }
                        };
                    }
                }

                return null;
            }
        }
    }

    private void merge(Workspace workspace, Snapshot mapSnapshot) {
        boolean walkQueue = mergeAndReturnIfShouldWalkDelayedQueue(workspace, mapSnapshot);

        if (walkQueue) {
            for (;;) {
                VersionMap map = workspace.pollToMergeLater();

                if (map == null)
                    break;

                if (Debug.ENABLED) {
                    mapSnapshot = Helper.instance().getToMergeLater().remove(map);
                    Debug.assertion(mapSnapshot != null);
                    mapSnapshot = mapSnapshot != Helper.TO_MERGE_LATER_IS_NULL ? mapSnapshot : null;
                }

                map.mergeAndReturnIfShouldWalkDelayedQueue(workspace, mapSnapshot);
            }
        }
    }

    private boolean mergeAndReturnIfShouldWalkDelayedQueue(Workspace workspace, Snapshot mapSnapshot) {
        // TODO bench read field before for less CAS

        /*
         * Try to switch both merge guards before merge, otherwise restore them and add to
         * queue to merge later.
         */
        if (!_mergeInfo.compareAndSet(MERGE_DEFAULT, MERGE_A)) {
            if (_mergeInfo.get() != MERGE_DONE) {
                if (Debug.ENABLED) {
                    mapSnapshot = mapSnapshot != null ? mapSnapshot : Helper.TO_MERGE_LATER_IS_NULL;
                    Debug.assertion(Helper.instance().getToMergeLater().put(this, mapSnapshot) == null);
                }

                workspace.keepToMergeLater(this);
            }

            return false;
        }

        /*
         * From there on mergeInfo has to be released on return so return true to retry
         * potential delayed merges.
         */

        Snapshot snapshot = workspace.snapshot();
        int aIndex = Helper.getIndex(snapshot, this);
        int bIndex = aIndex + 1;
        VersionMap b = snapshot.getVersionMaps()[bIndex];

        if (Debug.ENABLED) {
            /*
             * If closed, previous map is still considered last of queue, so it should
             * still have at least one watcher.
             */
            Debug.assertion(b != CLOSING);
        }

        if (!b._mergeInfo.compareAndSet(MERGE_DEFAULT, MERGE_B)) {
            if (Debug.ENABLED) {
                mapSnapshot = mapSnapshot != null ? mapSnapshot : Helper.TO_MERGE_LATER_IS_NULL;
                Debug.assertion(Helper.instance().getToMergeLater().put(this, mapSnapshot) == null);
            }

            /*
             * Other merge ongoing, add to queue to retry when finished.
             */
            workspace.keepToMergeLater(this);

            _mergeInfo.set(MERGE_DEFAULT);
            return true;
        }

        if (Debug.STM_LOG)
            Log.write("Merging " + this + " to " + b);

        Version[] mergedWrites = merge(this, snapshot.writes()[aIndex], b, snapshot.writes()[bIndex]);
        Version[] mergedReads = null;

        if (snapshot.getReads() != null && aIndex != TransactionManager.OBJECTS_VERSIONS_INDEX) {
            Version[] aReads = snapshot.getReads()[aIndex];
            Version[] bReads = snapshot.getReads()[bIndex];

            if (aReads != null && bReads != null)
                mergedReads = merge(this, aReads, b, bReads);
            else if (aReads != null)
                mergedReads = aReads;
            else if (bReads != null)
                mergedReads = bReads;
        }

        if (Debug.ENABLED)
            if (aIndex == TransactionManager.OBJECTS_VERSIONS_INDEX)
                Debug.assertion(mergedWrites == TransactionManager.OBJECTS_VERSIONS);

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

        for (;;) {
            newSnapshot.setVersionMaps(Helper.removeVersionMap(snapshot.getVersionMaps(), aIndex));
            Version[][] reads = snapshot.getReads();

            if (reads != null) {
                if (Debug.ENABLED && aIndex == TransactionManager.OBJECTS_VERSIONS_INDEX)
                    Debug.assertion(mergedReads == null);

                boolean empty = mergedReads == null;

                if (empty)
                    for (int i = reads.length - 1; i >= 0; i--)
                        if (i != aIndex && i != aIndex + 1 && reads[i] != null)
                            empty = false;

                if (!empty) {
                    reads = Helper.removeVersions(reads, aIndex);
                    reads[aIndex] = mergedReads;
                } else
                    reads = null;
            }

            newSnapshot.setReads(reads);
            newSnapshot.writes(Helper.removeVersions(snapshot.writes(), aIndex));
            newSnapshot.writes()[aIndex] = mergedWrites;
            newSnapshot.slowChanging(snapshot.slowChanging());

            if (Debug.ENABLED) {
                Debug.assertion(newSnapshot.getVersionMaps()[bIndex - 1] == b);
                Debug.assertion(newSnapshot.writes()[TransactionManager.OBJECTS_VERSIONS_INDEX] == TransactionManager.OBJECTS_VERSIONS);
            }

            if (workspace.casSnapshot(snapshot, newSnapshot)) {
                if (Debug.ENABLED) {
                    Debug.assertion(_mergeInfo.get() == MERGE_A);
                    Debug.assertion(b._mergeInfo.get() == MERGE_B);
                }

                if (Stats.ENABLED)
                    Stats.Instance.Merged.incrementAndGet();

                _mergeInfo.set(MERGE_DONE);
                b._mergeInfo.set(MERGE_DEFAULT);

                if (Debug.THREADS) {
                    ThreadAssert.addSharedDefinitively(this);
                    ThreadAssert.removeShared(this);
                }

                return true;
            }

            snapshot = workspace.snapshot();
            aIndex = Helper.getIndex(snapshot, this);

            if (Debug.ENABLED) {
                // Only to check the assertion before the CAS
                bIndex = Helper.getIndex(snapshot, b);
            }
        }
    }

    //

    /**
     * The merge process is crossed between maps and versions. Functionally map a is
     * merged to map b and removed, but data is merged from versions b to versions a. C.f.
     * method Version.merge.
     */
    private static Version[] merge(VersionMap a, Version[] aVersions, VersionMap b, Version[] bVersions) {
        if (Debug.ENABLED) {
            Debug.assertion(a.getWatchers() == 0);
            Debug.assertion(a._mergeInfo.get() == MERGE_A);
            Debug.assertion(b._mergeInfo.get() == MERGE_B);
        }

        Version[] result = aVersions;

        if (aVersions == TransactionManager.OBJECTS_VERSIONS) {
            for (int i = bVersions.length - 1; i >= 0; i--) {
                if (bVersions[i] != null) {
                    Version shared = (Version) bVersions[i].object().shared_();
                    Version merged = merge(shared, bVersions[i], false);

                    if (Debug.ENABLED)
                        Debug.assertion(merged == shared);
                }
            }
        } else {
            for (int i = bVersions.length - 1; i >= 0; i--) {
                if (bVersions[i] != null) {
                    TObject object = bVersions[i].object();
                    Version aVersion = TransactionBase.getVersion(result, object);

                    if (aVersion != null) {
                        Version merged = aVersion;
                        merged = merge(aVersion, merged, bVersions[i], false);

                        /*
                         * If merged is new version, some of its content might have no
                         * visibility from other threads. Store merge in new array so
                         * aVersions is not modified.
                         */
                        if (merged != aVersion) {
                            if (result == aVersions) {
                                result = new Version[aVersions.length];

                                for (int t = result.length - 1; t >= 0; t--) {
                                    if (aVersions[t] != aVersion)
                                        result[t] = aVersions[t];
                                    else
                                        result[t] = merged;
                                }
                            } else {
                                int index = TransactionBase.getIndex(result, object);

                                if (Debug.ENABLED)
                                    Debug.assertion(result[index] == aVersion);

                                result[index] = merged;
                            }
                        }
                    } else
                        result = TransactionBase.putVersion(result, bVersions[i]);
                }
            }
        }

        if (a._transaction != null) {
            dispose(a._transaction);

            if (Debug.THREADS)
                ThreadAssert.addPrivate(a);

            a._transaction = null;

            if (Debug.THREADS)
                ThreadAssert.removePrivate(a);
        }

        if (b._transaction != null) {
            dispose(b._transaction);

            if (Debug.THREADS)
                ThreadAssert.addPrivate(b);

            b._transaction = null;

            if (Debug.THREADS)
                ThreadAssert.removePrivate(b);
        }

        if (Debug.ENABLED)
            Debug.assertion(result != null);

        return result;
    }

    final void onAborted() {
        if (_transaction != null)
            if (_mergeInfo.compareAndSet(MERGE_DEFAULT, MERGE_DONE))
                dispose(_transaction);
    }

    private static void dispose(Transaction transaction) {
        if (transaction != null) {
            if (Debug.THREADS) {
                ThreadAssert.removeShared(transaction);
                ThreadAssert.addPrivate(transaction);
                Debug.assertion(transaction.isCommitted());
            }

            Workspace workspace = transaction.workspace();

            if (Debug.ENABLED)
                Debug.assertion(workspace.transaction() == null);

            transaction.reset();

            if (Debug.THREADS)
                ThreadAssert.removePrivate(transaction);

            workspace.recycle(transaction);
        }
    }

    static Version merge(Version target, Version source, boolean threadPrivate) {
        return merge(target, target, source, threadPrivate);
    }

    private static Version merge(Version target, Version merged, Version source, boolean threadPrivate) {
        List sourceWrites, sourceClones, targetWrites = null;

        if (Debug.ENABLED) {
            sourceWrites = new List();
            targetWrites = new List();
            source.getContentForDebug(sourceWrites);
            target.getContentForDebug(targetWrites);
            sourceClones = cloneArrays(sourceWrites);

            if (target == target.object().shared_())
                Debug.assertion(!threadPrivate);
        }

        Version result = merged.merge(merged, source, threadPrivate);

        if (Debug.ENABLED) {
            result.checkInvariants();

            /*
             * No need to clone if private.
             */
            if (threadPrivate)
                Debug.assertion(result == merged);

            if (target == target.object().shared_())
                Debug.assertion(result == target);

            /*
             * TODO: Test if would be better than copying content when 'merged' is fully
             * overriden by 'source'. For now it complicates merge methods and 'source' is
             * younger so it should be GC instead of 'target'.
             */
            Debug.assertion(result != source);

            List sourceWrites2 = new List();
            List targetWrites2 = new List();
            source.getContentForDebug(sourceWrites2);
            target.getContentForDebug(targetWrites2);

            /*
             * Assert source has not be modified.
             */
            assertNoChange(sourceWrites, sourceClones, sourceWrites2);

            /*
             * Assert that even if target has been modified, the objects themselves are
             * the same or from source. New arrays would not be visible to other threads.
             */
            if (!threadPrivate)
                if (!(target instanceof TKeyedSharedVersion))
                    for (int i = 0; i < targetWrites.size(); i++)
                        if (targetWrites.get(i) != null && targetWrites.get(i).getClass().isArray())
                            Debug.assertion(targetWrites2.get(i) == targetWrites.get(i) || sourceWrites.contains(targetWrites2.get(i)));
        }

        return result;
    }

    private static List cloneArrays(List array) {
        List clone = new List();

        for (int i = 0; i < array.size(); i++) {
            Object element = array.get(i);

            if (element instanceof Object[])
                clone.add(Platform.get().clone((Object[]) element));
            else
                clone.add(element);
        }

        return clone;
    }

    private static void assertNoChange(List list, List clones, List list2) {
        Debug.assertion(list.size() == list2.size());

        for (int i = 0; i < list.size(); i++) {
            Object element = list.get(i);

            if (element instanceof Integer)
                Debug.assertion(element.equals(list2.get(i)));
            else
                Debug.assertion(element == list2.get(i));

            if (element instanceof Object[])
                Debug.assertion(Platform.get().shallowEquals(element, clones.get(i), Platform.get().objectArrayClass()));
        }
    }

    @Override
    public String toString() {
        return Platform.get().defaultToString(this);
    }
}