package org.multiverse.stms.gamma.transactions.fat;
import org.multiverse.api.lifecycle.TxnEvent;
import org.multiverse.stms.gamma.GammaStm;
import org.multiverse.stms.gamma.Listeners;
import org.multiverse.stms.gamma.transactionalobjects.BaseGammaTxnRef;
import org.multiverse.stms.gamma.transactionalobjects.GammaObject;
import org.multiverse.stms.gamma.transactionalobjects.Tranlocal;
import org.multiverse.stms.gamma.transactions.GammaTxn;
import org.multiverse.stms.gamma.transactions.GammaTxnConfig;
import static org.multiverse.utils.Bugshaker.shakeBugs;
/**
* A Fat {@link org.multiverse.stms.gamma.transactions.GammaTxn} (supporting all features) but has a fixed capacity.
*
* @author Peter Veentjer.
*/
public final class FatFixedLengthGammaTxn extends GammaTxn {
public Tranlocal head;
public int size = 0;
public boolean hasReads = false;
public long localConflictCount;
public final Listeners[] listenersArray;
public FatFixedLengthGammaTxn(final GammaStm stm) {
this(new GammaTxnConfig(stm));
}
@SuppressWarnings({"ObjectAllocationInLoop"})
public FatFixedLengthGammaTxn(final GammaTxnConfig config) {
super(config, TRANSACTIONTYPE_FAT_FIXED_LENGTH);
listenersArray = new Listeners[config.maxFixedLengthTransactionSize];
Tranlocal h = null;
for (int k = 0; k < config.maxFixedLengthTransactionSize; k++) {
Tranlocal newNode = new Tranlocal();
if (h != null) {
h.previous = newNode;
newNode.next = h;
}
h = newNode;
}
head = h;
}
@Override
public final void commit() {
if (status == TX_COMMITTED) {
return;
}
if (status != TX_ACTIVE && status != TX_PREPARED) {
throw abortCommitOnBadStatus();
}
if (abortOnly) {
throw abortCommitOnAbortOnly();
}
if (status == TX_ACTIVE) {
notifyListeners(TxnEvent.PrePrepare);
}
if (size > 0) {
if (hasWrites) {
if (status == TX_ACTIVE) {
GammaObject o = prepareChainForCommit();
if (o != null) {
throw abortOnReadWriteConflict(o);
}
}
if (commitConflict) {
config.globalConflictCounter.signalConflict();
}
final Listeners[] listenersArray = commitChain();
if (listenersArray != null) {
Listeners.openAll(listenersArray, pool);
}
} else {
releaseChain(true);
}
}
status = TX_COMMITTED;
notifyListeners(TxnEvent.PostCommit);
}
private Listeners[] commitChain() {
int listenersIndex = 0;
Tranlocal node = head;
do {
if (SHAKE_BUGS) shakeBugs();
final BaseGammaTxnRef owner = node.owner;
//if we are at the end, we can return the listenersArray.
if (owner == null) {
return listenersArray;
}
final Listeners listeners = owner.commit(node, pool);
if (listeners != null) {
listenersArray[listenersIndex] = listeners;
listenersIndex++;
}
node = node.next;
} while (node != null);
return listenersArray;
}
@Override
public final void prepare() {
if (status == TX_PREPARED) {
return;
}
if (status != TX_ACTIVE) {
throw abortPrepareOnBadStatus();
}
if (abortOnly) {
throw abortPrepareOnAbortOnly();
}
notifyListeners(TxnEvent.PrePrepare);
GammaObject o = prepareChainForCommit();
if (o != null) {
throw abortOnReadWriteConflict(o);
}
status = TX_PREPARED;
}
@SuppressWarnings({"BooleanMethodIsAlwaysInverted"})
private BaseGammaTxnRef prepareChainForCommit() {
if (skipPrepare()) {
return null;
}
Tranlocal node = head;
do {
final BaseGammaTxnRef owner = node.owner;
if (owner == null) {
return null;
}
if (SHAKE_BUGS) shakeBugs();
if (!owner.prepare(this, node)) {
return owner;
}
node = node.next;
} while (node != null);
return null;
}
@Override
public final void abort() {
if (status == TX_ABORTED) {
return;
}
if (status == TX_COMMITTED) {
throw failAbortOnAlreadyCommitted();
}
releaseChain(false);
status = TX_ABORTED;
notifyListeners(TxnEvent.PostAbort);
}
private void releaseChain(final boolean success) {
Tranlocal node = head;
while (node != null) {
final BaseGammaTxnRef owner = node.owner;
if (owner == null) {
return;
}
if (SHAKE_BUGS) shakeBugs();
if (success) {
owner.releaseAfterReading(node, pool);
} else {
owner.releaseAfterFailure(node, pool);
}
node = node.next;
}
}
@Override
public final Tranlocal getRefTranlocal(final BaseGammaTxnRef ref) {
Tranlocal node = head;
while (node != null) {
//noinspection ObjectEquality
if (node.owner == ref) {
return node;
}
if (node.owner == null) {
return null;
}
node = node.next;
}
return null;
}
@Override
public final void retry() {
if (status != TX_ACTIVE) {
throw abortRetryOnBadStatus();
}
if (!config.isBlockingAllowed()) {
throw abortRetryOnNoBlockingAllowed();
}
if (size == 0) {
throw abortRetryOnNoRetryPossible();
}
retryListener.reset();
final long listenerEra = retryListener.getEra();
boolean furtherRegistrationNeeded = true;
boolean atLeastOneRegistration = false;
Tranlocal tranlocal = head;
do {
final BaseGammaTxnRef owner = tranlocal.owner;
if (furtherRegistrationNeeded) {
switch (owner.registerChangeListener(retryListener, tranlocal, pool, listenerEra)) {
case REGISTRATION_DONE:
atLeastOneRegistration = true;
break;
case REGISTRATION_NOT_NEEDED:
furtherRegistrationNeeded = false;
atLeastOneRegistration = true;
break;
case REGISTRATION_NONE:
break;
default:
throw new IllegalStateException();
}
}
owner.releaseAfterFailure(tranlocal, pool);
tranlocal = tranlocal.next;
} while (tranlocal != null && tranlocal.owner != null);
status = TX_ABORTED;
if (!atLeastOneRegistration) {
throw abortRetryOnNoRetryPossible();
}
throw newRetryError();
}
@Override
public final Tranlocal locate(BaseGammaTxnRef o) {
if (status != TX_ACTIVE) {
throw abortLocateOnBadStatus(o);
}
if (o == null) {
throw abortLocateOnNullArgument();
}
return getRefTranlocal(o);
}
@Override
public final void hardReset() {
if (listeners != null) {
listeners.clear();
pool.putArrayList(listeners);
listeners = null;
}
status = TX_ACTIVE;
hasWrites = false;
size = 0;
remainingTimeoutNs = config.timeoutNs;
richmansMansConflictScan = config.speculativeConfiguration.get().richMansConflictScanRequired;
attempt = 1;
hasReads = false;
abortOnly = false;
commitConflict = false;
evaluatingCommute = false;
}
@Override
public final boolean softReset() {
if (attempt >= config.getMaxRetries()) {
return false;
}
if (listeners != null) {
listeners.clear();
pool.putArrayList(listeners);
listeners = null;
}
commitConflict = false;
status = TX_ACTIVE;
hasWrites = false;
size = 0;
hasReads = false;
abortOnly = false;
attempt++;
evaluatingCommute = false;
return true;
}
public final void shiftInFront(Tranlocal newHead) {
//noinspection ObjectEquality
if (newHead == head) {
return;
}
head.previous = newHead;
if (newHead.next != null) {
newHead.next.previous = newHead.previous;
}
newHead.previous.next = newHead.next;
newHead.next = head;
newHead.previous = null;
head = newHead;
}
@Override
public final boolean isReadConsistent(Tranlocal justAdded) {
if (!hasReads) {
return true;
}
if (config.readLockModeAsInt > LOCKMODE_NONE) {
return true;
}
if (config.inconsistentReadAllowed) {
return true;
}
if (richmansMansConflictScan) {
if (SHAKE_BUGS) shakeBugs();
final long currentConflictCount = config.globalConflictCounter.count();
if (localConflictCount == currentConflictCount) {
return true;
}
localConflictCount = currentConflictCount;
//we are going to fall through to do a full conflict scan
} else if (size > config.maximumPoorMansConflictScanLength) {
throw abortOnRichmanConflictScanDetected();
}
//doing a full conflict scan
Tranlocal node = head;
while (node != null) {
if (SHAKE_BUGS) shakeBugs();
//if we are at the end, we are done.
if (node.owner == null) {
break;
}
final boolean skip = !richmansMansConflictScan && node == justAdded;
if (!skip && node.owner.hasReadConflict(node)) {
return false;
}
node = node.next;
}
return true;
}
@Override
public void initLocalConflictCounter() {
if (richmansMansConflictScan && !hasReads) {
localConflictCount = config.globalConflictCounter.count();
}
}
}