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

org.objectfabric.ThreadAssert 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.ArrayList;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Debug purposes. Allows objects to be associated to threads, and asserts that only this
 * thread access them. When a context switches thread, a common object needs to be given
 * to ensure that the switch is expected on both sides.
 */
final class ThreadAssert {

    /**
     * Notifies AspectJ checks that this object should be used in an single threaded way.
     */
    @interface SingleThreaded {
    }

    /**
     * Notifies AspectJ checks that this object should be used in an single threaded way,
     * and can be shared at some point between threads. Only reads are allowed on fields
     * once instance has been shared.
     */
    @interface SingleThreadedThenShared {
    }

    @interface AllowSharedRead {
    }

    @interface AllowSharedWrite {
    }

    private static final PlatformThreadLocal _current;

    private static final PlatformConcurrentMap _map;

    private static final PlatformConcurrentMap _shared;

    private static final PlatformMap> _exchangedObjects;

    private static final PlatformConcurrentMap _suspendedContexts;

    private static final PlatformThreadLocal _ownerKey;

    private static final PlatformMap _sharedDefinitively;

    private final AtomicReference _owner = new AtomicReference();

    private final PlatformConcurrentMap _objects = new PlatformConcurrentMap();

    // Various debug helpers

    private final ArrayList _overrideAssertBools = new ArrayList();

    private final ArrayList _overrideAssertKeys = new ArrayList();

    private final HashMap _readersDebugCounters = new HashMap();

    private final HashMap _writersDebugCounters = new HashMap();

    static {
        if (Debug.ENABLED) {
            _current = new PlatformThreadLocal();
            _map = new PlatformConcurrentMap();
            _shared = new PlatformConcurrentMap();
            _sharedDefinitively = new PlatformMap();
            _exchangedObjects = new PlatformMap>();
            _suspendedContexts = new PlatformConcurrentMap();
            _ownerKey = new PlatformThreadLocal();
        } else {
            _current = null;
            _map = null;
            _shared = null;
            _sharedDefinitively = null;
            _exchangedObjects = null;
            _suspendedContexts = null;
            _ownerKey = null;
        }
    }

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

    static ThreadAssert getOrCreateCurrent() {
        if (!Debug.ENABLED)
            throw new IllegalStateException();

        ThreadAssert current = _current.get();

        if (current == null) {
            current = new ThreadAssert();
            current._owner.set(getOwnerKey());
            _current.set(current);
        }

        return current;
    }

    /**
     * Replaces Thread.currentThread, which is not part of PlatformThread.
     */
    private static Object getOwnerKey() {
        if (!Debug.ENABLED)
            throw new IllegalStateException();

        Object key = _ownerKey.get();

        if (key == null)
            _ownerKey.set(key = new Object());

        return key;
    }

    static boolean isCurrentEmpty() {
        if (!Debug.THREADS)
            throw new IllegalStateException();

        ThreadAssert current = _current.get();
        return current == null || current._objects.size() == 0;
    }

    @SuppressWarnings("null")
    static void assertCurrentIsEmpty() {
        if (!Debug.THREADS)
            throw new IllegalStateException();

        ThreadAssert current = _current.get();
        int size = current != null ? current._objects.size() : 0;

        if (size > 0)
            for (Object object : current._objects.values())
                Debug.fail(object.toString());
    }

    static void assertIdle(List exceptions) {
        if (!Debug.THREADS)
            throw new IllegalStateException();

        assertCurrentIsEmpty();

        for (RefEqual key : _map.keySet()) {
            boolean ok = false;

            for (int i = 0; i < exceptions.size(); i++)
                if (key.getObject() == exceptions.get(i))
                    ok = true;

            if (!ok)
                Debug.fail("not empty: " + key.getObject());
        }

        for (RefEqual key : _shared.keySet()) {
            boolean ok = false;

            synchronized (_sharedDefinitively) {
                if (_sharedDefinitively.containsKey(key))
                    ok = true;
            }

            for (int i = 0; i < exceptions.size(); i++)
                if (key.getObject() == exceptions.get(i))
                    ok = true;

            if (!ok)
                Debug.fail(_shared.toString() + Platform.get().lineSeparator() + key.getObject().toString());
        }

        Debug.assertion(_exchangedObjects.size() == 0);
    }

