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

org.objectfabric.Helper 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.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;

import org.objectfabric.Continuation.IntBox;
import org.objectfabric.Memory.DefaultBackend;
import org.objectfabric.TObject.Transaction;
import org.objectfabric.TObject.Version;

/**
 * Utility functions and debug helper.
 */
@SuppressWarnings("rawtypes")
final class Helper {

    private static final Helper _instance;

    String ProcessName = "";

    boolean ConflictRandom, ConflictAlways;

    boolean TestingReset, FailReset, LastResetFailed;

    boolean AssertNoConflict;

    final PlatformConcurrentMap Resources = new PlatformConcurrentMap();

    final PlatformConcurrentMap Memories = new PlatformConcurrentMap();

    private final PlatformMap> _watchers;

    private final PlatformConcurrentMap> _validated;

    private final PlatformConcurrentMap _callbacks;

    private final PlatformConcurrentMap _toRecycle = new PlatformConcurrentMap();

    private final PlatformThreadLocal _retryCount;

    private final PlatformThreadLocal _expectedClass;

    private final PlatformThreadLocal _disableEqualsOrHashCheck;

    private final PlatformThreadLocal _sb;

    private final PlatformConcurrentMap _allowExistingReadsOrWrites;

    private final PlatformConcurrentMap _lock = new PlatformConcurrentMap();

    private final PlatformConcurrentMap _readers = new PlatformConcurrentMap();

    static final Snapshot TO_MERGE_LATER_IS_NULL = new Snapshot();

    /*
     * TODO: replace by additional CAS in VersionMap to hand merge to thread currently
     * merging to map.
     */
    private final PlatformConcurrentMap _toMergeLater;

    // TODO move all debug variables in classes to thread locals here

    Transaction _empty;

    private Helper() {
        if (!Debug.ENABLED)
            throw new IllegalStateException();

        _watchers = new PlatformMap>();
        _validated = new PlatformConcurrentMap>();
        _callbacks = new PlatformConcurrentMap();
        _retryCount = new PlatformThreadLocal();
        _expectedClass = new PlatformThreadLocal();
        _disableEqualsOrHashCheck = new PlatformThreadLocal();
        _sb = new PlatformThreadLocal();
        _allowExistingReadsOrWrites = new PlatformConcurrentMap();
        _toMergeLater = new PlatformConcurrentMap();
    }

    static {
        if (Debug.ENABLED)
            _instance = new Helper();
        else
            _instance = null;
    }

    static Helper instance() {
        return _instance;
    }

    static VersionMap[] addVersionMap(VersionMap[] array, VersionMap map) {
        VersionMap[] update = new VersionMap[array.length + 1];

        // Copy maps already acknowledged

        Platform.arraycopy(array, 0, update, 0, array.length);

        // Insert new map after last acknowledged one

        update[update.length - 1] = map;

        // Search nulls to assert indexes, races etc...

        if (Debug.ENABLED)
            Debug.assertion(!Arrays.asList(update).contains(null));

        return update;
    }

    static VersionMap[] removeVersionMap(VersionMap[] array, int index) {
        VersionMap[] update = new VersionMap[array.length - 1];
        Platform.arraycopy(array, 0, update, 0, index);
        Platform.arraycopy(array, index + 1, update, index, array.length - 1 - index);

        // Search nulls to assert indexes, races etc...

        if (Debug.ENABLED)
            Debug.assertion(!Arrays.asList(update).contains(null));

        return update;
    }

    static Version[][] addVersions(Version[][] array, Version[] versions) {
        Version[][] update = new Version[array.length + 1][];

        // Copy version arrays already acknowledged
        Platform.arraycopy(array, 0, update, 0, array.length);

        // Insert new array after last acknowledged one
        update[update.length - 1] = versions;

        return update;
    }

    static Version[][] removeVersions(Version[][] array, int index) {
        Version[][] update = new Version[array.length - 1][];
        Platform.arraycopy(array, 0, update, 0, index);
        Platform.arraycopy(array, index + 1, update, index, array.length - 1 - index);
        return update;
    }

