/**
* 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 org.objectfabric.TObject.Transaction;
import org.objectfabric.TObject.Version;
import org.objectfabric.ThreadAssert.AllowSharedRead;
import org.objectfabric.ThreadAssert.SingleThreadedThenShared;
@SingleThreadedThenShared
abstract class TransactionBase {
static final int FLAG_IGNORE_READS = 1 << 0;
static final int FLAG_NO_WRITES = 1 << 1;
// TODO remove? (OK to start read-only nested)
static final int FLAG_COMMITTED = 1 << 2;
private final Workspace _workspace;
private final Transaction _parent;
private Snapshot _snapshot;
private Version[] _reads;
private Version[] _writes;
/*
* Copied from snapshot to save one indirection level.
*/
private Version[][] _publicSnapshotVersions;
/*
* Allows private snapshots, e.g. to start a child transaction, to iterate over a
* collection or to run a method remotely.
*/
private Version[][] _privateSnapshotVersions;
@AllowSharedRead
private int _flags;
// TODO needed in transaction? merge?
private VersionMap _map;
// !! Add new fields to reset()
TransactionBase(Workspace workspace, Transaction parent) {
if (workspace == null)
throw new IllegalArgumentException();
_workspace = workspace;
_parent = parent;
}
final Workspace workspace() {
return _workspace;
}
/**
* Parent if transaction is nested, null otherwise.
*/
final Transaction parent() {
return _parent;
}
/**
* Transaction must be cleaned because it is about to be reused, e.g. when committing
* after a method call on same machine. later.
*/
final void reset() {
if (Debug.ENABLED) {
Helper.instance().disableEqualsOrHashCheck();
Helper.instance().getAllowExistingReadsOrWrites().remove(this);
Helper.instance().enableEqualsOrHashCheck();
}
boolean failReset = false;
if (Debug.ENABLED)
if (Helper.instance().FailReset)
failReset = true;
_reads = null;
_writes = null;
if (!failReset)
_publicSnapshotVersions = null;
_privateSnapshotVersions = null;
_flags = 0;
_snapshot = null;
_map = null;
}
final int flags() {
return _flags;
}
final void flags(int value) {
if (Debug.ENABLED)
Debug.assertion(_flags == 0);
_flags = value;
}
final void addFlags(int value) {
_flags |= value;
}
final boolean ignoreReads() {
return (_flags & FLAG_IGNORE_READS) != 0;
}
final boolean noWrites() {
return (_flags & FLAG_NO_WRITES) != 0;
}
// Reads
final Version[] getReads() {
return _reads;
}
final void setReads(Version[] value) {
if (Debug.ENABLED) {
Debug.assertion(value == null || !ignoreReads());
Debug.assertion(_reads == null);
}
_reads = value;
}
final Version getRead(TObject object) {
if (_reads != null)
return getVersion(_reads, object);
return null;
}
final void putRead(Version read) {
if (Debug.ENABLED)
Debug.assertion(!ignoreReads());
if (_reads == null)
_reads = new Version[OpenMap.CAPACITY];
while (!tryToPut(_reads, read)) {
Version[] previous = _reads;
for (;;) {
_reads = new Version[_reads.length << OpenMap.TIMES_TWO_SHIFT];
if (rehash(previous, _reads))
break;
}
}
}
// Writes
final Version[] getWrites() {
return _writes;
}
final void setWrites(Version[] value) {
if (Debug.ENABLED)
Debug.assertion(!noWrites() || value == null);
_writes = value;
}
final Version getVersion(TObject object) {
if (_writes != null)
return getVersion(_writes, object);
return null;
}
static Version getVersion(Version[] versions, TObject object) {
int index = object.hash() & (versions.length - 1);
for (int i = OpenMap.attemptsStart(versions.length); i >= 0; i--) {
Version version = versions[index];
if (version == null)
return null;
if (version.object() == object)
return version;
index = (index + 1) & (versions.length - 1);
}
return null;
}
static int getIndex(Version[] versions, TObject object) {
int index = object.hash() & (versions.length - 1);
for (int i = OpenMap.attemptsStart(versions.length); i >= 0; i--) {
Version version = versions[index];
if (version == null)
return -1;
if (version.object() == object)
return index;
index = (index + 1) & (versions.length - 1);
}
return -1;
}
final void putVersion(Version version) {
if (Debug.ENABLED) {
Debug.assertion(getWrites() == null || getVersion(version.object()) == null);
Debug.assertion(!noWrites());
}
if (_writes == null) {
if (noWrites())
throw new RuntimeException(Strings.READ_ONLY);
_writes = new Version[OpenMap.CAPACITY];
}
_writes = putVersion(_writes, version);
}
static Version[] putVersion(Version[] versions, Version version) {
while (!tryToPut(versions, version)) {
Version[] previous = versions;
for (;;) {
versions = new Version[versions.length << OpenMap.TIMES_TWO_SHIFT];
if (rehash(previous, versions))
break;
}
}
return versions;
}
//
private static boolean tryToPut(Version[] versions, Version version) {
int index = version.object().hash() & (versions.length - 1);
if (Stats.ENABLED)
Stats.Instance.Put.incrementAndGet();
for (int i = OpenMap.attemptsStart(versions.length); i >= 0; i--) {
Version current = versions[index];
if (current == null) {
versions[index] = version;
return true;
}
if (Debug.ENABLED) {
Debug.assertion(current != version);
Debug.assertion(current.object() != version.object());
}
if (Stats.ENABLED)
Stats.Instance.PutRetry.incrementAndGet();
index = (index + 1) & (versions.length - 1);
}
return false;
}
private static boolean rehash(Version[] source, Version[] target) {
for (int i = source.length - 1; i >= 0; i--) {
Version current = source[i];
if (current != null && !tryToPut(target, current))
return false;
}
return true;
}
//
final void mergeReads(TransactionBase child) {
Version[] sources = child.getReads();
if (sources != null) {
if (_reads == null)
_reads = sources;
else
_reads = merge(_reads, sources);
}
}
final void mergeWrites(TransactionBase child) {
Version[] sources = child.getWrites();
if (sources != null) {
if (isCommitted())
throw new RuntimeException(Strings.COMMITTED);
if (noWrites())
throw new RuntimeException(Strings.READ_ONLY);
if (_writes == null)
_writes = sources;
else
_writes = merge(_writes, sources);
}
}
private final Version[] merge(Version[] targets, Version[] sources) {
for (int i = sources.length - 1; i >= 0; i--) {
if (sources[i] != null) {
int index = getIndex(targets, sources[i].object());
if (index < 0)
targets = putVersion(targets, sources[i]);
else {
targets[index] = targets[index].merge(targets[index], sources[i], true);
if (Debug.ENABLED)
targets[index].checkInvariants();
}
}
}
return targets;
}
// Snapshot
final Version[][] getPublicSnapshotVersions() {
return _publicSnapshotVersions;
}
final void setPublicSnapshotVersions(Version[][] value) {
_publicSnapshotVersions = value;
}
//
static Transaction startAccess(Workspace workspace, boolean read) {
int flags = TransactionBase.FLAG_IGNORE_READS;
if (read)
flags |= TransactionBase.FLAG_NO_WRITES;
return workspace.startImpl(flags);
}
static void endAccess(Transaction inner, boolean commit) {
if (commit) {
boolean result = TransactionManager.commit(inner);
if (Debug.ENABLED)
Debug.assertion(result);
} else
TransactionManager.abort(inner);
}
static void checkWorkspace(Transaction outer, TObject object) {
if (outer.workspace() != object.workspace())
ExpectedExceptionThrower.throwRuntimeException(Strings.WRONG_WORKSPACE);
}
//
/*
* Private snapshot.
*/
final Version[][] getPrivateSnapshotVersions() {
return _privateSnapshotVersions;
}
final void setPrivateSnapshotVersions(Version[][] value) {
if (Debug.ENABLED)
Debug.assertion(_privateSnapshotVersions == null);
_privateSnapshotVersions = value;
}
final void addPrivateSnapshotVersions(Version[] value) {
if (_privateSnapshotVersions != null)
_privateSnapshotVersions = Helper.addVersions(_privateSnapshotVersions, value);
else
setPrivateSnapshotVersions(value);
}
final void setPrivateSnapshotVersions(Version[] versions) {
if (Debug.ENABLED) {
Debug.assertion(versions.length > 0);
Debug.assertion(_privateSnapshotVersions == null);
}
_privateSnapshotVersions = new Version[1][];
_privateSnapshotVersions[0] = versions;
}
final boolean isCommitted() {
boolean value = (flags() & FLAG_COMMITTED) != 0;
if (Debug.ENABLED)
if (value)
Debug.assertion(getWrites() == null);
return value;
}
final Snapshot getSnapshot() {
return _snapshot;
}
final void setSnapshot(Snapshot value) {
if (Debug.ENABLED)
Debug.assertion(value != null);
_snapshot = value;
}
final VersionMap getVersionMap() {
return _map;
}
final void setVersionMap(VersionMap value) {
_map = value;
}
final VersionMap getOrCreateVersionMap() {
if (_map == null) {
_map = new VersionMap();
if (Debug.ENABLED)
Helper.instance().addWatcher(_map, workspace(), _snapshot, "New commit");
}
return _map;
}
//
final Transaction startChild(int flags) {
Transaction transaction = new Transaction(workspace(), (Transaction) this);
transaction.setSnapshot(getSnapshot());
transaction.setPublicSnapshotVersions(getPublicSnapshotVersions());
transaction.setPrivateSnapshotVersions(getPrivateSnapshotVersions());
if (getWrites() != null)
transaction.addPrivateSnapshotVersions(getWrites());
transaction.onStart(flags | flags());
return transaction;
}
final void onStart(int flags) {
flags(flags);
if (Debug.ENABLED)
checkInvariants();
}
final void mergePrivate(Transaction child) {
if (Debug.ENABLED) {
Debug.assertion(child.parent() == this);
if (child.getVersionMap() != null)
Helper.instance().removeWatcher(child.getVersionMap(), this, _snapshot, "merge(Transaction child)");
}
mergeReads(child);
mergeWrites(child);
if (Debug.ENABLED)
assertUpdates();
}
// Debug
final void forceSnapshot(Snapshot value) {
_snapshot = value;
}
final void assertUpdates() {
if (!Debug.ENABLED)
throw new IllegalStateException();
Version[][] versions = getPublicSnapshotVersions();
if (getPrivateSnapshotVersions() != null || getWrites() != null) {
if (getPrivateSnapshotVersions() != null)
for (Version[] map : getPrivateSnapshotVersions())
if (map != null)
versions = Helper.addVersions(versions, map);
if (getWrites() != null)
versions = Helper.addVersions(versions, getWrites());
}
}
final void checkInvariants() {
if (!Debug.ENABLED)
throw new IllegalStateException();
if (parent() == null)
workspace().checkNotCached((Transaction) this);
if (Debug.THREADS) {
/*
* If committed, must be shared, otherwise private.
*/
Snapshot snapshot = null;
if (parent() == null)
snapshot = workspace().snapshot();
if (snapshot != null && getVersionMap() != null)
ThreadAssert.assertShared(getVersionMap());
else
ThreadAssert.assertPrivate(this);
}
Helper.instance().disableEqualsOrHashCheck();
if (ignoreReads() && !Helper.instance().getAllowExistingReadsOrWrites().containsKey(this))
Debug.assertion(_reads == null);
if (noWrites() && !Helper.instance().getAllowExistingReadsOrWrites().containsKey(this))
Debug.assertion(_writes == null);
Helper.instance().enableEqualsOrHashCheck();
}
}