package org.multiverse.stms.gamma.transactions.lean;
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 Lean GammaTxn that is optimized for a fixed number of GammaTxnRefs.
*/
public final class LeanFixedLengthGammaTxn extends GammaTxn {
public Tranlocal head;
public int size = 0;
public boolean hasReads = false;
public final Listeners[] listenersArray;
public LeanFixedLengthGammaTxn(final GammaStm stm) {
this(new GammaTxnConfig(stm));
}
@SuppressWarnings({"ObjectAllocationInLoop"})
public LeanFixedLengthGammaTxn(final GammaTxnConfig config) {
super(config, TRANSACTIONTYPE_LEAN_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 boolean isReadConsistent(Tranlocal justAdded) {
throw new UnsupportedOperationException();
}
@Override
public final void commit() {
int s = status;
if (s == TX_COMMITTED) {
return;
}
if (s != TX_ACTIVE && s != TX_PREPARED) {
throw abortCommitOnBadStatus();
}
if (hasWrites) {
if (s == TX_ACTIVE) {
GammaObject conflictingObject = prepareChainForCommit();
if (conflictingObject != null) {
throw abortOnReadWriteConflict(conflictingObject);
}
}
if (commitConflict) {
config.globalConflictCounter.signalConflict();
}
int listenersIndex = 0;
Tranlocal node = head;
do {
final BaseGammaTxnRef owner = node.owner;
if (owner == null) {
break;
}
if (SHAKE_BUGS) shakeBugs();
final Listeners listeners = owner.leanCommit(node);
if (listeners != null) {
listenersArray[listenersIndex] = listeners;
listenersIndex++;
}
node = node.next;
} while (node != null);
if (listenersArray != null) {
Listeners.openAll(listenersArray, pool);
}
} else {
releaseReadonlyChain();
}
status = TX_COMMITTED;
}
@Override
public final void prepare() {
if (status == TX_PREPARED) {
return;
}
if (status != TX_ACTIVE) {
throw abortPrepareOnBadStatus();
}
final GammaObject conflictingObject = prepareChainForCommit();
if (conflictingObject != null) {
throw abortOnReadWriteConflict(conflictingObject);
}
status = TX_PREPARED;
}
@SuppressWarnings({"BooleanMethodIsAlwaysInverted"})
private GammaObject prepareChainForCommit() {
Tranlocal node = head;
do {
final BaseGammaTxnRef owner = node.owner;
if (owner == null) {
return null;
}
if (SHAKE_BUGS) shakeBugs();
if (node.mode == TRANLOCAL_READ) {
continue;
}
final long version = node.version;
if (owner.version != version) {
return owner;
}
int arriveStatus = owner.arriveAndExclusiveLock(64);
if (arriveStatus == FAILURE) {
return owner;
}
if ((arriveStatus & MASK_CONFLICT) != 0) {
commitConflict = true;
}
node.hasDepartObligation = (arriveStatus & MASK_UNREGISTERED) == 0;
node.lockMode = LOCKMODE_EXCLUSIVE;
if (owner.version != version) {
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();
}
releaseChainForAbort();
status = TX_ABORTED;
}
private void releaseChainForAbort() {
Tranlocal node = head;
do {
final BaseGammaTxnRef owner = node.owner;
if (owner == null) {
return;
}
if (SHAKE_BUGS) shakeBugs();
if (node.isWrite()) {
if (node.getLockMode() == LOCKMODE_EXCLUSIVE) {
if (node.hasDepartObligation()) {
node.setDepartObligation(false);
owner.departAfterFailureAndUnlock();
} else {
owner.unlockByUnregistered();
}
node.setLockMode(LOCKMODE_NONE);
}
}
node.owner = null;
node.ref_oldValue = null;
node.ref_value = null;
node = node.next;
} while (node != null);
}
private void releaseReadonlyChain() {
Tranlocal node = head;
do {
final BaseGammaTxnRef owner = node.owner;
if (owner == null) {
return;
}
if (SHAKE_BUGS) shakeBugs();
node.owner = null;
node.ref_oldValue = null;
node.ref_value = null;
node = node.next;
} while (node != null);
}
@Override
public final Tranlocal getRefTranlocal(final BaseGammaTxnRef ref) {
Tranlocal node = head;
do {
//noinspection ObjectEquality
if (node.owner == ref) {
return node;
}
if (node.owner == null) {
return null;
}
node = node.next;
} while (node != null);
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() {
status = TX_ACTIVE;
hasWrites = false;
size = 0;
remainingTimeoutNs = config.timeoutNs;
attempt = 1;
commitConflict = false;
hasReads = false;
}
@Override
public final boolean softReset() {
if (attempt >= config.getMaxRetries()) {
return false;
}
commitConflict = false;
status = TX_ACTIVE;
hasWrites = false;
size = 0;
hasReads = false;
attempt++;
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 void initLocalConflictCounter() {
//ignore
}
}