    /**
     * TODO: hash or sort?
     */
    static int getIndex(Snapshot snapshot, VersionMap map) {
        for (int i = snapshot.getVersionMaps().length - 1; i >= 0; i--)
            if (snapshot.getVersionMaps()[i] == map)
                return i;

        throw new IllegalStateException();
    }

    static boolean validateCheckOnce(VersionMap map, Version[] reads, Snapshot snapshot, int start, int stop) {
        if (Debug.ENABLED) {
            Debug.assertion(start >= 0 && stop >= 0 && start <= stop);

            /*
             * Check we do not validate twice against the same map.
             */
            RefEqual wrapper = new RefEqual(map);

            for (int i = start; i < stop; i++) {
                PlatformConcurrentMap maps = instance()._validated.get(wrapper);

                if (maps == null) {
                    maps = new PlatformConcurrentMap();
                    Debug.assertion(instance()._validated.put(wrapper, maps) == null);
                }

                VersionMap m = snapshot.getVersionMaps()[i];
                Debug.assertion(maps.put(m, m) == null);
            }
        }

        return validate(map, reads, snapshot, start, stop);
    }

    static boolean validate(VersionMap map, Version[] reads, Snapshot snapshot, int start, int stop) {
        if (start < stop)
            if (reads != null)
                for (int i = reads.length - 1; i >= 0; i--)
                    if (reads[i] != null)
                        if (!reads[i].validAgainst(map, snapshot, start, stop))
                            return false;

        return true;
    }

    //

    static Extension[] add(Extension[] array, Extension value) {
        if (Debug.ENABLED)
            Debug.assertion(!contains(array, value));

        Extension[] update;

        if (array != null) {
            update = new Extension[array.length + 1];
            Platform.arraycopy(array, 0, update, 0, array.length);
            update[update.length - 1] = value;
        } else {
            update = new Extension[1];
            update[0] = value;
        }

        return update;
    }

    static Extension[] remove(Extension[] array, Extension value) {
        if (Debug.ENABLED)
            Debug.assertion(contains(array, value));

        if (array.length == 1)
            return null;

        Extension[] update = new Extension[array.length - 1];
        int j = 0;

        for (int i = 0; i < array.length; i++)
            if (array[i] != value)
                update[j++] = array[i];

        if (Debug.ENABLED)
            Debug.assertion(j == update.length);

        return update;
    }

    static boolean contains(Extension[] array, Extension value) {
        if (!Debug.ENABLED)
            throw new IllegalStateException();

        if (array == null)
            return false;

        for (int i = 0; i < array.length; i++)
            if (array[i] == value)
                return true;

        return false;
    }

    static Actor[] add(Actor[] array, Actor value) {
        if (Debug.ENABLED)
            Debug.assertion(!contains(array, value));

        Actor[] update;

        if (array != null) {
            update = new Actor[array.length + 1];
            Platform.arraycopy(array, 0, update, 0, array.length);
            update[update.length - 1] = value;
        } else {
            update = new Actor[1];
            update[0] = value;
        }

        return update;
    }

    static Actor[] remove(Actor[] array, Actor value) {
        if (Debug.ENABLED)
            Debug.assertion(contains(array, value));

        if (array.length == 1)
            return null;

        Actor[] update = new Actor[array.length - 1];
        int j = 0;

        for (int i = 0; i < array.length; i++)
            if (array[i] != value)
                update[j++] = array[i];

        if (Debug.ENABLED)
            Debug.assertion(j == update.length);

        return update;
    }

    private static boolean contains(Actor[] array, Actor value) {
        if (!Debug.ENABLED)
            throw new RuntimeException();

        if (array == null)
            return false;

        for (int i = 0; i < array.length; i++)
            if (array[i] == value)
                return true;

        return false;
    }

