package org.multiverse.stms.gamma.integration.isolation; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.multiverse.TestThread; import org.multiverse.api.Txn; import org.multiverse.api.callables.TxnVoidCallable; import org.multiverse.api.exceptions.ReadWriteConflict; import org.multiverse.stms.gamma.*; import org.multiverse.stms.gamma.GammaTxnExecutor; import org.multiverse.stms.gamma.transactionalobjects.BaseGammaTxnRef; import org.multiverse.stms.gamma.transactionalobjects.GammaTxnRef; 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.fat.*; import org.multiverse.stms.gamma.transactions.fat.FatFixedLengthGammaTxn; import org.multiverse.stms.gamma.transactions.fat.FatVariableLengthGammaTxn; import java.util.concurrent.atomic.AtomicBoolean; import static org.junit.Assert.*; import static org.multiverse.TestUtils.*; public class Orec_Ref_ReadConsistencyStressTest implements GammaConstants { private GammaStm stm; private GammaTxnRef[] refs; private volatile boolean stop; private final AtomicBoolean inconsistencyDetected = new AtomicBoolean(); private final long durationMs = 360 * 1000; private int refCount = 256; private int writingThreadCount; private int readingThreadCount; @Before public void setUp() { stm = new GammaStm(); stop = false; inconsistencyDetected.set(false); readingThreadCount = 10; writingThreadCount = 2; refs = new GammaTxnRef[refCount]; for (int k = 0; k < refs.length; k++) { refs[k] = new GammaTxnRef(stm, 0); } } @After public void after() { for (GammaTxnRef ref : refs) { System.out.println(ref.toDebugString()); } } class UpdatingThread extends TestThread { public UpdatingThread(int id) { super("UpdatingThread-" + id); } @Override public void doRun() throws Exception { GammaTxnExecutor executor = stm.newTxnFactoryBuilder() .setSpeculative(false) .setMaxRetries(100000) // .setReadLockMode(LockMode.Exclusive) .newTxnExecutor(); final String name = getName(); TxnVoidCallable callable = new TxnVoidCallable() { @Override public void call(Txn tx) throws Exception { for (GammaTxnRef ref : refs) { ref.set(tx, name); } } }; int iteration = 0; while (!stop) { executor.execute(callable); sleepRandomUs(100); iteration++; if (iteration % 100000 == 0) { System.out.printf("%s is at %s\n", getName(), iteration); } } } } @Test public void basicTest() { BasicReadThread[] readingThreads = new BasicReadThread[readingThreadCount]; for (int k = 0; k < readingThreads.length; k++) { readingThreads[k] = new BasicReadThread(k); } UpdatingThread[] updatingThreads = new UpdatingThread[writingThreadCount]; for (int k = 0; k < updatingThreads.length; k++) { updatingThreads[k] = new UpdatingThread(k); } startAll(readingThreads); startAll(updatingThreads); sleepMs(durationMs); stop = true; sleepMs(1000); joinAll(readingThreads); joinAll(updatingThreads); assertFalse(inconsistencyDetected.get()); } class BasicReadThread extends TestThread { private Tranlocal[] tranlocals; private long lastConflictCount = stm.getGlobalConflictCounter().count(); private GammaObjectPool pool = new GammaObjectPool(); private Tranlocal firstTranlocal; private GammaTxn dummyTransaction = stm.newDefaultTxn(); public BasicReadThread(int id) { super("ReadingThread-" + id); tranlocals = new Tranlocal[refs.length]; for (int k = 0; k < tranlocals.length; k++) { Tranlocal tranlocal = new Tranlocal(); tranlocal.owner = refs[k]; tranlocals[k] = tranlocal; } } @Override public void doRun() throws Exception { long iteration = 0; while (!stop) { singleRun(); iteration++; if (iteration % 10000 == 0) { System.out.printf("%s is at %s\n", getName(), iteration); } } } private void singleRun() { assertCorrectlyCleared(); fullRead(); assertReadConsistent(); releaseChainAfterSuccess(); } private void fullRead() { for (; ;) { lastConflictCount = stm.getGlobalConflictCounter().count(); Object v = null; for (int k = 0; k < refs.length; k++) { GammaTxnRef ref = refs[k]; Tranlocal tranlocal = tranlocals[k]; if (!ref.load(dummyTransaction, tranlocal, LOCKMODE_NONE, 64, true)) { releaseChainAfterFailure(); break; } if (!isReadConsistent()) { releaseChainAfterFailure(); break; } if (k == 0) { v = tranlocal.ref_value; } else { if (v != tranlocal.ref_value) { System.out.println("Inconsistency detected"); stop = true; fail(); } } if (k == refs.length - 1) { return; } } } } private void assertReadConsistent() { firstTranlocal = tranlocals[0]; for (int k = 1; k < tranlocals.length; k++) { boolean badValue = tranlocals[k].ref_value != firstTranlocal.ref_value; boolean badVersion = tranlocals[k].version != firstTranlocal.version; if (badValue || badVersion) { if (badValue) { System.out.printf("Inconsistency detected on bad value %s %s\n", tranlocals[k].ref_value, firstTranlocal.ref_value); } else { System.out.printf("Inconsistency detected on bad version %s %s\n", tranlocals[k].version, firstTranlocal.version); } inconsistencyDetected.compareAndSet(false, true); stop = true; break; } } } private boolean isReadConsistent() { long globalConflictCount = stm.getGlobalConflictCounter().count(); if (lastConflictCount == globalConflictCount) { return true; } lastConflictCount = globalConflictCount; for (Tranlocal tranlocal : tranlocals) { BaseGammaTxnRef owner = tranlocal.owner; if (!tranlocal.hasDepartObligation) { continue; } if (owner.hasReadConflict(tranlocal)) { return false; } //if (owner.hasExclusiveLock()) { // return false; //} //if (tranlocal.version != owner.version) { // return false; //} } return true; } private void assertCorrectlyCleared() { for (Tranlocal tranlocal : tranlocals) { assertFalse(tranlocal.hasDepartObligation); assertEquals(LOCKMODE_NONE, tranlocal.lockMode); } } private void releaseChainAfterFailure() { for (Tranlocal tranlocal : tranlocals) { BaseGammaTxnRef owner = tranlocal.owner; tranlocal.owner.releaseAfterFailure(tranlocal, pool); tranlocal.owner = owner; //if (tranlocal.hasDepartObligation) { // tranlocal.hasDepartObligation = false; // if (tranlocal.lockMode == LOCKMODE_NONE) { // tranlocal.owner.departAfterFailure(); // } else { // tranlocal.lockMode = LOCKMODE_NONE; // tranlocal.owner.departAfterFailureAndUnlock(); // } //} } } private void releaseChainAfterSuccess() { for (Tranlocal tranlocal : tranlocals) { BaseGammaTxnRef owner = tranlocal.owner; owner.releaseAfterReading(tranlocal, pool); tranlocal.owner = owner; //if (tranlocal.hasDepartObligation) { // tranlocal.hasDepartObligation = false; // if (tranlocal.lockMode == LOCKMODE_NONE) { // tranlocal.owner.departAfterReading(); // } else { // tranlocal.lockMode = LOCKMODE_NONE; // tranlocal.owner.departAfterUpdateAndUnlock(); // } //} } } } @Test public void testFixedLengthTransactionUsingReadThread() { FixedLengthTransactionUsingReadThread[] readingThreads = new FixedLengthTransactionUsingReadThread[readingThreadCount]; for (int k = 0; k < readingThreads.length; k++) { readingThreads[k] = new FixedLengthTransactionUsingReadThread(k); } UpdatingThread[] updatingThreads = new UpdatingThread[writingThreadCount]; for (int k = 0; k < updatingThreads.length; k++) { updatingThreads[k] = new UpdatingThread(k); } startAll(readingThreads); startAll(updatingThreads); sleepMs(durationMs); stop = true; sleepMs(1000); for (GammaTxnRef ref : refs) { System.out.println(ref.toDebugString()); } joinAll(readingThreads); joinAll(updatingThreads); assertFalse(inconsistencyDetected.get()); } class FixedLengthTransactionUsingReadThread extends TestThread { public final FatFixedLengthGammaTxn tx = new FatFixedLengthGammaTxn( new GammaTxnConfig(stm, refs.length + 1) .setMaxRetries(10000000) .setMaximumPoorMansConflictScanLength(0) .setDirtyCheckEnabled(false) .setSpeculative(false) ); public FixedLengthTransactionUsingReadThread(int id) { super("ReadingThread-" + id); } @Override public void doRun() throws Exception { long iteration = 0; while (!stop) { singleRun(); iteration++; if (iteration % 10000 == 0) { System.out.printf("%s is at %s\n", getName(), iteration); } } } private void singleRun() { tx.hardReset(); while (true) { try { fullRead(); assertReadConsistent(tx); tx.commit(); return; } catch (ReadWriteConflict expected) { tx.hardReset(); } } } private void fullRead() { for (int k = 0; k < refs.length; k++) { GammaTxnRef ref = refs[k]; Tranlocal tranlocal = tx.head; while (tranlocal.owner != null) { tranlocal = tranlocal.next; } tx.size++; if (!tx.hasReads) { tx.localConflictCount = stm.globalConflictCounter.count(); tx.hasReads = true; } if (!ref.load(tx, tranlocal, LOCKMODE_NONE, 64, true)) { throw tx.abortOnReadWriteConflict(ref); } if (!tx.isReadConsistent(tranlocal)) { throw tx.abortOnReadWriteConflict(ref); } //ref.openForRead(tx, LOCKMODE_NONE); if (k == refs.length - 1) { return; } } } } class FixedLengthTransactionReadingThread extends TestThread { private FatFixedLengthGammaTxn tx = new FatFixedLengthGammaTxn( new GammaTxnConfig(stm, refs.length) .setMaximumPoorMansConflictScanLength(0) .setDirtyCheckEnabled(false) ); public FixedLengthTransactionReadingThread(int id) { super("ReadingThread-" + id); } @Override public void doRun() throws Exception { long iteration = 0; while (!stop) { singleRun(System.nanoTime() % 10 == 0 && false); iteration++; if (iteration % 1000 == 0) { System.out.printf("%s is at %s\n", getName(), iteration); } } } private void singleRun(boolean write) { tx.hardReset(); boolean success = false; while (!success) { fullRead(write); assertReadConsistent(tx); try { tx.commit(); success = true; } catch (ReadWriteConflict expected) { success = false; tx.attempt = 1; tx.softReset(); } } tx.commit(); } private void fullRead(boolean write) { for (; ;) { try { for (int k = 0; k < refs.length; k++) { if (write) { Tranlocal tranlocal = refs[k].openForWrite(tx, LOCKMODE_NONE); tranlocal.ref_value = getName(); } else { refs[k].openForRead(tx, LOCKMODE_NONE); } if (k == refs.length - 1) { return; } } } catch (ReadWriteConflict expected) { tx.attempt = 1; tx.softReset(); } } } } class VariableLengthReadingThread extends TestThread { private FatVariableLengthGammaTxn tx = new FatVariableLengthGammaTxn( new GammaTxnConfig(stm, refs.length) .setMaximumPoorMansConflictScanLength(0) .setDirtyCheckEnabled(false) ); public VariableLengthReadingThread(int id) { super("ReadingThread-" + id); } @Override public void doRun() throws Exception { long iteration = 0; while (!stop) { singleRun(System.nanoTime() % 10 == 0 && false); iteration++; if (iteration % 1000 == 0) { System.out.printf("%s is at %s\n", getName(), iteration); } } } private void singleRun(boolean write) { tx.hardReset(); boolean success = false; while (!success) { fullRead(write); assertReadConsistent(tx); try { tx.commit(); success = true; } catch (ReadWriteConflict expected) { success = false; tx.attempt = 1; tx.softReset(); } } tx.commit(); } private void fullRead(boolean write) { for (; ;) { try { for (int k = 0; k < refs.length; k++) { if (write) { Tranlocal tranlocal = refs[k].openForWrite(tx, LOCKMODE_NONE); tranlocal.ref_value = getName(); } else { refs[k].openForRead(tx, LOCKMODE_NONE); } if (k == refs.length - 1) { return; } } } catch (ReadWriteConflict expected) { tx.attempt = 1; tx.softReset(); } } } } class VariableReadingWithBlockThread extends TestThread { private LeanGammaTxnExecutor executor; public VariableReadingWithBlockThread(int id) { super("VariableReadingWithBlockThread-" + id); } @Override public void doRun() throws Exception { GammaTxnConfig config = new GammaTxnConfig(stm, refs.length) .setMaximumPoorMansConflictScanLength(0) .setMaxRetries(100000) .setSpeculative(false) .setDirtyCheckEnabled(false); executor = new LeanGammaTxnExecutor(new FatVariableLengthGammaTxnFactory(config)); long iteration = 0; while (!stop) { singleRun(); iteration++; if (iteration % 10000 == 0) { System.out.printf("%s is at %s\n", getName(), iteration); } } } private void singleRun() { executor.execute(new TxnVoidCallable() { @Override public void call(Txn tx) throws Exception { fullRead((GammaTxn) tx); assertReadConsistent((GammaTxn) tx); } }); } private void fullRead(GammaTxn tx) { Object value = refs[0].get(tx); for (int k = 0; k < refs.length; k++) { assertSame(value, refs[k].openForRead(tx, LOCKMODE_NONE).ref_value); } } } class FixedReadingWithBlockThread extends TestThread { private LeanGammaTxnExecutor executor; private GammaTxnConfig config; public FixedReadingWithBlockThread(int id) { super("VariableReadingWithBlockThread-" + id); } @Override public void doRun() throws Exception { config = new GammaTxnConfig(stm, refs.length) .setMaximumPoorMansConflictScanLength(0) .setDirtyCheckEnabled(false); executor = new LeanGammaTxnExecutor(new FatFixedLengthGammaTxnFactory(config)); long iteration = 0; while (!stop) { singleRun(); iteration++; if (iteration % 10000 == 0) { System.out.printf("%s is at %s\n", getName(), iteration); } } } private void singleRun() { TxnVoidCallable callable = new TxnVoidCallable() { @Override public void call(Txn tx) throws Exception { fullRead((GammaTxn) tx); assertReadConsistent((GammaTxn) tx); } }; FatFixedLengthGammaTxn tx = new FatFixedLengthGammaTxn(config); while (true) { try { callable.call(tx); tx.commit(); return; } catch (ReadWriteConflict expected) { tx.hardReset(); } catch (Exception ex) { throw new RuntimeException(ex); } } //executor.atomicChecked(callable); } private void fullRead(GammaTxn tx) { for (int k = 0; k < refs.length; k++) { refs[k].openForRead(tx, LOCKMODE_NONE); } } } private void assertReadConsistent(GammaTxn tx) { long version = tx.getRefTranlocal(refs[0]).version; Object value = tx.getRefTranlocal(refs[0]).ref_value; for (int k = 1; k < refs.length; k++) { boolean badVersion = version != tx.getRefTranlocal(refs[k]).version; boolean badValue = value != tx.getRefTranlocal(refs[k]).ref_value; if (badValue || badVersion) { System.out.printf("Inconsistency detected badValue=%s badVersion=%s\n", badValue, badVersion); inconsistencyDetected.compareAndSet(false, true); stop = true; break; } } } }