/**
* JSTM (http://xstm.net)
* Distributed under the Apache License Version 2.0
* Copyright xstm.net
*/
package jstm4gwt.core;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import jstm4gwt.misc.ConcurrentHashMap;
import jstm4gwt.misc.ConcurrentLinkedQueue;
import jstm4gwt.misc.AtomicLong;
import jstm4gwt.misc.AtomicReference;
import jstm4gwt.core.Connection.Mode;
import jstm4gwt.core.TObject.Descriptor;
import jstm4gwt.core.Transaction.AbortedException;
import jstm4gwt.core.Transaction.Status;
import jstm4gwt.core.Transport.InitiatorInfo;
import jstm4gwt.extensions.Notifier;
import jstm4gwt.extensions.ThreadedNotifier;
import com.google.gwt.user.client.rpc.AsyncCallback;
import jstm4gwt.misc.Debug;
import jstm4gwt.misc.GWTAdapter;
import jstm4gwt.misc.Log;
final class TransactionManager {
public static final int OBJECTS_VERSIONS_INDEX = 0;
public static final TransactionEntry[] OBJECTS_DELTA = new TransactionEntry[0];
//
private final AtomicReference<Snapshot> _snapshot = new AtomicReference<Snapshot>();
private final ConcurrentLinkedQueue<TransactionCommit> _toMerge = new ConcurrentLinkedQueue<TransactionCommit>();
private final Object _threadSwitchLock = new Object();
//
private final ConcurrentHashMap<String, TObject> _objectsWithUID = new ConcurrentHashMap<String, TObject>();
private final ArrayList<TObject.Id> _freeObjectIds = new ArrayList<TObject.Id>();
private int _nextObjectId;
/**
* Negative for objects with UID.
*/
private int _nextUidId = -1;
//
private volatile Connection[] _fullWatchers;
private final Object _connectionsLock = new Object();
private volatile Notifier _defaultNotifier;
private final Object _defaultNotifierLock = new Object();
//
private final TransactionHelper _helper;
protected final AtomicLong _committedCount = new AtomicLong();
protected final AtomicLong _retriedCount = new AtomicLong();
protected final AtomicLong _abortedCount = new AtomicLong();
protected final AtomicLong _mergedCount = new AtomicLong();
//
public TransactionManager() {
if (Debug.ENABLED) {
_helper = new TransactionHelper(this);
_helper.allowThread();
if (Debug.DUMPS)
_dumpMap = new HashMap<Object, String>();
} else
_helper = null;
_snapshot.set(Snapshot.createInitial(this));
}
public TransactionHelper getHelper() {
return _helper;
}
public Notifier getDefaultNotifier() {
return _defaultNotifier;
}
public Notifier getOrCreateDefaultNotifier() {
Notifier notifier = _defaultNotifier;
if (notifier == null) {
synchronized (_defaultNotifierLock) {
notifier = _defaultNotifier;
if (notifier == null)
notifier = _defaultNotifier = new ThreadedNotifier();
}
}
return notifier;
}
public void setDefaultNotifier(Notifier value) {
_defaultNotifier = value;
}
// Connections
public Connection[] getFullWatchers() {
return _fullWatchers;
}
public void register(Connection connection) {
if (Debug.ENABLED)
Debug.assertion(connection.getMode() == Connection.Mode.ALL_CHANGES);
synchronized (_connectionsLock) {
Connection[] connections = _fullWatchers;
if (connections != null && Arrays.asList(connections).contains(connection))
throw new IllegalArgumentException("Already registered");
ArrayList<Connection> list = new ArrayList<Connection>();
if (connections != null)
list.addAll(Arrays.asList(connections));
list.add(connection);
_fullWatchers = list.toArray(new Connection[list.size()]);
}
}
public void unregister(Connection connection) {
if (Debug.ENABLED)
Debug.assertion(connection.getMode() == Connection.Mode.ALL_CHANGES);
synchronized (_connectionsLock) {
Connection[] connections = _fullWatchers;
if (connections == null || !Arrays.asList(connections).contains(connection))
throw new IllegalArgumentException("Not registered");
ArrayList<Connection> list = new ArrayList<Connection>();
if (connections != null)
list.addAll(Arrays.asList(connections));
list.remove(connection);
if (list.size() > 0)
_fullWatchers = list.toArray(new Connection[list.size()]);
else
_fullWatchers = null;
}
}
public boolean registered(Connection connection) {
if (Debug.ENABLED)
Debug.assertion(connection.getMode() == Connection.Mode.ALL_CHANGES);
Connection[] connections = _fullWatchers;
if (connections != null && Arrays.asList(connections).contains(connection))
return true;
return false;
}
/**
* Only for GWT
*/
public Site updateSiteKey(String previous, String current) {
Debug.assertion(GWTAdapter.GWT);
Site site = (Site) _objectsWithUID.remove(previous);
_objectsWithUID.put(current, site);
return site;
}
// Object Models
public ConcurrentHashMap<String, TObject> getObjectsWithUID() {
return _objectsWithUID;
}
//
public Snapshot getSnapshot() {
return _snapshot.get();
}
public int getPendingCommitCount() {
Snapshot snapshot = _snapshot.get();
return snapshot.getCommits().length;
}
//
public void startTransaction(Transaction transaction, boolean seeOnlyAcknowledgedData) {
if (Transaction.getCurrent() != null)
throw new RuntimeException(Strings.ALREADY_RUNNING);
if (Debug.STM)
Log.write("Starting " + transaction);
Snapshot snapshot;
while (true) {
/*
* volatile read ensures sync with shared memory.
*/
snapshot = _snapshot.get();
/*
* Increment watchers count to prevent the commit we are using as
* our snapshot to be modified by merges.
*/
TransactionCommit commit;
if (seeOnlyAcknowledgedData)
commit = snapshot.getLastAcknowledgedCommit();
else
commit = snapshot.getLast();
int current = commit.getWatcherCount();
/*
* If the commit has no watcher it is going to be merged. This can
* occur only if _snapshot has been updated since we read it because
* commits have watcherCount at 1 by default. In this case, retry
* with the new snapshot.
*/
if (current > 0) {
/*
* Otherwise try to add this transaction as new watcher.
*/
int next = current + 1;
if (commit.compareAndSetWatcherCount(current, next)) {
// Add succeeded, the snapshot is valid so go
if (Debug.ENABLED)
getHelper().addWatcher(commit, transaction, current, next, "Start");
break;
}
}
}
transaction.setSnapshot(snapshot);
transaction.setSnapshotCommits(snapshot.getCommits());
transaction.setSnapshotDeltas(snapshot.getDeltas());
if (seeOnlyAcknowledgedData)
transaction.setStartIndex(snapshot.getLastAcknowledgedIndex());
else
transaction.setStartIndex(snapshot.getCommits().length - 1);
Transaction.setCurrentInternal(transaction);
if (Debug.ENABLED)
Debug.assertion(transaction.getStatus() == Status.SUSPENDED);
transaction.setStatus(Status.RUNNING);
}
public void continueTransaction(Transaction transaction, Transaction model) {
if (Transaction.getCurrent() != null)
throw new RuntimeException(Strings.ALREADY_RUNNING);
if (Debug.STM)
Log.write("Continuing " + transaction);
int count = model.getStart().addAndGetWatcherCount(1);
if (Debug.ENABLED)
getHelper().addWatcher(model.getStart(), transaction, count - 1, count, "Continue");
if (Debug.ENABLED) {
// Assert model was not disposed
Debug.assertion(count > 1);
}
transaction.setSnapshot(model.getSnapshot());
transaction.setSnapshotCommits(model.getSnapshotCommits());
transaction.setSnapshotDeltas(model.getSnapshotDeltas());
transaction.setStartIndex(model.getStartIndex());
Transaction.setCurrentInternal(transaction);
if (Debug.ENABLED)
Debug.assertion(transaction.getStatus() == Status.SUSPENDED);
transaction.setStatus(Status.RUNNING);
}
public void beginCommit(Transaction transaction, AsyncCallback<Void> callback) {
Notifier notifier = null;
if (callback != null)
notifier = getOrCreateDefaultNotifier();
beginCommit(transaction, callback, notifier);
}
public void beginCommit(Transaction transaction, AsyncCallback<Void> callback, Notifier notifier) {
if (transaction != Transaction.getCurrent())
throw new RuntimeException(Strings.NOT_RUNNING);
if (transaction.getStatus() != Transaction.Status.RUNNING)
throw new RuntimeException(Strings.NOT_RUNNING);
transaction.setCallback(notifier, callback);
// If no read and no write, skip actual commit
if (Debug.ENABLED)
Debug.assertion((transaction.getCommit() != null) == (transaction.getDelta() != null));
if (transaction.getCommit() != null)
commit(transaction, null);
else {
/*
* If notifier is fullWatcher, it will get the event in order
* through the snapshot.
*/
if (notifier != null && notifier.getMode() == Mode.LAST_VALUES) {
Connection connection = notifier;
connection.run(transaction.getCallback(), null, null, transaction);
}
}
// Transaction does not watch its start commit anymore
stopWatching(transaction.getStart(), transaction, "No more watching start");
Transaction.setCurrentInternal(null);
}
public void propagate(Transaction transaction, Connection propagator) {
if (Debug.ENABLED)
Debug.assertion(transaction == Transaction.getCurrent());
if (transaction.getCommit() != null) {
commit(transaction, propagator);
if (Debug.ENABLED)
Debug.assertion(transaction.getStatus() == Transaction.Status.COMMITTED);
// Transaction does not watch its start commit anymore
stopWatching(transaction.getStart(), transaction, "No more watching start");
Transaction.setCurrentInternal(null);
} else {
commitEmpty(transaction, propagator);
dispose(transaction, false);
Transaction.setCurrent(null);
}
}
private void commit(Transaction transaction, Connection propagator) {
if (Debug.ENABLED) {
/*
* Transaction not yet committed so cannot be watched by anyone. It
* must have its default initial 1 watcher count.
*/
Debug.assertion(transaction.getCommit().getWatcherCount() == 1);
if (propagator != null)
Debug.assertion(propagator.interceptsCommits());
}
/*
* If connections need the transaction, increase commit's watching count
* so it is not merged until those connections are done with it.
*/
Connection[] fullWatchers = getFullWatchers();
if (fullWatchers != null) {
TransactionCommit commit = transaction.getCommit();
int watchers = 1 + fullWatchers.length;
commit.setWatcherCount(watchers);
if (Debug.ENABLED) {
for (int i = 0; i < fullWatchers.length; i++)
getHelper().addWatcher(commit, fullWatchers[i], 1, watchers, "Full watchers");
}
for (int i = 0; i < fullWatchers.length; i++)
fullWatchers[i].onCommitStarted(transaction);
}
Snapshot newSnapshot = null;
TransactionCommit lastValidated = transaction.getStart();
TransactionCommit watchedDuringLastValidation = null;
boolean done = false;
while (true) {
/*
* Same mechanism as when starting a transaction, but with last
* acknowledged commit.
*/
Snapshot snapshot;
int newWatchers;
while (true) {
while (true) {
snapshot = _snapshot.get();
/*
* Propagated transaction must not block. Sometimes commit
* acknowledgments that would unblock thread are behind on
* the line or the transport thats read it.
*/
if (propagator != null || snapshot.getCommits().length < Site.MAX_PENDING_COMMIT_COUNT)
break;
// Wait if too much pending commits
if (GWTAdapter.GWT)
throw new IllegalStateException(Strings.TOO_MUCH_PENDING_COMMITS);
else {
try {
throw new jstm4gwt.misc.InterruptedException();
/*
* TODO benchmark with wait() / notify()
*/
// synchronized (_lock) {
// _lock.wait();
// }
} catch (jstm4gwt.misc.InterruptedException e) {
throw new RuntimeException(e);
}
}
}
if (propagator != null)
transaction.setBeforeCommit(snapshot.getLastAcknowledgedCommit());
else
transaction.setBeforeCommit(snapshot.getLast());
/*
* Mark last acknowledged as watched so it stays there in case
* we have to retry validation. This way we can skip previous
* commits and only validate the transaction against new ones.
*/
newWatchers = 1;
/*
* Also mark connections in ALL_CHANGES mode as watchers so the
* transaction will not be merged with last acknowledged which
* will be just before it until they are done with it.
*/
if (fullWatchers != null)
newWatchers += fullWatchers.length;
/*
* C.f. startTransaction.
*/
int current = transaction.getBeforeCommit().getWatcherCount();
if (current > 0) {
int next = current + newWatchers;
if (transaction.getBeforeCommit().compareAndSetWatcherCount(current, next)) {
if (Debug.ENABLED) {
getHelper().addWatcher(transaction.getBeforeCommit(), transaction, current, next, "Keep last");
if (fullWatchers != null)
for (int i = 0; i < fullWatchers.length; i++)
getHelper().addWatcher(transaction.getBeforeCommit(), fullWatchers[i], current, next, "Keep for connection");
}
break;
}
}
}
/*
* Searched for last validated so we can validate transaction
* against writes that occurred between this and last acknowledged.
*/
int lastValidatedIndex = OBJECTS_VERSIONS_INDEX;
for (int i = 0; i < snapshot.getCommits().length; i++) {
if (snapshot.getCommits()[i] == lastValidated) {
lastValidatedIndex = i;
break;
}
}
/*
* If this is a retry, stop watching previous commit now that we
* have found the index of last validated in new snapshot.
*/
TransactionCommit toMerge = null;
if (watchedDuringLastValidation != null) {
int value = watchedDuringLastValidation.addAndGetWatcherCount(-newWatchers);
if (Debug.ENABLED) {
int previous = value + newWatchers;
getHelper().removeWatcher(watchedDuringLastValidation, transaction, previous, value, "Stop keep last 1");
if (fullWatchers != null)
for (int i = 0; i < fullWatchers.length; i++)
getHelper().removeWatcher(watchedDuringLastValidation, fullWatchers[i], previous, value, "Stop keep for connection 1");
}
if (value == 0)
toMerge = watchedDuringLastValidation;
}
watchedDuringLastValidation = transaction.getBeforeCommit();
/*
* Validate transaction.
*/
boolean valid = true;
if (propagator != null) {
if (Debug.ENABLED) {
Debug.assertion(transaction.getDependencies() == null);
Debug.assertion(transaction.validateAgainst(snapshot, lastValidatedIndex));
Debug.assertion(transaction.getInterception() == null);
}
} else {
valid = valid && transaction.validateDependencies();
valid = valid && transaction.validateAgainst(snapshot, lastValidatedIndex);
valid = valid && transaction.updateInterception(fullWatchers);
}
if (valid && fullWatchers != null)
for (int i = 0; i < fullWatchers.length; i++)
if (!fullWatchers[i].onCommitValidation(transaction))
valid = false;
if (Debug.ENABLED) {
if (TransactionHelper.AbortAlways)
valid = false;
else if (TransactionHelper.AbortRandom)
valid = valid && GWTAdapter.getRandom().nextBoolean();
}
if (!valid) {
if (Debug.STATS)
_abortedCount.incrementAndGet();
transaction.setStatus(Transaction.Status.ABORTED);
/*
* Notify connections.
*/
if (fullWatchers != null)
for (int i = 0; i < fullWatchers.length; i++)
fullWatchers[i].onCommitFailed(transaction);
// Remove watchers
int value = watchedDuringLastValidation.addAndGetWatcherCount(-newWatchers);
if (Debug.ENABLED) {
int previous = value + newWatchers;
getHelper().removeWatcher(watchedDuringLastValidation, transaction, previous, value, "Stop keep last 2");
if (fullWatchers != null)
for (int i = 0; i < fullWatchers.length; i++)
getHelper().removeWatcher(watchedDuringLastValidation, fullWatchers[i], previous, value, "Stop keep for connection 2");
}
if (value == 0)
merge(watchedDuringLastValidation);
if (Debug.ENABLED) {
TransactionCommit commit = transaction.getCommit();
String reason = "No more need of this commit as last";
getHelper().removeWatcher(commit, TransactionCommit.AmbientWatcher, 1, 0, reason);
if (fullWatchers != null)
for (int i = 0; i < fullWatchers.length; i++)
getHelper().removeWatcher(commit, fullWatchers[i], fullWatchers.length, 0, "Full watchers");
}
if (Debug.ENABLED) {
boolean a = transaction.getNotifier() == null;
boolean b = transaction.getCallback() == null;
Debug.assertion(a == b);
}
Notifier notifier = transaction.getNotifier();
if (notifier != null) {
Connection connection = notifier;
connection.run(transaction.getCallback(), null, new AbortedException("Conflict"), null);
}
InitiatorInfo info = transaction.getCommit().getInitiatorInfo();
if (info != null)
info.getConnection().enqueueAbort(transaction);
done = true;
} else {
if (newSnapshot == null)
newSnapshot = new Snapshot();
if (Debug.DUMPS)
Debug.log("Committed: " + getKey(transaction));
int insertionIndex;
/*
* Update transaction status before writing to volatile
* _snapshot field so other threads immediately see the new
* status.
*/
if (transaction.getInterception() == null) {
newSnapshot.setLastAcknowledgedIndex(snapshot.getLastAcknowledgedIndex() + 1);
insertionIndex = newSnapshot.getLastAcknowledgedIndex();
transaction.setStatus(Transaction.Status.COMMITTED);
transaction.getCommit().setStatus(TransactionCommit.Status.COMMITTED);
} else {
newSnapshot.setLastAcknowledgedIndex(snapshot.getLastAcknowledgedIndex());
insertionIndex = snapshot.getCommits().length;
transaction.setStatus(Transaction.Status.WAITING_ACKNOWLEDGMENT);
transaction.getCommit().setStatus(TransactionCommit.Status.WAITING_ACKNOWLEDGMENT);
}
newSnapshot.setCommits(TransactionHelper.insertCommit(snapshot.getCommits(), transaction.getCommit(), insertionIndex));
newSnapshot.setDeltas(TransactionHelper.insertDelta(snapshot.getDeltas(), transaction.getDelta(), insertionIndex));
// Store last transaction for each connection
storePropagator(transaction, snapshot, newSnapshot, propagator);
// Store last validated if intercepted
if (transaction.getInterception() != null || propagator != null) {
Connection connection = propagator;
if (connection == null)
connection = transaction.getInterception().getConnection();
if (snapshot.getLastReceived() != null) {
Transaction lastReceived = snapshot.getLastReceived().get(connection);
transaction.setLastValidatedAgainst(lastReceived);
}
}
//
newSnapshot.setAbortedOnStart(snapshot.getAbortedOnStart());
/*
* Try to publish the new snapshot.
*/
if (!_snapshot.compareAndSet(snapshot, newSnapshot)) {
if (Debug.STATS)
_retriedCount.incrementAndGet();
// Return to running so transaction can be manipulated.
transaction.setStatus(Transaction.Status.RUNNING);
/*
* If _snapshot had changed, retry from last acknowledged as
* we has already validated the transaction up to this
* commit.
*/
boolean once = true;
if (Debug.ENABLED && !TransactionHelper.ValidateOnce)
once = false;
if (once)
lastValidated = snapshot.getLast();
} else {
/*
* Success!
*/
if (Debug.ENABLED) {
// Assert all watched commits are still there
for (int i = 0; i < snapshot.getCommits().length; i++) {
if (snapshot.getCommits()[i].getWatcherCount() > 0)
Debug.assertion(Arrays.asList(newSnapshot.getCommits()).contains(snapshot.getCommits()[i]));
}
}
if (Debug.STATS)
_committedCount.incrementAndGet();
if (transaction.getInterception() == null) {
/*
* lastAcknowledged is not the last element anymore so
* decrease watchers count to allow merge. When
* intercepted, leave it so it is merged on
* acknowledgment.
*/
TransactionCommit previousLast = snapshot.getLastAcknowledgedCommit();
stopWatching(previousLast, TransactionCommit.AmbientWatcher, "No more last");
/*
* If notifier is fullWatcher, it will get the event in
* order through the snapshot.
*/
Notifier notifier = transaction.getNotifier();
if (notifier != null && notifier.getMode() == Mode.LAST_VALUES) {
Connection connection = notifier;
connection.run(transaction.getCallback(), null, null, transaction);
}
}
/*
* Notify full watchers.
*/
if (fullWatchers != null)
for (int i = 0; i < fullWatchers.length; i++)
fullWatchers[i].onCommitSucceeded(transaction);
transaction.notifyRelatedConnectionsOfChange();
/*
* No more need to find last validated commit in case of
* retry, but leave full watchers.
*/
stopWatching(watchedDuringLastValidation, transaction, "Stop watching last ack");
done = true;
}
}
if (toMerge != null)
merge(toMerge);
if (done) {
if (Debug.ENABLED)
getHelper().removeValidated(transaction);
break;
}
}
}
private void commitEmpty(Transaction transaction, Connection propagator) {
Snapshot newSnapshot = new Snapshot();
while (true) {
Snapshot snapshot = _snapshot.get();
newSnapshot.setCommits(snapshot.getCommits());
newSnapshot.setDeltas(snapshot.getDeltas());
newSnapshot.setLastAcknowledgedIndex(snapshot.getLastAcknowledgedIndex());
newSnapshot.setAbortedOnStart(snapshot.getAbortedOnStart());
storePropagator(transaction, snapshot, newSnapshot, propagator);
if (_snapshot.compareAndSet(snapshot, newSnapshot)) {
Connection[] fullWatchers = getFullWatchers();
/*
* Notify full watchers. This might make a client acknowledged
* it has received a transaction from server and unblock server.
*/
if (fullWatchers != null)
for (int i = 0; i < fullWatchers.length; i++)
fullWatchers[i].onCommitSucceeded(transaction);
break;
}
}
}
private void storePropagator(Transaction transaction, Snapshot snapshot, Snapshot newSnapshot, Connection propagator) {
HashMap<Connection, Transaction> previous = snapshot.getLastReceived();
if (previous != null)
newSnapshot.getOrCreateLastReceived().putAll(previous);
if (propagator != null)
newSnapshot.getOrCreateLastReceived().put(propagator, transaction);
}
public void stopWatching(TransactionCommit commit, Object watcher, String reason) {
int value = commit.decrementAndGetWatcherCount();
if (Debug.ENABLED) {
Debug.assertion(value >= 0);
getHelper().removeWatcher(commit, watcher, value + 1, value, reason);
}
if (value == 0)
merge(commit);
}
private void merge(TransactionCommit commit) {
if (Debug.ENABLED)
Debug.assertion(commit.getWatcherCount() == 0);
if (Debug.STM)
Log.write("Merge " + commit);
if (!tryMerge(commit))
_toMerge.add(commit);
else {
// Other merges could have been delayed by ours so try them again
for (TransactionCommit delayed : _toMerge)
if (tryMerge(delayed))
_toMerge.remove(delayed);
}
// TODO try those
// while (!tryMerge(commit)) {
// try {
// Thread.sleep(0);
// } catch (InterruptedException e) {
// }
// }
// Snapshot snapshot = _snapshot.get();
//
// for (int i = 0; i < snapshot.getCommits().length; i++)
// if (snapshot.getCommits()[i].getWatcherCount().get() == 0)
// tryMerge(snapshot.getCommits()[i]);
}
private boolean tryMerge(TransactionCommit commit) {
/*
* Try to switch both merge guards to true before merge, otherwise
* restore them as they were to merge later.
*/
if (!commit.compareAndSetMergeInfo(TransactionCommit.MergeInfo.NONE, TransactionCommit.MergeInfo.SOURCE)) {
return false;
} else {
if (commit.getStatus() == TransactionCommit.Status.COMMITTED)
return tryMergeToNextCommit(commit);
else {
if (Debug.ENABLED)
Debug.assertion(commit.getStatus() == TransactionCommit.Status.ABORTED);
removeCommit(commit);
return true;
}
}
}
private boolean tryMergeToNextCommit(TransactionCommit commit) {
/*
* Commit will not be merged from this point, next element will always
* exist if commit is not aborted.
*/
Snapshot snapshot = _snapshot.get();
int commitIndex = TransactionHelper.getIndex(snapshot, commit);
TransactionCommit nextCommit = snapshot.getCommits()[commitIndex + 1];
TransactionEntry[] nextDelta = snapshot.getDeltas()[commitIndex + 1];
if (!nextCommit.compareAndSetMergeInfo(TransactionCommit.MergeInfo.NONE, TransactionCommit.MergeInfo.TARGET)) {
commit.setMergeInfo(TransactionCommit.MergeInfo.NONE);
return false;
} else {
if (Debug.ENABLED)
Debug.assertion(commit.getStatus() == TransactionCommit.Status.COMMITTED);
nextDelta = nextCommit.mergePrevious(nextDelta, commit, snapshot.getDeltas()[commitIndex]);
if (Debug.ENABLED && commitIndex == OBJECTS_VERSIONS_INDEX)
Debug.assertion(nextDelta == OBJECTS_DELTA);
copyInitiatorInfos(commit, nextCommit);
/*
* Merge is done, replace _snapshot
*/
Snapshot newSnapshot = new Snapshot();
while (true) {
newSnapshot.setLastAcknowledgedIndex(snapshot.getLastAcknowledgedIndex() - 1);
int index = TransactionHelper.getIndex(snapshot, commit);
newSnapshot.setCommits(TransactionHelper.removeCommit(snapshot.getCommits(), index));
newSnapshot.setDeltas(TransactionHelper.removeDelta(snapshot.getDeltas(), index));
newSnapshot.getDeltas()[index] = nextDelta;
if (Debug.ENABLED) {
Debug.assertion(newSnapshot.getCommits()[index] == nextCommit);
Debug.assertion(newSnapshot.getDeltas()[TransactionManager.OBJECTS_VERSIONS_INDEX] == TransactionManager.OBJECTS_DELTA);
}
copyLastReceived(snapshot, newSnapshot);
newSnapshot.setAbortedOnStart(snapshot.getAbortedOnStart());
if (_snapshot.compareAndSet(snapshot, newSnapshot)) {
if (Debug.ENABLED) {
Debug.assertion(commit.getMergeInfo() == TransactionCommit.MergeInfo.SOURCE);
Debug.assertion(nextCommit.getMergeInfo() == TransactionCommit.MergeInfo.TARGET);
// InitiatorInfo info = commit.getInitiatorInfo();
//
// while (info != null) {
// Debug.assertion(nextCommit.initiatedBy(info.
// getTransaction()));
// info = info.getNext();
// }
}
if (Debug.STATS)
_mergedCount.incrementAndGet();
if (Debug.COMMUNICATIONS_LOG)
Debug.log("Merged");
if (Debug.DUMPS)
Debug.log("Merged: " + getKey(commit));
nextCommit.setMergeInfo(TransactionCommit.MergeInfo.NONE);
return true;
}
snapshot = _snapshot.get();
}
}
}
private void copyInitiatorInfos(TransactionCommit commit, TransactionCommit nextCommit) {
InitiatorInfo info = commit.getInitiatorInfo();
while (info != null) {
addIfAbsentForConnection(nextCommit, info);
info = info.getNext();
}
}
private void addIfAbsentForConnection(TransactionCommit commit, InitiatorInfo previous) {
InitiatorInfo info = commit.getInitiatorInfo();
while (info != null) {
if (previous.getConnection() == info.getConnection()) {
if (Debug.ENABLED) {
Descriptor a = previous.getTransaction().getDescriptor();
Descriptor b = info.getTransaction().getDescriptor();
if (a != null && b != null)
Debug.assertion(a.getId() < b.getId());
}
return;
}
info = info.getNext();
}
commit.setInitiatorInfo(new InitiatorInfo(previous.getConnection(), previous.getTransaction(), commit.getInitiatorInfo()));
}
private void removeCommit(TransactionCommit commit) {
Snapshot newSnapshot = new Snapshot();
while (true) {
Snapshot snapshot = _snapshot.get();
int index = TransactionHelper.getIndex(snapshot, commit);
newSnapshot.setCommits(TransactionHelper.removeCommit(snapshot.getCommits(), index));
newSnapshot.setDeltas(TransactionHelper.removeDelta(snapshot.getDeltas(), index));
newSnapshot.setLastAcknowledgedIndex(snapshot.getLastAcknowledgedIndex());
copyLastReceived(snapshot, newSnapshot);
if (snapshot.getAbortedOnStart() != null)
for (TransactionCommit aborted : snapshot.getAbortedOnStart())
if (aborted != commit)
newSnapshot.getOrCreateAbortedOnStart().add(aborted);
if (_snapshot.compareAndSet(snapshot, newSnapshot)) {
if (Debug.ENABLED) {
Debug.assertion(commit.getMergeInfo() == TransactionCommit.MergeInfo.SOURCE);
Debug.assertion(newSnapshot.getCommits().length == newSnapshot.getDeltas().length);
Debug.assertion(!Arrays.asList(newSnapshot.getCommits()).contains(commit));
Debug.assertion(!Arrays.asList(newSnapshot.getDeltas()).contains(snapshot.getDeltas()[index]));
}
break;
}
}
}
private void copyLastReceived(Snapshot snapshot, Snapshot newSnapshot) {
if (newSnapshot.getLastReceived() != null)
newSnapshot.getLastReceived().clear();
HashMap<Connection, Transaction> previousMap = snapshot.getLastReceived();
if (previousMap != null)
newSnapshot.getOrCreateLastReceived().putAll(previousMap);
if (Debug.COMMUNICATIONS_LOG)
Debug.log("Copied LastReceived " + newSnapshot.getLastReceived());
}
public void onInterceptionAcknowledgments(Connection connection, ArrayList<Transaction> acknowledged) {
Snapshot newSnapshot = new Snapshot();
// Before the snapshot for visibility
for (int i = acknowledged.size() - 1; i >= 0; i--) {
Transaction transaction = acknowledged.get(i);
Transaction.setCurrent(transaction, connection);
if (transaction.getStatus() == Status.WAITING_ACKNOWLEDGMENT) {
transaction.setStatus(Status.COMMITTED);
if (transaction.getCommit() != null)
transaction.getCommit().setStatus(TransactionCommit.Status.COMMITTED);
} else {
acknowledged.remove(i);
if (Debug.ENABLED) {
Debug.assertion(transaction.getStatus() == Status.ABORTED);
Debug.assertion(transaction.getCommit().getStatus() == TransactionCommit.Status.ABORTED);
}
}
}
while (true) {
Snapshot snapshot = _snapshot.get();
int newIndex = snapshot.getLastAcknowledgedIndex();
newSnapshot.setCommits(snapshot.getCommits());
newSnapshot.setDeltas(snapshot.getDeltas());
for (Transaction transaction : acknowledged) {
int index = TransactionHelper.getIndex(newSnapshot, transaction.getCommit());
newIndex++;
newSnapshot.setCommits(TransactionHelper.moveCommit(newSnapshot.getCommits(), index, newIndex));
newSnapshot.setDeltas(TransactionHelper.moveDelta(newSnapshot.getDeltas(), index, newIndex));
}
newSnapshot.setLastAcknowledgedIndex(newIndex);
newSnapshot.setLastReceived(snapshot.getLastReceived());
newSnapshot.setAbortedOnStart(snapshot.getAbortedOnStart());
if (_snapshot.compareAndSet(snapshot, newSnapshot)) {
if (Debug.ENABLED) {
Debug.assertion(snapshot.getCommits().length == newSnapshot.getCommits().length);
Debug.assertion(newSnapshot.getCommits().length == newSnapshot.getDeltas().length);
int first = newIndex - acknowledged.size() + 1;
for (int i = 0; i < acknowledged.size(); i++) {
Debug.assertion(TransactionHelper.getIndex(newSnapshot, acknowledged.get(i).getCommit()) == first + i);
Debug.assertion(newSnapshot.getDeltas()[first + i] == acknowledged.get(i).getDelta());
}
}
for (int i = snapshot.getLastAcknowledgedIndex(); i < newSnapshot.getLastAcknowledgedIndex(); i++)
stopWatching(newSnapshot.getCommits()[i], TransactionCommit.AmbientWatcher, "No more last");
if (Debug.STATS)
_committedCount.incrementAndGet();
for (Transaction transaction : acknowledged) {
Transaction.setCurrent(transaction);
connection.done(transaction);
transaction.notifyRelatedConnectionsOfChange();
/*
* If notifier is fullWatcher, it will get the event in
* order through the snapshot.
*/
Notifier notifier = transaction.getNotifier();
if (notifier != null && notifier.getMode() == Mode.LAST_VALUES)
Connection.callback(notifier, transaction.getCallback(), null, null, transaction);
transaction.getInterception().onSuccess(true);
}
Connection[] fullWatchers = getFullWatchers();
if (fullWatchers != null)
for (int i = 0; i < fullWatchers.length; i++)
fullWatchers[i].onChange();
Transaction.setCurrent(null);
break;
}
}
}
public void onInterceptionRejected(Transaction transaction) {
if (Debug.ENABLED)
Debug.assertion(transaction == Transaction.getCurrent());
Snapshot newSnapshot = new Snapshot();
while (true) {
Snapshot snapshot = _snapshot.get();
/*
* Do not remove commit yet as it can be still watched, but mark it
* as aborted in a new snapshot.
*/
newSnapshot.setCommits(snapshot.getCommits());
newSnapshot.setDeltas(snapshot.getDeltas());
newSnapshot.setLastAcknowledgedIndex(snapshot.getLastAcknowledgedIndex());
newSnapshot.setLastReceived(snapshot.getLastReceived());
if (snapshot.getAbortedOnStart() != null)
newSnapshot.getOrCreateAbortedOnStart().addAll(snapshot.getAbortedOnStart());
newSnapshot.getOrCreateAbortedOnStart().add(transaction.getCommit());
if (_snapshot.compareAndSet(snapshot, newSnapshot)) {
if (Debug.STATS)
_abortedCount.incrementAndGet();
dispose(transaction, true);
Connection[] fullWatchers = getFullWatchers();
if (fullWatchers != null)
for (int i = 0; i < fullWatchers.length; i++)
fullWatchers[i].onCommitRejected(transaction);
Notifier notifier = transaction.getNotifier();
if (notifier != null) {
Connection connection = notifier;
String reason = transaction.getAbortReason();
connection.run(transaction.getCallback(), null, new AbortedException(reason), null);
}
transaction.getInterception().onSuccess(false);
Transaction.setCurrent(null);
break;
}
}
}
public void dispose(Transaction transaction, boolean abort) {
if (Debug.ENABLED)
Debug.assertion(transaction == Transaction.getCurrent());
Transaction.Status status = transaction.getStatus();
if (abort) {
if (Debug.ENABLED)
Debug.assertion(status == Status.RUNNING || status == Status.WAITING_ACKNOWLEDGMENT);
if (Debug.STATS)
_abortedCount.incrementAndGet();
transaction.setStatus(Status.ABORTED);
if (transaction.getCommit() != null) {
transaction.getCommit().setStatus(TransactionCommit.Status.ABORTED);
}
} else {
if (Debug.ENABLED && transaction.getCommit() != null) {
Debug.assertion(status == Status.RUNNING || status == Status.COMMITTED);
TransactionCommit.Status s = transaction.getCommit().getStatus();
Debug.assertion(s == null || s == TransactionCommit.Status.COMMITTED);
}
}
/*
* !! Volatile update of watcher count ensures other threads will see
* status when they merge the commit. When aborted, the commit is not
* merged but simply removed so it is important that threads see the
* right status.
*/
if (status == Status.RUNNING) {
stopWatching(transaction.getStart(), transaction, "Dispose, start");
if (Debug.ENABLED && transaction.getCommit() != null) {
int watchers = transaction.getCommit().decrementAndGetWatcherCount();
Debug.assertion(watchers == 0);
getHelper().removeWatcher(transaction.getCommit(), TransactionCommit.AmbientWatcher, 1, 0, "Dispose, commit");
}
} else {
// Already removed watcher on start when committed
Debug.assertion(status == Status.WAITING_ACKNOWLEDGMENT);
if (transaction.getCommit() != null)
stopWatching(transaction.getCommit(), TransactionCommit.AmbientWatcher, "Dispose, commit rejected");
}
}
/**
* In case of suspend and resume, there is no access to _snapshot to
* synchronize memory between two threads. Threads must communicate through
* a lock.
*/
public void suspend(Transaction transaction) {
synchronized (_threadSwitchLock) {
transaction.setStatus(Transaction.Status.SUSPENDED);
}
}
/**
* C.f. comment on suspend.
*/
public boolean resume(Transaction transaction) {
synchronized (_threadSwitchLock) {
// Ensures only one thread can resume a transaction
if (transaction.getStatus() == Status.SUSPENDED) {
transaction.setStatus(Status.RUNNING);
return true;
}
return false;
}
}
// Objects IDs
public int getNextObjectId() {
if (_freeObjectIds.size() > 0)
return _freeObjectIds.remove(_freeObjectIds.size() - 1).Value;
return _nextObjectId++;
}
public int getNextUidId() {
return _nextUidId--;
}
// TODO
// public void onObjectDisconnected(TObject.Id id) {
// _freeObjectIds.add(id);
// }
// Debug help!
private HashMap<Object, String> _dumpMap;
protected String getKey(Object o) {
if (!Debug.DUMPS)
throw new IllegalStateException();
if (o == null)
return null;
synchronized (this) {
if (!_dumpMap.containsKey(o))
_dumpMap.put(o, GWTAdapter.getClassSimpleName(o.getClass()) + _dumpMap.size());
return _dumpMap.get(o);
}
}
protected void putKey(Object key, String value) {
if (!Debug.DUMPS)
throw new IllegalStateException();
synchronized (this) {
_dumpMap.put(key, value);
}
}
protected void copyKey(Transaction transaction, TransactionCommit commit) {
if (!Debug.DUMPS)
throw new IllegalStateException();
synchronized (this) {
String value = getKey(transaction);
_dumpMap.put(commit, value);
}
}
protected void dumpSnapshot() {
if (!Debug.DUMPS)
throw new IllegalStateException();
TransactionCommit[] commits = getSnapshot().getCommits();
for (int i = 0; i < commits.length; i++) {
ArrayList<Object> list = new ArrayList<Object>();
for (Object o : getHelper().getWatchers(commits[i])) {
list.add(getKey(o));
}
System.out.println("Watchers for " + i + " " + getKey(commits[i]) + ": " + list);
}
}
}
|