    static void addPrivate(Object object) {
        Debug.assertion(!(object instanceof List));
        Debug.assertion(add(object));
    }

    static void addPrivateList(List list) {
        if (!Debug.THREADS)
            throw new IllegalStateException();

        for (int i = 0; i < list.size(); i++)
            Debug.assertion(add(list.get(i)));
    }

    static boolean addPrivateIfNot(Object object) {
        return add(object);
    }

    private static boolean add(Object object) {
        if (!Debug.THREADS)
            throw new IllegalStateException();

        if (Debug.THREADS_LOG)
            Log.write("add(" + Platform.get().defaultToString(object) + ")");

        ThreadAssert current = getOrCreateCurrent();
        RefEqual wrapper = new RefEqual(object);
        ThreadAssert previous = _map.putIfAbsent(wrapper, current);
        Debug.assertion(previous == null || previous == current);

        if (previous == null) {
            Debug.assertion(current._objects.putIfAbsent(wrapper, object) == null);
            return true;
        }

        return false;
    }

    static void assertPrivate(Object object) {
        if (!Debug.THREADS)
            throw new IllegalStateException();

        if (Debug.THREADS_LOG)
            Log.write("assertPrivate(" + Platform.get().defaultToString(object) + ")");

        Debug.assertion(isPrivate(object));
    }

    static void assertPrivateOrShared(Object object) {
        if (!Debug.THREADS)
            throw new IllegalStateException();

        if (Debug.THREADS_LOG)
            Log.write("assertPrivateOrShared(" + Platform.get().defaultToString(object) + ")");

        if (!isPrivate(object))
            assertShared(object);
    }

    static boolean isPrivate(Object object) {
        if (!Debug.THREADS)
            throw new IllegalStateException();

        ThreadAssert current = _current.get();

        if (current != null) {
            Debug.assertion(current._owner.get() == getOwnerKey());
            RefEqual wrapper = new RefEqual(object);

            if (_map.get(wrapper) == current) {
                Debug.assertion(current._objects.get(wrapper) == object);
                return true;
            }
        }

        return false;
    }

    static void removePrivate(Object object) {
        if (!Debug.THREADS)
            throw new IllegalStateException();

        if (Debug.THREADS_LOG)
            Log.write("remove(" + Platform.get().defaultToString(object) + ")");

        Debug.assertion(!(object instanceof List));
        ThreadAssert current = _current.get();
        Debug.assertion(current._owner.get() == getOwnerKey());
        RefEqual wrapper = new RefEqual(object);
        ThreadAssert previous = _map.remove(wrapper);
        Debug.assertion(previous == current);
        Debug.assertion(current._objects.remove(wrapper) == object);
    }

    static void removePrivateList(List list) {
        if (!Debug.THREADS)
            throw new IllegalStateException();

        for (int i = 0; i < list.size(); i++)
            removePrivate(list.get(i));
    }

    //

    static void share(Object object) {
        removePrivate(object);
        RefEqual wrapper = new RefEqual(object);
        Debug.assertion(_shared.putIfAbsent(wrapper, object) == null);
    }

    static void assertShared(Object object) {
        if (!Debug.THREADS)
            throw new IllegalStateException();

        RefEqual wrapper = new RefEqual(object);

        if (!_shared.containsKey(wrapper)) {
            synchronized (_sharedDefinitively) {
                Debug.assertion(_sharedDefinitively.containsKey(object));
            }
        }
    }

    static void removeShared(Object object) {
        if (!Debug.THREADS)
            throw new IllegalStateException();

        RefEqual wrapper = new RefEqual(object);
        Debug.assertion(_shared.remove(wrapper) == object);
    }

    static void addSharedDefinitively(Object object) {
        if (!Debug.THREADS)
            throw new IllegalStateException();

        synchronized (_sharedDefinitively) {
            Object previous = _sharedDefinitively.put(object, null);
            Debug.assertion(previous == null);
        }
    }

    //

