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 } }