![JAR search and dependency download from the Maven repository](/logo.png)
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
© 2015 - 2025 Weber Informatics LLC | Privacy Policy