package org.multiverse.stms.gamma.transactionalobjects; import org.multiverse.api.IsolationLevel; import org.multiverse.api.LockMode; import org.multiverse.api.Txn; import org.multiverse.api.blocking.RetryLatch; import org.multiverse.api.exceptions.LockedException; import org.multiverse.api.exceptions.TxnMandatoryException; import org.multiverse.api.functions.*; import org.multiverse.stms.gamma.GammaObjectPool; import org.multiverse.stms.gamma.GammaStm; import org.multiverse.stms.gamma.GammaStmUtils; import org.multiverse.stms.gamma.Listeners; import org.multiverse.stms.gamma.transactions.GammaTxn; import org.multiverse.stms.gamma.transactions.GammaTxnConfig; import org.multiverse.stms.gamma.transactions.fat.FatFixedLengthGammaTxn; import org.multiverse.stms.gamma.transactions.fat.FatMonoGammaTxn; import org.multiverse.stms.gamma.transactions.fat.FatVariableLengthGammaTxn; import org.multiverse.stms.gamma.transactions.lean.LeanFixedLengthGammaTxn; import org.multiverse.stms.gamma.transactions.lean.LeanMonoGammaTxn; import static java.lang.Math.max; import static org.multiverse.api.TxnThreadLocal.getThreadLocalTxn; import static org.multiverse.stms.gamma.GammaStmUtils.asGammaTxn; import static org.multiverse.stms.gamma.GammaStmUtils.getRequiredThreadLocalGammaTxn; import static org.multiverse.stms.gamma.ThreadLocalGammaObjectPool.getThreadLocalGammaObjectPool; import static org.multiverse.utils.Bugshaker.shakeBugs; @SuppressWarnings({"OverlyComplexClass", "OverlyCoupledClass"}) public abstract class BaseGammaTxnRef extends AbstractGammaObject { public final int type; @SuppressWarnings({"VolatileLongOrDoubleField"}) public volatile long long_value; public volatile Object ref_value; protected BaseGammaTxnRef(GammaStm stm, int type) { super(stm); this.type = type; } @SuppressWarnings({"BooleanMethodIsAlwaysInverted"}) public final boolean flattenCommute(final GammaTxn tx, final Tranlocal tranlocal, final int lockMode) { assert tranlocal.mode == TRANLOCAL_COMMUTING; final GammaTxnConfig config = tx.config; tx.initLocalConflictCounter(); if (!load(tx, tranlocal, lockMode, config.spinCount, tx.richmansMansConflictScan)) { return false; } tranlocal.setDirty(!config.dirtyCheck); tranlocal.mode = TRANLOCAL_WRITE; if (!tx.isReadConsistent(tranlocal)) { return false; } boolean abort = true; try { CallableNode node = tranlocal.headCallable; while (node != null) { evaluate(tranlocal, tx, node.function); CallableNode newNext = node.next; tx.pool.putCallableNode(node); node = newNext; } tranlocal.headCallable = null; abort = false; } finally { if (abort) { tx.abort(); } } return true; } private void evaluate(final Tranlocal tranlocal, GammaTxn tx, final Function function) { tx.evaluatingCommute = true; try { switch (type) { case TYPE_REF: tranlocal.ref_value = function.call(tranlocal.ref_value); break; case TYPE_INT: IntFunction intFunction = (IntFunction) function; tranlocal.long_value = intFunction.call((int) tranlocal.long_value); break; case TYPE_LONG: LongFunction longFunction = (LongFunction) function; tranlocal.long_value = longFunction.call(tranlocal.long_value); break; case TYPE_DOUBLE: DoubleFunction doubleFunction = (DoubleFunction) function; double doubleResult = doubleFunction.call(GammaStmUtils.longAsDouble(tranlocal.long_value)); tranlocal.long_value = GammaStmUtils.doubleAsLong(doubleResult); break; case TYPE_BOOLEAN: BooleanFunction booleanFunction = (BooleanFunction) function; boolean booleanResult = booleanFunction.call(GammaStmUtils.longAsBoolean(tranlocal.long_value)); tranlocal.long_value = GammaStmUtils.booleanAsLong(booleanResult); break; default: throw new IllegalStateException(); } } finally { tx.evaluatingCommute = false; } } public final Listeners commit(final Tranlocal tranlocal, final GammaObjectPool pool) { if (!tranlocal.isDirty) { releaseAfterReading(tranlocal, pool); return null; } if (type == TYPE_REF) { ref_value = tranlocal.ref_value; //we need to set them to null to prevent memory leaks. tranlocal.ref_value = null; tranlocal.ref_oldValue = null; } else { long_value = tranlocal.long_value; } version = tranlocal.version + 1; Listeners listenerAfterWrite = listeners; if (listenerAfterWrite != null) { listenerAfterWrite = ___removeListenersAfterWrite(); } releaseAfterUpdate(tranlocal, pool); return listenerAfterWrite; } public final Listeners leanCommit(final Tranlocal tranlocal) { assert type == TYPE_REF; if (tranlocal.mode == TRANLOCAL_READ) { tranlocal.ref_value = null; tranlocal.owner = null; return null; } ref_value = tranlocal.ref_value; version = tranlocal.version + 1; Listeners listenerAfterWrite = listeners; if (listenerAfterWrite != null) { listenerAfterWrite = ___removeListenersAfterWrite(); } departAfterUpdateAndUnlock(); tranlocal.ref_value = null; tranlocal.lockMode = LOCKMODE_NONE; tranlocal.owner = null; tranlocal.hasDepartObligation = false; return listenerAfterWrite; } @SuppressWarnings({"BooleanMethodIsAlwaysInverted"}) public final boolean prepare(final GammaTxn tx, final Tranlocal tranlocal) { final int mode = tranlocal.getMode(); if (mode == TRANLOCAL_CONSTRUCTING) { return true; } if (mode == TRANLOCAL_READ) { if (!tranlocal.writeSkewCheck) { return true; } return tryLockAndCheckConflict(tx, tranlocal, tx.config.spinCount, LOCKMODE_READ); } if (mode == TRANLOCAL_COMMUTING) { if (!flattenCommute(tx, tranlocal, LOCKMODE_EXCLUSIVE)) { return false; } } if (!tranlocal.isDirty) { final boolean isDirty = type == TYPE_REF ? tranlocal.ref_value != tranlocal.ref_oldValue : tranlocal.long_value != tranlocal.long_oldValue; if (!isDirty) { if (!tranlocal.writeSkewCheck) { return true; } return tryLockAndCheckConflict(tx, tranlocal, tx.config.spinCount, LOCKMODE_READ); } tranlocal.isDirty = true; } return tryLockAndCheckConflict(tx, tranlocal, tx.config.spinCount, LOCKMODE_EXCLUSIVE); } public final void releaseAfterFailure(final Tranlocal tranlocal, final GammaObjectPool pool) { if (type == TYPE_REF) { tranlocal.ref_value = null; tranlocal.ref_oldValue = null; } if (tranlocal.headCallable != null) { CallableNode node = tranlocal.headCallable; do { CallableNode next = node.next; pool.putCallableNode(node); node = next; } while (node != null); tranlocal.headCallable = null; } if (tranlocal.hasDepartObligation()) { if (tranlocal.isConstructing()) { tranlocal.setLockMode(LOCKMODE_NONE); } else if (tranlocal.getLockMode() != LOCKMODE_NONE) { departAfterFailureAndUnlock(); tranlocal.setLockMode(LOCKMODE_NONE); } else { departAfterFailure(); } tranlocal.setDepartObligation(false); } else if (tranlocal.getLockMode() != LOCKMODE_NONE) { unlockByUnregistered(); tranlocal.setLockMode(LOCKMODE_NONE); } tranlocal.owner = null; } public final void releaseAfterUpdate(final Tranlocal tranlocal, final GammaObjectPool pool) { if (type == TYPE_REF) { tranlocal.ref_value = null; tranlocal.ref_oldValue = null; } departAfterUpdateAndUnlock(); tranlocal.lockMode = LOCKMODE_NONE; tranlocal.owner = null; tranlocal.hasDepartObligation = false; } public final void releaseAfterReading(final Tranlocal tranlocal, final GammaObjectPool pool) { if (type == TYPE_REF) { tranlocal.ref_value = null; tranlocal.ref_oldValue = null; } if (tranlocal.hasDepartObligation()) { if (tranlocal.getLockMode() != LOCKMODE_NONE) { departAfterReadingAndUnlock(); tranlocal.setLockMode(LOCKMODE_NONE); } else { departAfterReading(); } tranlocal.setDepartObligation(false); } else if (tranlocal.getLockMode() != LOCKMODE_NONE) { unlockByUnregistered(); tranlocal.setLockMode(LOCKMODE_NONE); } tranlocal.owner = null; } public final boolean load( final GammaTxn tx, final Tranlocal tranlocal, final int lockMode, int spinCount, final boolean arriveNeeded) { if (lockMode != LOCKMODE_NONE) { final int result = arriveAndLock(spinCount, lockMode); if (result == FAILURE) { return false; } tranlocal.owner = this; tranlocal.version = version; if (type == TYPE_REF) { final Object value = ref_value; tranlocal.ref_value = value; tranlocal.ref_oldValue = value; } else { final long value = long_value; tranlocal.long_value = value; tranlocal.long_oldValue = value; } tranlocal.lockMode = lockMode; tranlocal.hasDepartObligation = (result & MASK_UNREGISTERED) == 0; tx.commitConflict = (result & MASK_CONFLICT) != 0; return true; } while (true) { long readLong = 0; Object readRef = null; long readVersion; if (type == TYPE_REF) { do { readVersion = version; readRef = ref_value; if (SHAKE_BUGS) shakeBugs(); } while (readVersion != version); } else { do { readVersion = version; readLong = long_value; if (SHAKE_BUGS) shakeBugs(); } while (readVersion != version); } if (SHAKE_BUGS) shakeBugs(); int arriveStatus; if (arriveNeeded) { arriveStatus = arrive(spinCount); } else if (waitForExclusiveLockToBecomeFree(spinCount)) { arriveStatus = MASK_SUCCESS + MASK_UNREGISTERED; } else { arriveStatus = FAILURE; } if (arriveStatus == FAILURE) { return false; } if (SHAKE_BUGS) shakeBugs(); if (version == readVersion) { tranlocal.owner = this; tranlocal.version = readVersion; tranlocal.lockMode = LOCKMODE_NONE; tranlocal.hasDepartObligation = (arriveStatus & MASK_UNREGISTERED) == 0; if (type == TYPE_REF) { tranlocal.ref_value = readRef; tranlocal.ref_oldValue = readRef; } else { tranlocal.long_value = readLong; tranlocal.long_oldValue = readLong; } return true; } //we are not lucky, the value has changed. But before retrying, we need to depart if the arrive was normal if ((arriveStatus & MASK_UNREGISTERED) == 0) { departAfterFailure(); } } } public final Tranlocal openForConstruction(GammaTxn tx) { if (tx == null) { throw new NullPointerException(); } final int type = tx.transactionType; if (type == TRANSACTIONTYPE_FAT_MONO) { return openForConstruction((FatMonoGammaTxn) tx); } else if (type == TRANSACTIONTYPE_FAT_FIXED_LENGTH) { return openForConstruction((FatFixedLengthGammaTxn) tx); } else if (type == TRANSACTIONTYPE_FAT_VARIABLE_LENGTH) { return openForConstruction((FatVariableLengthGammaTxn) tx); } else { throw tx.abortOpenForConstructionRequired(this); } } private void initTranlocalForConstruction(final Tranlocal tranlocal) { tranlocal.isDirty = true; tranlocal.mode = TRANLOCAL_CONSTRUCTING; tranlocal.setLockMode(LOCKMODE_EXCLUSIVE); tranlocal.setDepartObligation(true); if (type == TYPE_REF) { tranlocal.ref_value = null; tranlocal.ref_oldValue = null; } else { tranlocal.long_value = 0; tranlocal.long_oldValue = 0; } } public final Tranlocal openForConstruction(FatMonoGammaTxn tx) { if (tx.status != TX_ACTIVE) { throw tx.abortOpenForConstructionOnBadStatus(this); } final GammaTxnConfig config = tx.config; //noinspection ObjectEquality if (config.stm != stm) { throw tx.abortOpenForConstructionOnBadStm(this); } if (config.readonly) { throw tx.abortOpenForConstructionOnReadonly(this); } if (tx.evaluatingCommute) { throw tx.abortOnOpenForConstructionWhileEvaluatingCommute(this); } final Tranlocal tranlocal = tx.tranlocal; //noinspection ObjectEquality if (tranlocal.owner == this) { if (!tranlocal.isConstructing()) { throw tx.abortOpenForConstructionOnBadReference(this); } return tranlocal; } if (tranlocal.owner != null) { throw tx.abortOnTransactionTooSmall(2); } tx.hasWrites = true; tranlocal.owner = this; initTranlocalForConstruction(tranlocal); return tranlocal; } public final Tranlocal openForConstruction(FatVariableLengthGammaTxn tx) { if (tx.status != TX_ACTIVE) { throw tx.abortOpenForConstructionOnBadStatus(this); } final GammaTxnConfig config = tx.config; //noinspection ObjectEquality if (config.stm != stm) { throw tx.abortOpenForConstructionOnBadStm(this); } if (config.readonly) { throw tx.abortOpenForConstructionOnReadonly(this); } if (tx.evaluatingCommute) { throw tx.abortOnOpenForConstructionWhileEvaluatingCommute(this); } final int identityHash = identityHashCode(); final int indexOf = tx.indexOf(this, identityHash); if (indexOf > -1) { final Tranlocal tranlocal = tx.array[indexOf]; if (!tranlocal.isConstructing()) { throw tx.abortOpenForConstructionOnBadReference(this); } return tranlocal; } final Tranlocal tranlocal = tx.pool.take(this); tranlocal.owner = this; initTranlocalForConstruction(tranlocal); tx.hasWrites = true; tx.attach(tranlocal, identityHash); tx.size++; return tranlocal; } public final Tranlocal openForConstruction(FatFixedLengthGammaTxn tx) { if (tx.status != TX_ACTIVE) { throw tx.abortOpenForConstructionOnBadStatus(this); } final GammaTxnConfig config = tx.config; //noinspection ObjectEquality if (config.stm != stm) { throw tx.abortOpenForConstructionOnBadStm(this); } if (config.readonly) { throw tx.abortOpenForConstructionOnReadonly(this); } if (tx.evaluatingCommute) { throw tx.abortOnOpenForConstructionWhileEvaluatingCommute(this); } Tranlocal found = null; Tranlocal newNode = null; Tranlocal node = tx.head; while (true) { if (node == null) { break; } else if (node.owner == this) { found = node; break; } else if (node.owner == null) { newNode = node; break; } else { node = node.next; } } if (found != null) { if (!found.isConstructing()) { throw tx.abortOpenForConstructionOnBadReference(this); } tx.shiftInFront(found); return found; } if (newNode == null) { throw tx.abortOnTransactionTooSmall(config.maxFixedLengthTransactionSize + 1); } newNode.owner = this; initTranlocalForConstruction(newNode); tx.size++; tx.shiftInFront(newNode); tx.hasWrites = true; return newNode; } // ============================================================================================ // =============================== open for read ============================================== // ============================================================================================ public final Tranlocal openForRead(final GammaTxn tx, final int lockMode) { if (tx == null) { throw new NullPointerException(); } final int type = tx.transactionType; if (type == TRANSACTIONTYPE_LEAN_MONO) { return openForRead((LeanMonoGammaTxn) tx, lockMode); } else if (type == TRANSACTIONTYPE_LEAN_FIXED_LENGTH) { return openForRead((LeanFixedLengthGammaTxn) tx, lockMode); } else if (type == TRANSACTIONTYPE_FAT_MONO) { return openForRead((FatMonoGammaTxn) tx, lockMode); } else if (type == TRANSACTIONTYPE_FAT_FIXED_LENGTH) { return openForRead((FatFixedLengthGammaTxn) tx, lockMode); } else { return openForRead((FatVariableLengthGammaTxn) tx, lockMode); } } public final Tranlocal openForRead(final LeanMonoGammaTxn tx, int lockMode) { if (tx.status != TX_ACTIVE) { throw tx.abortOpenForReadOnBadStatus(this); } if (lockMode != LOCKMODE_NONE) { throw tx.abortOpenForReadOrWriteOnExplicitLockingDetected(this); } final Tranlocal tranlocal = tx.tranlocal; //noinspection ObjectEquality if (tranlocal.owner == this) { return tranlocal; } if (tranlocal.owner != null) { throw tx.abortOnTransactionTooSmall(2); } final GammaTxnConfig config = tx.config; if (config.stm != stm) { throw tx.abortOpenForReadOnBadStm(this); } if (type != TYPE_REF) { throw tx.abortOpenForReadOnNonRefTypeDetected(this); } tranlocal.mode = TRANLOCAL_READ; tranlocal.owner = this; for (; ;) { //do the read of the version and ref. It needs to be repeated to make sure that the version we read, belongs to the //value. Object readRef; long readVersion; do { readVersion = version; readRef = ref_value; if (SHAKE_BUGS) shakeBugs(); } while (readVersion != version); //wait for the exclusive lock to come available. int spinCount = 64; for (; ;) { if (SHAKE_BUGS) shakeBugs(); if (!hasExclusiveLock()) { break; } spinCount--; if (spinCount < 0) { throw tx.abortOnReadWriteConflict(this); } } if (SHAKE_BUGS) shakeBugs(); //check if the version is still the same, if it is not, we have read illegal memory, //In that case we are going to try again. if (readVersion == version) { //at this point we are sure that the read was unlocked. tranlocal.version = readVersion; tranlocal.ref_value = readRef; break; } } return tranlocal; } public final Tranlocal openForRead(final LeanFixedLengthGammaTxn tx, int lockMode) { if (tx.status != TX_ACTIVE) { throw tx.abortOpenForReadOnBadStatus(this); } if (lockMode != LOCKMODE_NONE) { throw tx.abortOpenForReadOrWriteOnExplicitLockingDetected(this); } if (tx.head.owner == this) { return tx.head; } //look inside the transaction if it already is opened for read or otherwise look for an empty spot to //place the read. Tranlocal found = null; Tranlocal newNode = null; Tranlocal node = tx.head; while (true) { if (node == null) { break; } else if (node.owner == this) { found = node; break; } else if (node.owner == null) { newNode = node; break; } else { node = node.next; } } //we have found it. if (found != null) { tx.shiftInFront(found); return found; } //we have not found it, but there also is no spot available. if (newNode == null) { throw tx.abortOnTransactionTooSmall(tx.config.maxFixedLengthTransactionSize + 1); } final GammaTxnConfig config = tx.config; if (config.stm != stm) { throw tx.abortOpenForReadOnBadStm(this); } if (type != TYPE_REF) { throw tx.abortOpenForReadOnNonRefTypeDetected(this); } int size = tx.size; if (size > config.maximumPoorMansConflictScanLength) { throw tx.abortOnRichmanConflictScanDetected(); } //load it newNode.mode = TRANLOCAL_READ; newNode.isDirty = false; newNode.owner = this; while (true) { //JMM: nothing can jump behind the following statement long readVersion; Object readRef; do { readVersion = version; readRef = ref_value; if (SHAKE_BUGS) shakeBugs(); } while (readVersion != version); //wait for the exclusive lock to come available. int spinCount = 64; for (; ;) { if (SHAKE_BUGS) shakeBugs(); if (!hasExclusiveLock()) { break; } spinCount--; if (spinCount < 0) { throw tx.abortOnReadWriteConflict(this); } } if (SHAKE_BUGS) shakeBugs(); //check if the version and value we read are still the same, if they are not, we have read illegal memory, //so we are going to try again. if (readVersion == version && readRef == ref_value) { //at this point we are sure that the read was unlocked. newNode.version = readVersion; newNode.ref_value = readRef; break; } } tx.size = size + 1; //lets put it in the front it isn't the first one that is opened. if (tx.size > 1) { tx.shiftInFront(newNode); } //check if the transaction still is read consistent. if (tx.hasReads) { node = tx.head; do { final BaseGammaTxnRef owner = node.owner; //if we are at the end, we are done. if (owner == null) { break; } if (SHAKE_BUGS) shakeBugs(); if (node != newNode && (owner.hasExclusiveLock() || owner.version != node.version)) { throw tx.abortOnReadWriteConflict(this); } node = node.next; } while (node != null); } else { tx.hasReads = true; } //we are done, the load was correct and the transaction still is read consistent. return newNode; } private static void initTranlocalForRead(final GammaTxnConfig config, final Tranlocal tranlocal) { tranlocal.isDirty = false; tranlocal.mode = TRANLOCAL_READ; tranlocal.writeSkewCheck = config.isolationLevel == IsolationLevel.Serializable; tranlocal.version = -1; } public final Tranlocal openForRead(final FatMonoGammaTxn tx, int lockMode) { if (tx.status != TX_ACTIVE) { throw tx.abortOpenForReadOnBadStatus(this); } final GammaTxnConfig config = tx.config; //noinspection ObjectEquality if (config.stm != stm) { throw tx.abortOpenForReadOnBadStm(this); } if (tx.evaluatingCommute) { throw tx.abortOnOpenForReadWhileEvaluatingCommute(this); } lockMode = config.readLockModeAsInt <= lockMode ? lockMode : config.readLockModeAsInt; final Tranlocal tranlocal = tx.tranlocal; //noinspection ObjectEquality if (tranlocal.owner == this) { //we have found the tranlocal we are looking for. int mode = tranlocal.mode; if (mode == TRANLOCAL_CONSTRUCTING) { return tranlocal; } if (mode == TRANLOCAL_COMMUTING) { if (!flattenCommute(tx, tranlocal, lockMode)) { throw tx.abortOnReadWriteConflict(this); } return tranlocal; } if (lockMode > tranlocal.getLockMode()) { if (!tryLockAndCheckConflict(tx, tranlocal, config.spinCount, lockMode)) { throw tx.abortOnReadWriteConflict(this); } } return tranlocal; } if (tranlocal.owner != null) { throw tx.abortOnTransactionTooSmall(2); } initTranlocalForRead(config, tranlocal); if (!load(tx, tranlocal, lockMode, config.spinCount, tx.richmansMansConflictScan)) { throw tx.abortOnReadWriteConflict(this); } return tranlocal; } public final Tranlocal openForRead(final FatFixedLengthGammaTxn tx, int desiredLockMode) { if (tx.status != TX_ACTIVE) { throw tx.abortOpenForReadOnBadStatus(this); } final GammaTxnConfig config = tx.config; //noinspection ObjectEquality if (config.stm != stm) { throw tx.abortOpenForReadOnBadStm(this); } if (tx.evaluatingCommute) { throw tx.abortOnOpenForReadWhileEvaluatingCommute(this); } Tranlocal found = null; Tranlocal newNode = null; Tranlocal node = tx.head; while (true) { if (node == null) { break; } else if (node.owner == this) { found = node; break; } else if (node.owner == null) { newNode = node; break; } else { node = node.next; } } desiredLockMode = config.readLockModeAsInt <= desiredLockMode ? desiredLockMode : config.readLockModeAsInt; if (found != null) { final int mode = found.mode; if (mode == TRANLOCAL_CONSTRUCTING) { return found; } if (mode == TRANLOCAL_COMMUTING) { if (!flattenCommute(tx, found, desiredLockMode)) { throw tx.abortOnReadWriteConflict(this); } return found; } if (desiredLockMode > found.getLockMode()) { if (!tryLockAndCheckConflict(tx, found, config.spinCount, desiredLockMode)) { throw tx.abortOnReadWriteConflict(this); } } tx.shiftInFront(found); return found; } if (newNode == null) { throw tx.abortOnTransactionTooSmall(config.maxFixedLengthTransactionSize + 1); } tx.size++; initTranlocalForRead(config, newNode); final boolean hasReadsBeforeLoading = tx.hasReads; if (!hasReadsBeforeLoading) { tx.localConflictCount = config.globalConflictCounter.count(); tx.hasReads = true; } if (!load(tx, newNode, desiredLockMode, config.spinCount, tx.richmansMansConflictScan)) { throw tx.abortOnReadWriteConflict(this); } //if (hasReadsBeforeLoading && !tx.isReadConsistent(newNode)) { if (!tx.isReadConsistent(newNode)) { throw tx.abortOnReadWriteConflict(this); } tx.shiftInFront(newNode); return newNode; } public final Tranlocal openForRead(final FatVariableLengthGammaTxn tx, int desiredLockMode) { if (tx.status != TX_ACTIVE) { throw tx.abortOpenForReadOnBadStatus(this); } final GammaTxnConfig config = tx.config; //noinspection ObjectEquality if (config.stm != stm) { throw tx.abortOpenForReadOnBadStm(this); } if (tx.evaluatingCommute) { throw tx.abortOnOpenForReadWhileEvaluatingCommute(this); } desiredLockMode = config.readLockModeAsInt <= desiredLockMode ? desiredLockMode : config.readLockModeAsInt; final int identityHash = identityHashCode(); final int indexOf = tx.indexOf(this, identityHash); if (indexOf > -1) { final Tranlocal tranlocal = tx.array[indexOf]; final int mode = tranlocal.mode; if (mode == TRANLOCAL_CONSTRUCTING) { return tranlocal; } if (mode == TRANLOCAL_COMMUTING) { if (!flattenCommute(tx, tranlocal, desiredLockMode)) { throw tx.abortOnReadWriteConflict(this); } return tranlocal; } if (desiredLockMode > tranlocal.getLockMode()) { if (!tryLockAndCheckConflict(tx, tranlocal, config.spinCount, desiredLockMode)) { throw tx.abortOnReadWriteConflict(this); } } return tranlocal; } final Tranlocal tranlocal = tx.pool.take(this); initTranlocalForRead(config, tranlocal); tx.attach(tranlocal, identityHash); tx.size++; final boolean hasReadsBeforeLoading = tx.hasReads; if (!hasReadsBeforeLoading) { tx.hasReads = true; tx.localConflictCount = config.globalConflictCounter.count(); } if (!load(tx, tranlocal, desiredLockMode, config.spinCount, tx.richmansMansConflictScan)) { throw tx.abortOnReadWriteConflict(this); } //if (hasReadsBeforeLoading && !tx.isReadConsistent(tranlocal)) { if (!tx.isReadConsistent(tranlocal)) { throw tx.abortOnReadWriteConflict(this); } return tranlocal; } // ============================================================================================ // =============================== open for write ============================================= // ============================================================================================ public final Tranlocal openForWrite(final GammaTxn tx, final int lockMode) { if (tx == null) { throw new NullPointerException(); } final int type = tx.transactionType; if (type == TRANSACTIONTYPE_LEAN_MONO) { return openForWrite((LeanMonoGammaTxn) tx, lockMode); } else if (type == TRANSACTIONTYPE_LEAN_FIXED_LENGTH) { return openForWrite((LeanFixedLengthGammaTxn) tx, lockMode); } else if (type == TRANSACTIONTYPE_FAT_MONO) { return openForWrite((FatMonoGammaTxn) tx, lockMode); } else if (type == TRANSACTIONTYPE_FAT_FIXED_LENGTH) { return openForWrite((FatFixedLengthGammaTxn) tx, lockMode); } else { return openForWrite((FatVariableLengthGammaTxn) tx, lockMode); } } public final Tranlocal openForWrite(final LeanMonoGammaTxn tx, int lockMode) { final Tranlocal tranlocal = openForRead(tx, lockMode); if (!tx.hasWrites) { tx.hasWrites = true; } if (tranlocal.mode == TRANLOCAL_READ) { tranlocal.mode = TRANLOCAL_WRITE; } return tranlocal; } public final Tranlocal openForWrite(final LeanFixedLengthGammaTxn tx, int lockMode) { final Tranlocal tranlocal = openForRead(tx, lockMode); if (!tx.hasWrites) { tx.hasWrites = true; } if (tranlocal.mode == TRANLOCAL_READ) { tranlocal.mode = TRANLOCAL_WRITE; } return tranlocal; } public final Tranlocal openForWrite(final FatMonoGammaTxn tx, final int desiredLockMode) { GammaTxnConfig config = tx.config; Tranlocal tranlocal = openForRead(tx, max(desiredLockMode, config.writeLockModeAsInt)); if (config.readonly) { throw tx.abortOpenForWriteOnReadonly(this); } if (!tx.hasWrites) { tx.hasWrites = true; } if (tranlocal.mode == TRANLOCAL_READ) { tranlocal.mode = TRANLOCAL_WRITE; tranlocal.writeSkewCheck = config.isolationLevel == IsolationLevel.Serializable; tranlocal.setDirty(!config.dirtyCheck); } return tranlocal; } public final Tranlocal openForWrite(final FatFixedLengthGammaTxn tx, final int lockMode) { GammaTxnConfig config = tx.config; Tranlocal tranlocal = openForRead(tx, max(lockMode, config.writeLockModeAsInt)); if (config.readonly) { throw tx.abortOpenForWriteOnReadonly(this); } if (!tx.hasWrites) { tx.hasWrites = true; } if (tranlocal.mode == TRANLOCAL_READ) { tranlocal.mode = TRANLOCAL_WRITE; tranlocal.writeSkewCheck = config.isolationLevel == IsolationLevel.Serializable; tranlocal.setDirty(!config.dirtyCheck); } return tranlocal; } public final Tranlocal openForWrite(final FatVariableLengthGammaTxn tx, final int lockMode) { GammaTxnConfig config = tx.config; Tranlocal tranlocal = openForRead(tx, max(lockMode, config.writeLockModeAsInt)); if (config.readonly) { throw tx.abortOpenForWriteOnReadonly(this); } if (!tx.hasWrites) { tx.hasWrites = true; } if (tranlocal.mode == TRANLOCAL_READ) { tranlocal.mode = TRANLOCAL_WRITE; tranlocal.writeSkewCheck = config.isolationLevel == IsolationLevel.Serializable; tranlocal.setDirty(!config.dirtyCheck); } return tranlocal; } // ============================================================================================ // ================================= open for commute ========================================= // ============================================================================================ public final void openForCommute(final GammaTxn tx, final Function function) { if (tx == null) { throw new NullPointerException("txn can't be null"); } final int type = tx.transactionType; if (type == TRANSACTIONTYPE_FAT_MONO) { openForCommute((FatMonoGammaTxn) tx, function); } else if (type == TRANSACTIONTYPE_FAT_FIXED_LENGTH) { openForCommute((FatFixedLengthGammaTxn) tx, function); } else if (type == TRANSACTIONTYPE_FAT_VARIABLE_LENGTH) { openForCommute((FatVariableLengthGammaTxn) tx, function); } else { throw tx.abortCommuteOnCommuteDetected(this); } } private void initTranlocalForCommute(final GammaTxnConfig config, final Tranlocal tranlocal) { tranlocal.owner = this; tranlocal.mode = TRANLOCAL_COMMUTING; tranlocal.isDirty = !config.dirtyCheck; tranlocal.writeSkewCheck = false; } public final void openForCommute(final FatMonoGammaTxn tx, final Function function) { if (tx == null) { throw new NullPointerException(); } if (tx.status != TX_ACTIVE) { throw tx.abortCommuteOnBadStatus(this, function); } if (function == null) { throw tx.abortCommuteOnNullFunction(this); } if (tx.evaluatingCommute) { throw tx.abortOnOpenForCommuteWhileEvaluatingCommute(this); } final GammaTxnConfig config = tx.config; //noinspection ObjectEquality if (config.stm != stm) { throw tx.abortCommuteOnBadStm(this); } if (config.isReadonly()) { throw tx.abortCommuteOnReadonly(this); } if (config.writeLockModeAsInt > LOCKMODE_NONE) { } final Tranlocal tranlocal = tx.tranlocal; //noinspection ObjectEquality if (tranlocal.owner == this) { if (tranlocal.isCommuting()) { tranlocal.addCommutingFunction(tx.pool, function); return; } if (tranlocal.isRead()) { tranlocal.mode = TRANLOCAL_WRITE; tx.hasWrites = true; } boolean abort = true; try { evaluate(tranlocal, tx, function); abort = false; } finally { if (abort) { tx.abort(); } } return; } if (tranlocal.owner != null) { throw tx.abortOnTransactionTooSmall(2); } tx.hasWrites = true; initTranlocalForCommute(config, tranlocal); tranlocal.addCommutingFunction(tx.pool, function); int writeLockMode = config.writeLockModeAsInt; if (writeLockMode > LOCKMODE_NONE) { flattenCommute(tx, tranlocal, writeLockMode); } } public final void openForCommute(final FatFixedLengthGammaTxn tx, final Function function) { if (tx == null) { throw new NullPointerException(); } if (tx.status != TX_ACTIVE) { throw tx.abortCommuteOnBadStatus(this, function); } if (function == null) { throw tx.abortCommuteOnNullFunction(this); } if (tx.evaluatingCommute) { throw tx.abortOnOpenForCommuteWhileEvaluatingCommute(this); } final GammaTxnConfig config = tx.config; //noinspection ObjectEquality if (config.stm != stm) { throw tx.abortCommuteOnBadStm(this); } if (config.isReadonly()) { throw tx.abortCommuteOnReadonly(this); } Tranlocal found = null; Tranlocal newNode = null; Tranlocal node = tx.head; if (config.writeLockModeAsInt > LOCKMODE_NONE) { found = openForWrite(tx, config.writeLockModeAsInt); } else { while (true) { if (node == null) { break; } else //noinspection ObjectEquality if (node.owner == this) { found = node; break; } else if (node.owner == null) { newNode = node; break; } else { node = node.next; } } } if (found != null) { if (found.isCommuting()) { found.addCommutingFunction(tx.pool, function); return; } if (found.isRead()) { found.mode = TRANLOCAL_WRITE; tx.hasWrites = true; } boolean abort = true; try { evaluate(found, tx, function); abort = false; } finally { if (abort) { tx.abort(); } } return; } if (newNode == null) { throw tx.abortOnTransactionTooSmall(config.maxFixedLengthTransactionSize + 1); } tx.size++; tx.shiftInFront(newNode); tx.hasWrites = true; initTranlocalForCommute(config, newNode); newNode.addCommutingFunction(tx.pool, function); int writeLockMode = config.writeLockModeAsInt; if (writeLockMode > LOCKMODE_NONE) { flattenCommute(tx, newNode, writeLockMode); } } public final void openForCommute(final FatVariableLengthGammaTxn tx, final Function function) { if (tx == null) { throw new NullPointerException(); } if (tx.status != TX_ACTIVE) { throw tx.abortCommuteOnBadStatus(this, function); } if (function == null) { throw tx.abortCommuteOnNullFunction(this); } if (tx.evaluatingCommute) { throw tx.abortOnOpenForCommuteWhileEvaluatingCommute(this); } final GammaTxnConfig config = tx.config; //noinspection ObjectEquality if (config.stm != stm) { throw tx.abortCommuteOnBadStm(this); } if (config.isReadonly()) { throw tx.abortCommuteOnReadonly(this); } final int identityHash = identityHashCode(); final int indexOf = tx.indexOf(this, identityHash); if (indexOf > -1) { final Tranlocal tranlocal = tx.array[indexOf]; if (tranlocal.isCommuting()) { tranlocal.addCommutingFunction(tx.pool, function); return; } if (tranlocal.isRead()) { tranlocal.mode = TRANLOCAL_WRITE; tx.hasWrites = true; } boolean abort = true; try { evaluate(tranlocal, tx, function); abort = false; } finally { if (abort) { tx.abort(); } } return; } final Tranlocal tranlocal = tx.pool.take(this); initTranlocalForCommute(config, tranlocal); tx.hasWrites = true; tx.attach(tranlocal, identityHash); tx.size++; tranlocal.addCommutingFunction(tx.pool, function); int writeLockMode = config.writeLockModeAsInt; if (writeLockMode > LOCKMODE_NONE) { flattenCommute(tx, tranlocal, writeLockMode); } } public final void ensure() { ensure(getRequiredThreadLocalGammaTxn()); } public final void ensure(final Txn self) { ensure(asGammaTxn(self)); } public final void ensure(final GammaTxn tx) { if (tx == null) { throw new NullPointerException(); } if (tx.status != TX_ACTIVE) { throw tx.abortEnsureOnBadStatus(this); } if (tx.isLean()) { throw tx.abortEnsureOnEnsureDetected(this); } if (tx.config.readonly) { return; } final Tranlocal tranlocal = openForRead(tx, LOCKMODE_NONE); tranlocal.writeSkewCheck = true; } protected final long getLong(final GammaTxn tx, final LockMode lockMode) { assert type != TYPE_REF; if (tx == null) { throw new NullPointerException(); } if (tx.status != TX_ACTIVE) { throw tx.abortOpenForReadOnBadStatus(this); } if (lockMode == null) { throw tx.abortOpenForReadOnNullLockMode(this); } return openForRead(tx, lockMode.asInt()).long_value; } protected final Object getObject(final GammaTxn tx, final LockMode lockMode) { assert type == TYPE_REF; if (tx == null) { throw new NullPointerException(); } if (tx.status != TX_ACTIVE) { throw tx.abortOpenForReadOnBadStatus(this); } if (lockMode == null) { throw tx.abortOpenForReadOnNullLockMode(this); } return openForRead(tx, lockMode.asInt()).ref_value; } protected final long setLong(final GammaTxn tx, final LockMode lockMode, final long newValue, final boolean returnOld) { assert type != TYPE_REF; if (tx == null) { throw new NullPointerException(); } if (tx.status != TX_ACTIVE) { throw tx.abortOpenForReadOnBadStatus(this); } if (lockMode == null) { throw tx.abortOpenForReadOnNullLockMode(this); } final Tranlocal tranlocal = openForWrite(tx, lockMode.asInt()); final long oldValue = tranlocal.long_value; tranlocal.long_value = newValue; return returnOld ? oldValue : newValue; } protected final Object setObject(final GammaTxn tx, final LockMode lockMode, final Object newValue, final boolean returnOld) { assert type == TYPE_REF; if (tx == null) { throw new NullPointerException(); } if (tx.status != TX_ACTIVE) { throw tx.abortOpenForReadOnBadStatus(this); } if (lockMode == null) { throw tx.abortOpenForReadOnNullLockMode(this); } final Tranlocal tranlocal = openForWrite(tx, lockMode.asInt()); final Object oldValue = tranlocal.ref_value; tranlocal.ref_value = newValue; return returnOld ? oldValue : newValue; } public final long atomicGetLong() { assert type != TYPE_REF; int attempt = 1; do { if (!hasExclusiveLock()) { long read = long_value; if (!hasExclusiveLock()) { return read; } } stm.defaultBackoffPolicy.delayUninterruptible(attempt); attempt++; } while (attempt <= stm.spinCount); throw new LockedException(); } public final Object atomicObjectGet() { assert type == TYPE_REF; int attempt = 1; do { if (!hasExclusiveLock()) { Object read = ref_value; if (!hasExclusiveLock()) { return read; } } stm.defaultBackoffPolicy.delayUninterruptible(attempt); attempt++; } while (attempt <= stm.spinCount); throw new LockedException(); } public final long atomicSetLong(final long newValue, boolean returnOld) { assert type != TYPE_REF; final int arriveStatus = arriveAndExclusiveLockOrBackoff(); if (arriveStatus == FAILURE) { throw new LockedException(); } final long oldValue = long_value; if (oldValue == newValue) { if ((arriveStatus & MASK_UNREGISTERED) != 0) { unlockByUnregistered(); } else { departAfterReadingAndUnlock(); } return newValue; } if ((arriveStatus & MASK_CONFLICT) != 0) { stm.globalConflictCounter.signalConflict(); } long_value = newValue; //noinspection NonAtomicOperationOnVolatileField version++; final Listeners listeners = ___removeListenersAfterWrite(); departAfterUpdateAndUnlock(); if (listeners != null) { final GammaObjectPool pool = getThreadLocalGammaObjectPool(); listeners.openAll(pool); } return returnOld ? oldValue : newValue; } public final Object atomicSetObject(final Object newValue, boolean returnOld) { assert type == TYPE_REF; final int arriveStatus = arriveAndExclusiveLockOrBackoff(); if (arriveStatus == FAILURE) { throw new LockedException(); } final Object oldValue = ref_value; if (oldValue == newValue) { if ((arriveStatus & MASK_UNREGISTERED) != 0) { unlockByUnregistered(); } else { departAfterReadingAndUnlock(); } return newValue; } if ((arriveStatus & MASK_CONFLICT) != 0) { stm.globalConflictCounter.signalConflict(); } ref_value = newValue; //noinspection NonAtomicOperationOnVolatileField version++; final Listeners listeners = ___removeListenersAfterWrite(); departAfterUpdateAndUnlock(); if (listeners != null) { final GammaObjectPool pool = getThreadLocalGammaObjectPool(); listeners.openAll(pool); } return returnOld ? oldValue : newValue; } public final boolean atomicCompareAndSetLong(final long expectedValue, final long newValue) { final int arriveStatus = arriveAndExclusiveLockOrBackoff(); if (arriveStatus == FAILURE) { throw new LockedException(); } final long currentValue = long_value; if (currentValue != expectedValue) { departAfterFailureAndUnlock(); return false; } if (expectedValue == newValue) { if ((arriveStatus & MASK_UNREGISTERED) != 0) { unlockByUnregistered(); } else { departAfterReadingAndUnlock(); } return true; } if ((arriveStatus & MASK_CONFLICT) != 0) { stm.globalConflictCounter.signalConflict(); } long_value = newValue; //noinspection NonAtomicOperationOnVolatileField version++; final Listeners listeners = ___removeListenersAfterWrite(); departAfterUpdateAndUnlock(); if (listeners != null) { listeners.openAll(getThreadLocalGammaObjectPool()); } return true; } @Override public final void acquire(final LockMode desiredLockMode) { final GammaTxn tx = (GammaTxn) getThreadLocalTxn(); if (tx == null) { throw new TxnMandatoryException(); } acquire(tx, desiredLockMode); } @Override public final void acquire(final Txn tx, final LockMode desiredLockMode) { acquire((GammaTxn) tx, desiredLockMode); } public final void acquire(final GammaTxn tx, final LockMode lockMode) { if (tx == null) { throw new NullPointerException(); } if (lockMode == null) { throw tx.abortAcquireOnNullLockMode(this); } openForRead(tx, lockMode.asInt()); } /** * Tries to acquire a lock on a previous read/written tranlocal and checks for conflict. * <p/> * If the lockMode == LOCKMODE_NONE, this call is ignored. * <p/> * The call to this method can safely made if the current lock level is higher the the desired LockMode. * <p/> * If the can't be acquired, no changes are made on the tranlocal. * * @param tx * @param tranlocal the tranlocal * @param spinCount the maximum number of times to spin * @param desiredLockMode * @return true if the lock was acquired successfully and there was no conflict. */ public final boolean tryLockAndCheckConflict( final GammaTxn tx, final Tranlocal tranlocal, final int spinCount, final int desiredLockMode) { final int currentLockMode = tranlocal.getLockMode(); //if the currentLockMode mode is higher or equal than the desired lockmode, we are done. if (currentLockMode >= desiredLockMode) { return true; } //no lock currently is acquired, lets acquire it. if (currentLockMode == LOCKMODE_NONE) { final long expectedVersion = tranlocal.version; //if the version already is different, there is a conflict, we are done since since the lock doesn't need to be acquired. if (expectedVersion != version) { return false; } if (tranlocal.hasDepartObligation()) { int result = lockAfterArrive(spinCount, desiredLockMode); if (result == FAILURE) { return false; } if ((result & MASK_CONFLICT) != 0) { tx.commitConflict = true; } if (version != expectedVersion) { tranlocal.setDepartObligation(false); departAfterFailureAndUnlock(); return false; } } else { //we need to arrive as well because the the tranlocal was readbiased, and no real arrive was done. final int result = arriveAndLock(spinCount, desiredLockMode); if (result == FAILURE) { return false; } tranlocal.setLockMode(desiredLockMode); if ((result & MASK_UNREGISTERED) == 0) { tranlocal.hasDepartObligation = true; } if ((result & MASK_CONFLICT) != 0) { tx.commitConflict = true; } if (version != expectedVersion) { return false; } } tranlocal.setLockMode(desiredLockMode); return true; } //if a readlock is acquired, we need to upgrade it to a write/exclusive-lock if (currentLockMode == LOCKMODE_READ) { int result = upgradeReadLock(spinCount, desiredLockMode == LOCKMODE_EXCLUSIVE); if (result == FAILURE) { return false; } if ((result & MASK_CONFLICT) != 0) { tx.commitConflict = true; } tranlocal.setLockMode(desiredLockMode); return true; } //so we have the write lock, its needs to be upgraded to a commit lock. if (upgradeWriteLock()) { tx.commitConflict = true; } tranlocal.setLockMode(LOCKMODE_EXCLUSIVE); return true; } public final int registerChangeListener( final RetryLatch latch, final Tranlocal tranlocal, final GammaObjectPool pool, final long listenerEra) { if (tranlocal.isCommuting() || tranlocal.isConstructing()) { return REGISTRATION_NONE; } final long version = tranlocal.version; if (version != this.version) { //if it currently already contains a different version, we are done. latch.open(listenerEra); return REGISTRATION_NOT_NEEDED; } //we are going to register the listener since the current value still matches with is active. //But it could be that the registration completes after the write has happened. Listeners update = pool.takeListeners(); //update.threadName = Thread.currentThread().getName(); update.listener = latch; update.listenerEra = listenerEra; //we need to do this in a loop because other register thread could be contending for the same //listeners field. while (true) { if (version != this.version) { //if it currently already contains a different version, we are done. latch.open(listenerEra); return REGISTRATION_NOT_NEEDED; } //the listeners object is mutable, but as long as it isn't yet registered, this calling //thread has full ownership of it. final Listeners current = listeners; update.next = current; //lets try to register our listeners. final boolean registered = ___unsafe.compareAndSwapObject(this, listenersOffset, current, update); if (!registered) { //so we are contending with another register thread, so lets try it again. Since the compareAndSwap //didn't succeed, we know that the current thread still has exclusive ownership on the Listeners object //so we can try to register it again, but now with the newly found listeners continue; } //the registration was a success. We need to make sure that the ___version hasn't changed. //JMM: the volatile read of ___version can't jump in front of the unsafe.compareAndSwap. if (version == this.version) { //we are lucky, the registration was done successfully and we managed to cas the listener //before the update (since the update we are interested in, hasn't happened yet). This means that //the updating thread is now responsible for notifying the listeners. Retrieval of the most recently //published listener, always happens after the version is updated return REGISTRATION_DONE; } //the version has changed, so an interesting write has happened. No registration is needed. //JMM: the unsafe.compareAndSwap can't jump over the volatile read this.___version. //the update has taken place, we need to check if our listeners still is in place. //if it is, it should be removed and the listeners notified. If the listeners already has changed, //it is the task for the other to do the listener cleanup and notify them while (true) { update = listeners; final boolean removed = ___unsafe.compareAndSwapObject(this, listenersOffset, update, null); if (!removed) { continue; } if (update != null) { //we have complete ownership of the listeners that are removed, so lets open them. update.openAll(pool); } return REGISTRATION_NOT_NEEDED; } } } @SuppressWarnings({"SimplifiableIfStatement"}) public final boolean hasReadConflict(final Tranlocal tranlocal) { if (tranlocal.lockMode != LOCKMODE_NONE) { return false; } if (hasExclusiveLock()) { return true; } return tranlocal.version != version; } protected final int arriveAndExclusiveLockOrBackoff() { final int maxRetries = stm.defaultMaxRetries; final int spinCount = stm.spinCount; for (int k = 0; k <= maxRetries; k++) { final int arriveStatus = arriveAndExclusiveLock(spinCount); if (arriveStatus != FAILURE) { return arriveStatus; } stm.defaultBackoffPolicy.delayUninterruptible(k + 1); } return FAILURE; } }