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 org.multiverse.stms.gamma.transactions.SpeculativeGammaConfiguration; import static org.multiverse.utils.Bugshaker.shakeBugs; @SuppressWarnings({"OverlyComplexClass"}) public final class FatVariableLengthGammaTxn extends GammaTxn { public Tranlocal[] array; public int size = 0; public boolean hasReads = false; public long localConflictCount; public FatVariableLengthGammaTxn(GammaStm stm) { this(new GammaTxnConfig(stm)); } public FatVariableLengthGammaTxn(GammaTxnConfig config) { super(config, TRANSACTIONTYPE_FAT_VARIABLE_LENGTH); this.array = new Tranlocal[config.minimalArrayTreeSize]; } @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 conflictingObject = doPrepare(); if (conflictingObject != null) { throw abortOnReadWriteConflict(conflictingObject); } } if (commitConflict) { config.globalConflictCounter.signalConflict(); } Listeners[] listenersArray = commitArray(); if (listenersArray != null) { Listeners.openAll(listenersArray, pool); pool.putListenersArray(listenersArray); } } else { releaseArray(true); } } status = TX_COMMITTED; notifyListeners(TxnEvent.PostCommit); } private Listeners[] commitArray() { Listeners[] listenersArray = null; int listenersIndex = 0; int itemCount = 0; //first write everything without releasing for (int k = 0; k < array.length; k++) { if (SHAKE_BUGS) shakeBugs(); final Tranlocal tranlocal = array[k]; if (tranlocal == null) { continue; } final BaseGammaTxnRef owner = tranlocal.owner; final Listeners listeners = owner.commit(tranlocal, pool); if (listeners != null) { if (listenersArray == null) { listenersArray = pool.takeListenersArray(size - itemCount); } listenersArray[listenersIndex] = listeners; listenersIndex++; } pool.put(tranlocal); itemCount++; } return listenersArray; } private void releaseArray(boolean success) { for (int k = 0; k < array.length; k++) { final Tranlocal tranlocal = array[k]; if (tranlocal != null) { if (SHAKE_BUGS) shakeBugs(); array[k] = null; if (success) { tranlocal.owner.releaseAfterReading(tranlocal, pool); } else { tranlocal.owner.releaseAfterFailure(tranlocal, pool); } pool.put(tranlocal); } } } @Override public final void prepare() { if (status == TX_PREPARED) { return; } if (status != TX_ACTIVE) { throw abortPrepareOnBadStatus(); } if (abortOnly) { throw abortPrepareOnAbortOnly(); } notifyListeners(TxnEvent.PrePrepare); if (hasWrites) { final GammaObject conflictingObject = doPrepare(); if (conflictingObject != null) { throw abortOnReadWriteConflict(conflictingObject); } } status = TX_PREPARED; } @SuppressWarnings({"BooleanMethodIsAlwaysInverted"}) private GammaObject doPrepare() { if (skipPrepare()) { return null; } for (int k = 0; k < array.length; k++) { if (SHAKE_BUGS) shakeBugs(); final Tranlocal tranlocal = array[k]; if (tranlocal == null) { continue; } final BaseGammaTxnRef owner = tranlocal.owner; if (!owner.prepare(this, tranlocal)) { return owner; } } return null; } @Override public final void abort() { if (status == TX_ABORTED) { return; } if (status == TX_COMMITTED) { throw failAbortOnAlreadyCommitted(); } if (size > 0) { releaseArray(false); } status = TX_ABORTED; notifyListeners(TxnEvent.PostAbort); } @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 Tranlocal getRefTranlocal(BaseGammaTxnRef ref) { int indexOf = indexOf(ref, ref.identityHashCode()); return indexOf == -1 ? null : array[indexOf]; } @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; for (int k = 0; k < array.length; k++) { final Tranlocal tranlocal = array[k]; if (tranlocal == null) { continue; } array[k] = null; 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); pool.put(tranlocal); } status = TX_ABORTED; if (!atLeastOneRegistration) { throw abortRetryOnNoRetryPossible(); } throw newRetryError(); } @Override public final boolean softReset() { if (attempt >= config.getMaxRetries()) { return false; } status = TX_ACTIVE; hasReads = false; hasWrites = false; size = 0; abortOnly = false; attempt++; commitConflict = false; evaluatingCommute = false; if (listeners != null) { listeners.clear(); pool.putArrayList(listeners); listeners = null; } return true; } @Override public final void hardReset() { status = TX_ACTIVE; hasReads = false; hasWrites = false; size = 0; abortOnly = false; attempt = 1; remainingTimeoutNs = config.timeoutNs; //todo: only change when the array size is different. if (array != null) { pool.putTranlocalArray(array); } array = pool.takeTranlocalArray(config.minimalArrayTreeSize); final SpeculativeGammaConfiguration speculativeConfig = config.speculativeConfiguration.get(); richmansMansConflictScan = speculativeConfig.richMansConflictScanRequired; commitConflict = false; evaluatingCommute = false; if (listeners != null) { listeners.clear(); pool.putArrayList(listeners); listeners = null; } } @Override public void initLocalConflictCounter() { if (richmansMansConflictScan && !hasReads) { localConflictCount = config.globalConflictCounter.count(); } } @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 conflictCount = config.globalConflictCounter.count(); if (localConflictCount == conflictCount) { return true; } localConflictCount = conflictCount; //we are going to fall through to do a full conflict scan } else if (size > config.maximumPoorMansConflictScanLength) { throw abortOnRichmanConflictScanDetected(); } //doing a full conflict scan for (int k = 0; k < array.length; k++) { if (SHAKE_BUGS) shakeBugs(); final Tranlocal tranlocal = array[k]; //noinspection ObjectEquality final boolean skip = tranlocal == null || (!richmansMansConflictScan && justAdded == tranlocal); if (!skip && tranlocal.owner.hasReadConflict(tranlocal)) { return false; } } return true; } public final float getUsage() { return (size * 1.0f) / array.length; } public final int size() { return size; } public final int indexOf(final BaseGammaTxnRef ref, final int hash) { int jump = 0; boolean goLeft = true; do { final int offset = goLeft ? -jump : jump; int index = (hash + offset) % array.length; if(index<0){ index = index+array.length; } final Tranlocal current = array[index]; if (current == null || current.owner == null) { return -1; } //noinspection ObjectEquality if (current.owner == ref) { return index; } final int currentHash = current.owner.identityHashCode(); goLeft = currentHash > hash; jump = jump == 0 ? 1 : jump * 2; } while (jump < array.length); return -1; } public final void attach(final Tranlocal tranlocal, final int hash) { int jump = 0; boolean goLeft = true; do { final int offset = goLeft ? -jump : jump; int index = (hash + offset) % array.length; if(index<0){ index = index+array.length; } Tranlocal current = array[index]; if (current == null) { array[index] = tranlocal; return; } final int currentHash = current.owner.identityHashCode(); goLeft = currentHash > hash; jump = jump == 0 ? 1 : jump * 2; } while (jump < array.length); expand(); attach(tranlocal, hash); } private void expand() { Tranlocal[] oldArray = array; int newSize = oldArray.length * 2; array = pool.takeTranlocalArray(newSize); for (int k = 0; k < oldArray.length; k++) { final Tranlocal tranlocal = oldArray[k]; if (tranlocal == null) { continue; } oldArray[k] = null; attach(tranlocal, tranlocal.owner.identityHashCode()); } pool.putTranlocalArray(oldArray); } }