package org.multiverse.stms.gamma.transactionalobjects; import org.multiverse.api.TxnLock; import org.multiverse.api.LockMode; import org.multiverse.api.Txn; import org.multiverse.api.exceptions.PanicError; import org.multiverse.api.exceptions.TxnMandatoryException; import org.multiverse.stms.gamma.GammaStm; import org.multiverse.stms.gamma.Listeners; import org.multiverse.stms.gamma.transactions.GammaTxn; import org.multiverse.utils.ToolUnsafe; import sun.misc.Unsafe; import static java.lang.String.format; import static org.multiverse.api.TxnThreadLocal.getThreadLocalTxn; @SuppressWarnings({"OverlyComplexClass"}) public abstract class AbstractGammaObject implements GammaObject, TxnLock { public static final long MASK_OREC_EXCLUSIVELOCK = 0x8000000000000000L; public static final long MASK_OREC_UPDATELOCK = 0x4000000000000000L; public static final long MASK_OREC_READBIASED = 0x2000000000000000L; public static final long MASK_OREC_READLOCKS = 0x1FFFFF0000000000L; public static final long MASK_OREC_SURPLUS = 0x000000FFFFFFFE00L; public static final long MASK_OREC_READONLY_COUNT = 0x00000000000003FFL; protected static final Unsafe ___unsafe = ToolUnsafe.getUnsafe(); protected static final long listenersOffset; protected static final long valueOffset; static { try { listenersOffset = ___unsafe.objectFieldOffset( AbstractGammaObject.class.getDeclaredField("listeners")); valueOffset = ___unsafe.objectFieldOffset( AbstractGammaObject.class.getDeclaredField("orec")); } catch (Exception ex) { throw new Error(ex); } } public final GammaStm stm; @SuppressWarnings({"UnusedDeclaration"}) public volatile Listeners listeners; @SuppressWarnings({"VolatileLongOrDoubleField"}) public volatile long version; @SuppressWarnings({"VolatileLongOrDoubleField"}) public volatile long orec; //This field has a controlled JMM problem (just like the hashcode of String). protected int identityHashCode; //it is important that the maximum threshold is not larger than 1023 (there are 10 bits for the readonly count) private final int readBiasedThreshold; public AbstractGammaObject(GammaStm stm) { assert stm != null; this.stm = stm; this.readBiasedThreshold = stm.readBiasedThreshold; } @Override public final long getVersion() { return version; } @Override public final GammaStm getStm() { return stm; } @Override public final TxnLock getLock() { return this; } public final Listeners ___removeListenersAfterWrite() { if (listeners == null) { return null; } Listeners removedListeners; while (true) { removedListeners = listeners; if (___unsafe.compareAndSwapObject(this, listenersOffset, removedListeners, null)) { return removedListeners; } } } //a controlled jmm problem here since identityHashCode is not synchronized/volatile/final. //this is the same as with the hashcode and String. @Override public final int identityHashCode() { int tmp = identityHashCode; if (tmp != 0) { return tmp; } tmp = System.identityHashCode(this); identityHashCode = tmp; return tmp; } public final int atomicGetLockModeAsInt() { final long current = orec; if (hasExclusiveLock(current)) { return LOCKMODE_EXCLUSIVE; } if (hasWriteLock(current)) { return LOCKMODE_WRITE; } if (getReadLockCount(current) > 0) { return LOCKMODE_READ; } return LOCKMODE_NONE; } @Override public final LockMode atomicGetLockMode() { switch (atomicGetLockModeAsInt()) { case LOCKMODE_NONE: return LockMode.None; case LOCKMODE_READ: return LockMode.Read; case LOCKMODE_WRITE: return LockMode.Write; case LOCKMODE_EXCLUSIVE: return LockMode.Exclusive; default: throw new IllegalStateException(); } } @Override public final LockMode getLockMode() { final GammaTxn tx = (GammaTxn) getThreadLocalTxn(); if (tx == null) { throw new TxnMandatoryException(); } return getLockMode(tx); } @Override public final LockMode getLockMode(final Txn tx) { return getLockMode((GammaTxn) tx); } public final LockMode getLockMode(final GammaTxn tx) { final Tranlocal tranlocal = tx.locate((BaseGammaTxnRef) this); if (tranlocal == null) { return LockMode.None; } switch (tranlocal.getLockMode()) { case LOCKMODE_NONE: return LockMode.None; case LOCKMODE_READ: return LockMode.Read; case LOCKMODE_WRITE: return LockMode.Write; case LOCKMODE_EXCLUSIVE: return LockMode.Exclusive; default: throw new IllegalStateException(); } } private static void yieldIfNeeded(final int remainingSpins) { if (remainingSpins % SPIN_YIELD == 0 && remainingSpins > 0) { //noinspection CallToThreadYield Thread.yield(); } } public final boolean waitForExclusiveLockToBecomeFree(int spinCount) { do { if (!hasExclusiveLock(orec)) { return true; } spinCount--; } while (spinCount >= 0); return false; } public final boolean hasWriteLock() { return hasWriteLock(orec); } public final boolean hasExclusiveLock() { return hasExclusiveLock(orec); } public final int getReadBiasedThreshold() { return readBiasedThreshold; } public final long getSurplus() { return getSurplus(orec); } public final boolean isReadBiased() { return isReadBiased(orec); } public final int getReadonlyCount() { return getReadonlyCount(orec); } public final int getReadLockCount() { return getReadLockCount(orec); } /** * Arrives. The Arrive is needed for the fast conflict detection (rich mans conflict). * * @param spinCount the maximum number of times to spin if the exclusive lock is acquired. * @return the arrive status. */ public final int arrive(int spinCount) { do { final long current = orec; if (hasExclusiveLock(current)) { spinCount--; yieldIfNeeded(spinCount); continue; } long surplus = getSurplus(current); final boolean isReadBiased = isReadBiased(current); if (isReadBiased) { if (surplus == 0) { surplus = 1; } else if (surplus == 1) { return MASK_SUCCESS + MASK_UNREGISTERED; } else { throw new PanicError("Surplus for a readbiased orec can never be larger than 1"); } } else { surplus++; } final long next = setSurplus(current, surplus); if (___unsafe.compareAndSwapLong(this, valueOffset, current, next)) { int result = MASK_SUCCESS; if (isReadBiased) { result += MASK_UNREGISTERED; } return result; } } while (spinCount >= 0); return FAILURE; } public final int upgradeReadLock(int spinCount, final boolean exclusiveLock) { do { final long current = orec; int readLockCount = getReadLockCount(current); if (readLockCount == 0) { throw new PanicError(format("Can't update from readlock to %s if no readlocks are acquired", exclusiveLock ? "exclusiveLock" : "writeLock")); } if (readLockCount > 1) { spinCount--; yieldIfNeeded(spinCount); continue; } long next = setReadLockCount(current, 0); if (exclusiveLock) { next = setExclusiveLock(next, true); } else { next = setWriteLock(next, true); } if (___unsafe.compareAndSwapLong(this, valueOffset, current, next)) { int result = MASK_SUCCESS; if (exclusiveLock) { if (isReadBiased(current) || getSurplus(current) > 1) { result += MASK_CONFLICT; } } return result; } } while (spinCount >= 0); return FAILURE; } /** * Upgrades the writeLock to an exclusive lock. * * @return true if there was at least one conflict write. */ public final boolean upgradeWriteLock() { while (true) { final long current = orec; if (hasExclusiveLock(current)) { return false; } if (!hasWriteLock(current)) { throw new PanicError("WriteLock is not acquired"); } long next = setExclusiveLock(current, true); next = setWriteLock(next, false); if (___unsafe.compareAndSwapLong(this, valueOffset, current, next)) { return isReadBiased(current) || getSurplus(current) > 1; } } } /** * Arrives and tries to acquire the lock. If one of them fails, there will not be any state change. * * @param spinCount the maximum number of times to spin to wait for the lock to come available. * @param lockMode the desired lockmode. It isn't allowed to be LOCKMODE_NONE. * @return the result of this operation. */ public final int arriveAndLock(int spinCount, final int lockMode) { assert lockMode != LOCKMODE_NONE; do { final long current = orec; boolean locked = lockMode == LOCKMODE_READ ? hasWriteOrExclusiveLock(current) : hasAnyLock(current); if (locked) { spinCount--; yieldIfNeeded(spinCount); continue; } long currentSurplus = getSurplus(current); long surplus = currentSurplus; boolean isReadBiased = isReadBiased(current); if (isReadBiased) { if (surplus == 0) { surplus = 1; } else if (surplus > 1) { throw new PanicError("Surplus is larger than 1 and orec is readbiased: " + toOrecString(current)); } } else { surplus++; } long next = setSurplus(current, surplus); if (lockMode == LOCKMODE_EXCLUSIVE) { next = setExclusiveLock(next, true); } else if (lockMode == LOCKMODE_READ) { next = setReadLockCount(next, getReadLockCount(current) + 1); } else if (lockMode == LOCKMODE_WRITE) { next = setWriteLock(next, true); } if (___unsafe.compareAndSwapLong(this, valueOffset, current, next)) { int result = MASK_SUCCESS; if (isReadBiased) { result += MASK_UNREGISTERED; } if (lockMode == LOCKMODE_EXCLUSIVE && currentSurplus > 0) { result += MASK_CONFLICT; } return result; } } while (spinCount >= 0); return FAILURE; } /** * Tries to acquire the exclusive lock and arrive. * * @param spinCount the maximum number of spins when it is locked. * @return the arrive-status. */ public final int arriveAndExclusiveLock(int spinCount) { do { final long current = orec; if (hasAnyLock(current)) { spinCount--; yieldIfNeeded(spinCount); continue; } final long currentSurplus = getSurplus(current); long surplus = currentSurplus; boolean isReadBiased = isReadBiased(current); if (isReadBiased) { if (surplus == 0) { surplus = 1; } else if (surplus > 1) { throw new PanicError("Surplus is larger than 2: " + toOrecString(current)); } } else { surplus++; } long next = setSurplus(current, surplus); next = setExclusiveLock(next, true); if (___unsafe.compareAndSwapLong(this, valueOffset, current, next)) { int result = MASK_SUCCESS; if (isReadBiased) { result += MASK_UNREGISTERED; } if (currentSurplus > 0) { result += MASK_CONFLICT; } return result; } } while (spinCount >= 0); return FAILURE; } /** * Arrives and tries to acquire the lock. If one of them fails, there will not be any state change. * * @param spinCount the maximum number of times to spin if a lock is acquired. * @param lockMode the desired lockMode. This is not allowed to be LOCKMODE_NONE. * @return the status of the operation. */ public final int lockAfterArrive(int spinCount, final int lockMode) { assert lockMode != LOCKMODE_NONE; do { final long current = orec; if (isReadBiased(current)) { throw new PanicError("Orec is readbiased " + toOrecString(current)); } boolean locked = lockMode == LOCKMODE_READ ? hasWriteOrExclusiveLock(current) : hasAnyLock(current); if (locked) { spinCount--; yieldIfNeeded(spinCount); continue; } final long currentSurplus = getSurplus(current); if (currentSurplus == 0) { throw new PanicError("There is no surplus (so if it didn't do a read before)" + toOrecString(current)); } long next = current; if (lockMode == LOCKMODE_READ) { next = setReadLockCount(next, getReadLockCount(current) + 1); } else if (lockMode == LOCKMODE_EXCLUSIVE) { next = setExclusiveLock(next, true); } else { next = setWriteLock(current, true); } if (___unsafe.compareAndSwapLong(this, valueOffset, current, next)) { int result = MASK_SUCCESS; if (lockMode == LOCKMODE_EXCLUSIVE && currentSurplus > 1) { result += MASK_CONFLICT; } return result; } } while (spinCount >= 0); return FAILURE; } /** * Departs after a successful read is done and no lock was acquired. * <p/> * This call increased the readonly count. If the readonly count threshold is reached, the orec is * made readbiased and the readonly count is set to 0. */ public final void departAfterReading() { while (true) { final long current = orec; long surplus = getSurplus(current); if (surplus == 0) { throw new PanicError("There is no surplus " + toOrecString(current)); } boolean isReadBiased = isReadBiased(current); if (isReadBiased) { throw new PanicError("Orec is readbiased " + toOrecString(current)); } int readonlyCount = getReadonlyCount(current); if (readonlyCount < readBiasedThreshold) { readonlyCount++; } if (surplus <= 1 && hasAnyLock(current)) { throw new PanicError("There is not enough surplus " + toOrecString(current)); } surplus--; final boolean hasExclusiveLock = hasExclusiveLock(current); if (!hasExclusiveLock && surplus == 0 && readonlyCount == readBiasedThreshold) { isReadBiased = true; readonlyCount = 0; } long next = setIsReadBiased(current, isReadBiased); next = setReadonlyCount(next, readonlyCount); next = setSurplus(next, surplus); if (___unsafe.compareAndSwapLong(this, valueOffset, current, next)) { return; } } } /** * Departs after a successful read is done and release the lock (it doesn't matter which lock is acquired as long is * it is a read/write/exclusive lock. * <p/> * This method increases the readonly count of the orec and upgraded from update-biased to * readbiased if the READBIASED_THRESHOLD is reached (also the readonly count is set to zero * if that happens). */ public final void departAfterReadingAndUnlock() { while (true) { final long current = orec; long surplus = getSurplus(current); if (surplus == 0) { throw new PanicError("There is no surplus: " + toOrecString(current)); } int readLockCount = getReadLockCount(current); if (readLockCount == 0 && !hasWriteOrExclusiveLock(current)) { throw new PanicError("No TxnLock acquired " + toOrecString(current)); } boolean isReadBiased = isReadBiased(current); if (isReadBiased) { throw new PanicError("Orec is readbiased " + toOrecString(current)); } int readonlyCount = getReadonlyCount(current); surplus--; if (readonlyCount < readBiasedThreshold) { readonlyCount++; } if (surplus == 0 && readonlyCount == readBiasedThreshold) { isReadBiased = true; readonlyCount = 0; } long next = current; if (readLockCount > 0) { next = setReadLockCount(next, readLockCount - 1); } else { next = setExclusiveLock(next, false); next = setWriteLock(next, false); } next = setIsReadBiased(next, isReadBiased); next = setReadonlyCount(next, readonlyCount); next = setSurplus(next, surplus); if (___unsafe.compareAndSwapLong(this, valueOffset, current, next)) { return; } } } public final void departAfterUpdateAndUnlock() { while (true) { final long current = orec; if (!hasExclusiveLock(current)) { throw new PanicError( "Can't departAfterUpdateAndUnlock if the commit lock is not acquired " + toOrecString(current)); } long surplus = getSurplus(current); if (surplus == 0) { throw new PanicError( "Can't departAfterUpdateAndUnlock is there is no surplus " + toOrecString(current)); } if (isReadBiased(current)) { if (surplus > 1) { throw new PanicError( "The surplus can never be larger than 1 if readBiased " + toOrecString(current)); } //there always is a conflict when a readbiased orec is updated. surplus = 0; } else { surplus--; } if (surplus == 0) { orec = 0; return; } final long next = setSurplus(0, surplus); if (___unsafe.compareAndSwapLong(this, valueOffset, current, next)) { return; } } } /** * Departs after a transaction fails and has an arrive on this Orec. It doesn't matter what the lock level * is, as long as it is higher than LOCKMODE_NONE. This call can safely be made on a read or update biased * ref. */ public final void departAfterFailureAndUnlock() { while (true) { final long current = orec; //-1 indicates write or commit lock, value bigger than 0 indicates readlock int lockMode; if (hasWriteOrExclusiveLock(current)) { lockMode = -1; } else { lockMode = getReadLockCount(current); } if (lockMode == 0) { throw new PanicError( "No lock was not acquired " + toOrecString(current)); } long surplus = getSurplus(current); if (surplus == 0) { throw new PanicError( "There is no surplus " + toOrecString(current)); } //we can only decrease the surplus if it is not read biased. Because with a read biased //orec, we have no idea how many readers there are. if (!isReadBiased(current)) { surplus--; } long next = setSurplus(current, surplus); if (lockMode == -1) { next = setExclusiveLock(next, false); next = setWriteLock(next, false); } else { next = setReadLockCount(next, lockMode - 1); } if (___unsafe.compareAndSwapLong(this, valueOffset, current, next)) { return; } } } /** * Departs after failure. */ public final void departAfterFailure() { while (true) { final long current = orec; if (isReadBiased(current)) { throw new PanicError("Orec is readbiased:" + toOrecString(current)); } long surplus = getSurplus(current); if (hasExclusiveLock(current)) { if (surplus < 2) { throw new PanicError( "there must be at least 2 readers, the thread that acquired the lock, " + "and the calling thread " + toOrecString(current)); } } else if (surplus == 0) { throw new PanicError("There is no surplus " + toOrecString(current)); } surplus--; long next = setSurplus(current, surplus); if (___unsafe.compareAndSwapLong(this, valueOffset, current, next)) { return; } } } public final void unlockByUnregistered() { while (true) { final long current = orec; //-1 indicates write or commit lock, value bigger than 0 indicates readlock if (!isReadBiased(current)) { throw new PanicError( "Can't ___unlockByReadBiased when it is not readbiased " + toOrecString(current)); } int lockMode; if (hasWriteOrExclusiveLock(current)) { lockMode = -1; } else { lockMode = getReadLockCount(current); } if (lockMode == 0) { throw new PanicError("No TxnLock " + toOrecString(current)); } if (getSurplus(current) > 1) { throw new PanicError("Surplus for readbiased orec larger than 1 " + toOrecString(current)); } long next = current; if (lockMode > 0) { next = setReadLockCount(next, lockMode - 1); } else { next = setExclusiveLock(next, false); next = setWriteLock(next, false); } if (___unsafe.compareAndSwapLong(this, valueOffset, current, next)) { return; } } } public final String ___toOrecString() { return toOrecString(orec); } public static long setReadLockCount(final long value, final long readLockCount) { return (value & ~MASK_OREC_READLOCKS) | (readLockCount << 40); } public static int getReadLockCount(final long value) { return (int) ((value & MASK_OREC_READLOCKS) >> 40); } public static long setExclusiveLock(final long value, final boolean exclusiveLock) { return (value & ~MASK_OREC_EXCLUSIVELOCK) | ((exclusiveLock ? 1L : 0L) << 63); } public static boolean hasWriteOrExclusiveLock(final long value) { return ((value & (MASK_OREC_EXCLUSIVELOCK + MASK_OREC_UPDATELOCK)) != 0); } public static boolean hasAnyLock(final long value) { return ((value & (MASK_OREC_EXCLUSIVELOCK + MASK_OREC_UPDATELOCK + MASK_OREC_READLOCKS)) != 0); } public static boolean hasExclusiveLock(final long value) { return (value & MASK_OREC_EXCLUSIVELOCK) != 0; } public static boolean isReadBiased(final long value) { return (value & MASK_OREC_READBIASED) != 0; } public static long setIsReadBiased(final long value, final boolean isReadBiased) { return (value & ~MASK_OREC_READBIASED) | ((isReadBiased ? 1L : 0L) << 61); } public static boolean hasWriteLock(final long value) { return (value & MASK_OREC_UPDATELOCK) != 0; } public static long setWriteLock(final long value, final boolean updateLock) { return (value & ~MASK_OREC_UPDATELOCK) | ((updateLock ? 1L : 0L) << 62); } public static int getReadonlyCount(final long value) { return (int) (value & MASK_OREC_READONLY_COUNT); } public static long setReadonlyCount(final long value, final int readonlyCount) { return (value & ~MASK_OREC_READONLY_COUNT) | readonlyCount; } public static long setSurplus(final long value, final long surplus) { return (value & ~MASK_OREC_SURPLUS) | (surplus << 10); } public static long getSurplus(final long value) { return (value & MASK_OREC_SURPLUS) >> 10; } private static String toOrecString(final long value) { return format( "Orec(hasExclusiveLock=%s, hasWriteLock=%s, readLocks=%s, surplus=%s, isReadBiased=%s, readonlyCount=%s)", hasExclusiveLock(value), hasWriteLock(value), getReadLockCount(value), getSurplus(value), isReadBiased(value), getReadonlyCount(value)); } }