/**
* 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<ThreadAssert> _current;
private static final PlatformConcurrentMap<RefEqual, ThreadAssert> _map;
private static final PlatformConcurrentMap<RefEqual, Object> _shared;
private static final PlatformMap<RefEqual, List<Object>> _exchangedObjects;
private static final PlatformConcurrentMap<RefEqual, ThreadAssert> _suspendedContexts;
private static final PlatformThreadLocal<Object> _ownerKey;
private static final PlatformMap<Object, Object> _sharedDefinitively;
private final AtomicReference<Object> _owner = new AtomicReference<Object>();
private final PlatformConcurrentMap<RefEqual, Object> _objects = new PlatformConcurrentMap<RefEqual, Object>();
// Various debug helpers
private final ArrayList<Boolean> _overrideAssertBools = new ArrayList<Boolean>();
private final ArrayList<Object> _overrideAssertKeys = new ArrayList<Object>();
private final HashMap<Object, Long> _readersDebugCounters = new HashMap<Object, Long>();
private final HashMap<Object, Long> _writersDebugCounters = new HashMap<Object, Long>();
static {
if (Debug.ENABLED) {
_current = new PlatformThreadLocal<ThreadAssert>();
_map = new PlatformConcurrentMap<RefEqual, ThreadAssert>();
_shared = new PlatformConcurrentMap<RefEqual, Object>();
_sharedDefinitively = new PlatformMap<Object, Object>();
_exchangedObjects = new PlatformMap<RefEqual, List<Object>>();
_suspendedContexts = new PlatformConcurrentMap<RefEqual, ThreadAssert>();
_ownerKey = new PlatformThreadLocal<Object>();
} 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<Object> 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<Object> 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<Object> 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<Object> queue = _exchangedObjects.get(wrapper);
if (queue == null)
_exchangedObjects.put(wrapper, queue = new List<Object>());
queue.add(object);
}
}
static void exchangeGiveList(Object key, List<Object> list) {
if (!Debug.THREADS)
throw new IllegalStateException();
for (int i = 0; i < list.size(); i++)
exchangeGive(key, list.get(i));
}
static List<Object> exchangeTake(Object key) {
if (!Debug.THREADS)
throw new IllegalStateException();
List<Object> 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);
ThreadAssert previous = _suspendedContexts.put(wrapper, context);
Debug.assertion(previous == 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<Boolean> getOverrideAssertBools() {
return _overrideAssertBools;
}
ArrayList<Object> 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();
}
}