    static URIHandler[] add(URIHandler[] array, URIHandler value) {
        URIHandler[] update;

        if (array != null) {
            update = new URIHandler[array.length + 1];
            Platform.arraycopy(array, 0, update, 0, array.length);
            update[update.length - 1] = value;
        } else {
            update = new URIHandler[1];
            update[0] = value;
        }

        return update;
    }

    static URIHandler[] add(int index, URIHandler[] array, URIHandler value) {
        URIHandler[] update;

        if (array != null) {
            update = new URIHandler[array.length + 1];
            Platform.arraycopy(array, 0, update, 0, index);
            update[index] = value;
            Platform.arraycopy(array, index, update, index + 1, array.length - index);
        } else {
            update = new URIHandler[1];
            update[index] = value;
        }

        return update;
    }

    static Location[] add(Location[] array, Location value) {
        Location[] update;

        if (array != null) {
            update = new Location[array.length + 1];
            Platform.arraycopy(array, 0, update, 0, array.length);
            update[update.length - 1] = value;
        } else {
            update = new Location[1];
            update[0] = value;
        }

        return update;
    }

    //

    static int[] extend(int[] array) {
        int[] result = new int[array.length << OpenMap.TIMES_TWO_SHIFT];
        Platform.arraycopy(array, 0, result, 0, array.length);
        return result;
    }

    static long[] extend(long[] array) {
        long[] result = new long[array.length << OpenMap.TIMES_TWO_SHIFT];
        Platform.arraycopy(array, 0, result, 0, array.length);
        return result;
    }

    static TObject[] extend(TObject[] array) {
        TObject[] temp = new TObject[array.length << OpenMap.TIMES_TWO_SHIFT];
        Platform.arraycopy(array, 0, temp, 0, array.length);
        return temp;
    }

    static Resource[] extend(Resource[] array) {
        Resource[] temp = new Resource[array.length << OpenMap.TIMES_TWO_SHIFT];
        Platform.arraycopy(array, 0, temp, 0, array.length);
        return temp;
    }

    static TObject[][] extend(TObject[][] array) {
        TObject[][] temp = new TObject[array.length << OpenMap.TIMES_TWO_SHIFT][];
        Platform.arraycopy(array, 0, temp, 0, array.length);
        return temp;
    }

    /*
     * Debug.
     */

    void removeValidated(VersionMap map) {
        RefEqual wrapper = new RefEqual(map);
        _validated.remove(wrapper);
    }