    static void assertCleaned(Object object) {
        if (!Debug.THREADS)
            throw new IllegalStateException();

        if (Debug.THREADS_LOG)
            Log.write("assertCleaned(" + Platform.get().defaultToString(object) + ")");

        RefEqual wrapper = new RefEqual(object);
        Debug.assertion(_map.get(wrapper) == null);

        ThreadAssert current = _current.get();

        if (current != null) {
            Debug.assertion(current._owner.get() == getOwnerKey());
            Debug.assertion(current._objects.get(wrapper) == null);
        }

        Debug.assertion(_shared.get(wrapper) == null);
    }

    //

    static void exchangeGive(Object key, Object object) {
        if (!Debug.THREADS)
            throw new IllegalStateException();

        Debug.assertion(!(object instanceof List));
        removePrivate(object);

        synchronized (_exchangedObjects) {
            RefEqual wrapper = new RefEqual(key);
            List queue = _exchangedObjects.get(wrapper);

            if (queue == null)
                _exchangedObjects.put(wrapper, queue = new List());

            queue.add(object);
        }
    }

    static void exchangeGiveList(Object key, List list) {
        if (!Debug.THREADS)
            throw new IllegalStateException();

        for (int i = 0; i < list.size(); i++)
            exchangeGive(key, list.get(i));
    }

    static List exchangeTake(Object key) {
        if (!Debug.THREADS)
            throw new IllegalStateException();

        List queue = null;

        synchronized (_exchangedObjects) {
            RefEqual wrapper = new RefEqual(key);
            queue = _exchangedObjects.remove(wrapper);
        }

        if (queue != null)
            for (int i = 0; i < queue.size(); i++)
                addPrivate(queue.get(i));

        return queue;
    }

    /*
     * Whole context operations, used also to debug non thread related stuff.
     */

    static void suspend(Object key) {
        if (!Debug.ENABLED)
            throw new IllegalStateException();

        if (Debug.THREADS_LOG)
            Log.write("suspend(" + key + ")");

        ThreadAssert context = getOrCreateCurrent();
        Debug.assertion(context._owner.compareAndSet(getOwnerKey(), null));
        RefEqual wrapper = new RefEqual(key);
        Debug.assertion(_suspendedContexts.put(wrapper, context) == null);
        _current.set(null);
    }

    static void resume(Object key) {
        resume(key, true);
    }

    static void resume(Object key, boolean assertExists) {
        if (!Debug.ENABLED)
            throw new IllegalStateException();

        if (Debug.THREADS_LOG)
            Log.write("resume(" + key + ")");

        if (Debug.THREADS)
            assertCurrentIsEmpty();

        RefEqual wrapper = new RefEqual(key);
        ThreadAssert context = _suspendedContexts.remove(wrapper);

        if (Debug.ENABLED && assertExists)
            Debug.assertion(context != null);

        if (context != null) {
            Debug.assertion(context._owner.compareAndSet(null, getOwnerKey()));
            _current.set(context);
        }
    }

    //

    ArrayList getOverrideAssertBools() {
        return _overrideAssertBools;
    }

    ArrayList getOverrideAssertKeys() {
        return _overrideAssertKeys;
    }

    //

    long getReaderDebugCounter(Object reader) {
        if (!Debug.COMMUNICATIONS)
            throw new IllegalStateException();

        Long value = _readersDebugCounters.get(reader);
        return value != null ? value : 0;
    }

    long getAndIncrementReaderDebugCounter(Object reader) {
        long value = getReaderDebugCounter(reader);
        _readersDebugCounters.put(reader, value + 1);
        return value;
    }

    void resetReaderDebugCounter(Object reader) {
        if (!Debug.COMMUNICATIONS)
            throw new IllegalStateException();

        _readersDebugCounters.remove(reader);
    }

    //

    long getWriterDebugCounter(Object writer) {
        if (!Debug.COMMUNICATIONS)
            throw new IllegalStateException();

        Long value = _writersDebugCounters.get(writer);
        return value != null ? value : 0;
    }

    long getAndIncrementWriterDebugCounter(Object writer) {
        long value = getWriterDebugCounter(writer);
        _writersDebugCounters.put(writer, value + 1);
        return value;
    }

    void resetWriterDebugCounter(Object writer) {
        if (!Debug.COMMUNICATIONS)
            throw new IllegalStateException();

        _writersDebugCounters.remove(writer);
    }

    //

    void resetCounters() {
        _readersDebugCounters.clear();
        _writersDebugCounters.clear();
    }
}