    void checkFieldsHaveDefaultValues(final Transaction transaction) {
        if (_empty == null) {
            _empty = new Transaction(transaction.workspace(), null);

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

        boolean ok = true;

        try {
            ok &= Platform.get().shallowEquals(_empty, transaction, Platform.get().transactionBaseClass(), "_workspace", "_parent");
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }

        if (!TestingReset)
            Debug.assertion(ok);
        else {
            if (FailReset)
                LastResetFailed = true;
            else
                LastResetFailed = false;
        }
    }

    //

    int getExpectedClass() {
        return _expectedClass.get();
    }

    void setExpectedClass(int value) {
        _expectedClass.set(value);
    }

    boolean allowEqualsOrHash() {
        Boolean value = _disableEqualsOrHashCheck.get();

        if (value != null && value)
            return true;

        return false;
    }

    void disableEqualsOrHashCheck() {
        _disableEqualsOrHashCheck.set(true);
    }

    void enableEqualsOrHashCheck() {
        _disableEqualsOrHashCheck.set(false);
    }

    StringBuilder getSB() {
        StringBuilder sb = _sb.get();

        if (sb == null)
            _sb.set(sb = new StringBuilder());

        return sb;
    }

    void assertClassLoaderIdle() {
        Debug.assertion(_disableEqualsOrHashCheck.get() == null || !_disableEqualsOrHashCheck.get());
        List workspaces = new List();

        synchronized (_watchers) {
            for (List value : _watchers.values()) {
                Workspace workspace = (Workspace) value.get(0);

                if (workspace != null) {
                    Debug.assertion(!workspaces.contains(workspace));
                    workspaces.add(workspace);
                }
            }
        }

        for (int i = 0; i < workspaces.size(); i++)
            assertWorkspaceIdle(workspaces.get(i));

        if (Debug.THREADS) {
            for (int i = 0; i < workspaces.size(); i++) {
                Snapshot snapshot = workspaces.get(i).snapshot();
                ThreadAssert.removeShared(snapshot.getVersionMaps()[TransactionManager.OBJECTS_VERSIONS_INDEX]);
            }

            List exceptions = new List();

            for (ThreadContext instance : ThreadContext.getInstances())
                exceptions.addAll(instance.getThreadContextObjects());

            ThreadAssert.assertIdle(exceptions);
        }

        synchronized (_watchers) {
            _watchers.clear();
        }

        Debug.assertAlways(InFlight.idle());

        for (Entry entry : Memories.entrySet())
            for (Object o : entry.getValue().Map.values())
                entry.getKey().onEviction(o);

        Memories.clear();

        for (WeakReference ref : ClientURIHandler.remotes().values()) {
            Remote remote = ref.get();

            if (remote != null)
                for (URI uri : remote.uris().values())
                    Debug.assertion(uri.resources() == null);
        }

        if (Stats.ENABLED) {
            Debug.assertion(Stats.Instance.ConnectionQueues.get() == 0);
            Debug.assertion(Stats.Instance.BlockQueues.get() == 0);
        }

        Debug.assertion(Resources.size() == 0);
        Debug.assertion(_validated.size() == 0);
        Debug.assertion(_callbacks.size() == 0);
        Debug.assertion(_toRecycle.size() == 0);
        Debug.assertion(_lock.size() == 0);

        _readers.clear();

        ThreadAssert.getOrCreateCurrent().resetCounters();
        Debug.assertion(!AssertNoConflict);
        Debug.assertion(!ExpectedExceptionThrower.isCounterDisabled());
        Debug.assertion(ProcessName.length() == 0);
    }

    void assertWorkspaceIdle(Workspace workspace) {
        workspace.assertIdle();

        Snapshot snapshot = workspace.snapshot();
        Debug.assertion(snapshot.getVersionMaps().length == 2);
        Debug.assertion(snapshot.getVersionMaps()[1] == VersionMap.CLOSING);
        Debug.assertion(snapshot.getReads() == null);
        Debug.assertion(snapshot.writes().length == 2);
        Debug.assertion(snapshot.writes()[0] == TransactionManager.OBJECTS_VERSIONS);
        Debug.assertion(snapshot.writes()[1] == null);
        Debug.assertion(snapshot.getVersionMaps()[0].getTransaction() == null);

        if (snapshot.slowChanging() != null) {
            Debug.assertion(snapshot.slowChanging().Extensions == null);
            Debug.assertion(snapshot.slowChanging().Splitters == null);
        }

        for (Origin origin : workspace.resolver().origins().keySet())
            for (URI uri : origin.uris().values())
                Debug.assertion(!uri.contains(workspace));

        for (Resource resource : Resources.keySet()) {
            if (resource.workspaceImpl() == workspace) {
                resource.assertIdle();
                Resources.remove(resource);
            }
        }

        synchronized (_watchers) {
            Debug.assertion(_watchers.containsKey(snapshot.getVersionMaps()[0]));
        }
    }

    void toRecycle(Object object) {
        RefEqual wrapper = new RefEqual(object);
        RefEqual previous = _toRecycle.put(wrapper, wrapper);

        if (Debug.ENABLED)
            Debug.assertion(previous == null);
    }

    void onRecycled(Object object) {
        RefEqual previous = _toRecycle.remove(new RefEqual(object));

        if (Debug.ENABLED) {
            Debug.assertion(previous != null);
            Debug.assertion(!_toRecycle.containsKey(new RefEqual(object)));
        }
    }

    void addWatcher(VersionMap map, Object watcher, Snapshot snapshot, String reason) {
        if (Debug.ENABLED)
            Debug.assertion(watcher != null);

        Object[] watchers;

        synchronized (_watchers) {
            List list = _watchers.get(map);

            if (list == null) {
                list = new List();
                Object previous = _watchers.get(map);
                _watchers.put(map, list);
                Debug.assertion(previous == null);
            }

            list.add(watcher);

            if (Debug.STM_LOG)
                watchers = list.toArray();
        }

        if (Debug.STM_LOG)
            Log.write("+1 " + (watchers.length) + " on map " + map + ", queue: " + snapshot.getVersionMaps().length + " (" + watcher + "): " + reason + " (" + Arrays.toString(watchers) + ")");
    }

    void removeWatcher(VersionMap map, Object watcher, Snapshot snapshot, String reason) {
        if (Debug.ENABLED)
            Debug.assertion(watcher != null);

        Object[] watchers;

        synchronized (_watchers) {
            List list = _watchers.get(map);
            int index = -1;

            for (int i = 0; i < list.size(); i++)
                if (list.get(i) == watcher)
                    index = i;

            list.remove(index);

            if (Debug.STM_LOG)
                watchers = list.toArray();

            if (list.size() == 0)
                _watchers.remove(map);
        }

        if (Debug.STM_LOG)
            Log.write("-1 " + (watchers.length) + " on map " + map + ", queue: " + snapshot.getVersionMaps().length + " (" + watcher + "): " + reason + " (" + Arrays.toString(watchers) + ")");
    }

    //

    int getRetryCount() {
        Integer boxed = _retryCount.get();

        if (boxed != null)
            return boxed;

        return 0;
    }

    void setRetryCount(int value) {
        _retryCount.set(value);
    }

    void checkVersionsHaveWrites(TObject.Version[] versions) {
        for (int i = versions.length - 1; i >= 0; i--) {
            if (versions[i] != null) {
                List list = new List();
                versions[i].getContentForDebug(list);
                Debug.assertion(list.size() > 0);
            }
        }
    }

    PlatformConcurrentMap getAllowExistingReadsOrWrites() {
        return _allowExistingReadsOrWrites;
    }

    PlatformConcurrentMap getToMergeLater() {
        return _toMergeLater;
    }

    PlatformConcurrentMap getLocks() {
        return _lock;
    }

    /*
     * Little state machine to assert readers closing sequence.
     */
    static final int IDLE = 0, READING = 1, CLOSE_WHEN_DONE = 2, CLOSED = 3;

    private AtomicInteger getRead(Connection connection) {
        AtomicInteger state = _readers.get(connection);

        if (state == null) {
            AtomicInteger previous = _readers.putIfAbsent(connection, state = new AtomicInteger());

            if (previous != null)
                state = previous;
        }

        return state;
    }

    boolean startRead(Connection connection) {
        return getRead(connection).compareAndSet(IDLE, READING);
    }

    boolean stopRead(Connection connection) {
        AtomicInteger state = getRead(connection);
        boolean result = state.compareAndSet(READING, IDLE);

        if (!result)
            Debug.assertion(state.get() == CLOSE_WHEN_DONE);

        return result;
    }

    void assertReadClosing(Connection connection) {
        int state = getRead(connection).get();
        Debug.assertion(state == CLOSE_WHEN_DONE || state == CLOSED, "" + state);
    }

    boolean closeRead(Connection connection) {
        AtomicInteger state = getRead(connection);

        for (;;) {
            int value = state.get();

            switch (value) {
                case IDLE: {
                    if (state.compareAndSet(value, CLOSED))
                        return true;

                    break;
                }
                case READING: {
                    if (state.compareAndSet(value, CLOSE_WHEN_DONE))
                        return false;

                    break;
                }
                case CLOSE_WHEN_DONE:
                case CLOSED:
                    return false;
            }
        }
    }

    //

    void addCallback(Object callback) {
        Debug.assertion(_callbacks.putIfAbsent(callback, callback) == null);
    }

    void removeCallback(Object callback) {
        Debug.assertion(_callbacks.remove(callback) == callback);
    }
}