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.GammaTxnLong; 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.FatFixedLengthGammaTxn; import org.multiverse.stms.gamma.transactions.fat.FatVariableLengthGammaTxn; import org.multiverse.stms.gamma.transactions.fat.FatVariableLengthGammaTxnFactory; import java.util.concurrent.atomic.AtomicBoolean; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.multiverse.TestUtils.*; /** * Conclusion so far: * with refs it fails * with longs it succeeds. */ public class Orec_LongRef_ReadConsistencyStressTest implements GammaConstants { private GammaStm stm; private GammaTxnLong[] refs; private volatile boolean stop; private final AtomicBoolean inconstencyDetected = new AtomicBoolean(); private int readingThreadCount; private int writingThreadCount; private int refCount; private int durationMs; @Before public void setUp() { stm = new GammaStm(); stop = false; refCount = 128; readingThreadCount = 10; writingThreadCount = 2; durationMs = 300 * 1000; refs = new GammaTxnLong[refCount]; for (int k = 0; k < refs.length; k++) { refs[k] = new GammaTxnLong(stm, 0); } inconstencyDetected.set(false); } @After public void after() { for (GammaTxnLong ref : refs) { System.out.println(ref.toDebugString()); } } @Test public void test() { FatVariableLengthTransactionWithBlockThread[] readingThreads = new FatVariableLengthTransactionWithBlockThread[readingThreadCount]; for (int k = 0; k < readingThreads.length; k++) { readingThreads[k] = new FatVariableLengthTransactionWithBlockThread(k); } UpdatingThread[] threads = new UpdatingThread[writingThreadCount]; for (int k = 0; k < threads.length; k++) { threads[k] = new UpdatingThread(k); } startAll(readingThreads); startAll(threads); sleepMs(durationMs); stop = true; joinAll(readingThreads); joinAll(threads); assertFalse(inconstencyDetected.get()); } class UpdatingThread extends TestThread { private int id; public UpdatingThread(int id) { super("UpdatingThread-" + id); this.id = id; } @Override public void doRun() throws Exception { GammaTxnExecutor executor = stm.newTxnFactoryBuilder() .setSpeculative(false) .setMaxRetries(100000) .newTxnExecutor(); TxnVoidCallable callable = new TxnVoidCallable() { @Override public void call(Txn tx) throws Exception { for (GammaTxnLong ref : refs) { ref.incrementAndGet(1); } } }; 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); } } } } class ReadingThread extends TestThread { private Tranlocal[] tranlocals; private long lastConflictCount = stm.getGlobalConflictCounter().count(); private GammaObjectPool pool = new GammaObjectPool(); private GammaTxn dummyTx = stm.newDefaultTxn(); public ReadingThread(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(System.nanoTime() % 10 == 0 && false); iteration++; if (iteration % 10000 == 0) { System.out.printf("%s is at %s\n", getName(), iteration); } } } private void singleRun(boolean write) { boolean success = false; while (!success) { assertCorrectlyCleared(); fullRead(); assertReadConsistent(); success = !write || fullWrite(); } releaseChainAfterSuccess(write); } private boolean fullWrite() { for (int k = 0; k < refs.length; k++) { GammaTxnLong ref = refs[k]; Tranlocal tranlocal = tranlocals[k]; //if (!ref.tryLockAfterNormalArrive(64, LOCKMODE_EXCLUSIVE)) { // releaseChainAfterFailure(); // return false; //} tranlocal.lockMode = LOCKMODE_EXCLUSIVE; if (tranlocal.version != ref.version) { releaseChainAfterFailure(); return false; } } for (int k = 0; k < refs.length; k++) { refs[k].version++; refs[k].departAfterUpdateAndUnlock(); tranlocals[k].lockMode = LOCKMODE_NONE; tranlocals[k].hasDepartObligation = false; } return true; } private void fullRead() { for (; ;) { lastConflictCount = stm.getGlobalConflictCounter().count(); for (int k = 0; k < refs.length; k++) { GammaTxnLong ref = refs[k]; Tranlocal tranlocal = tranlocals[k]; if (!ref.load(dummyTx,tranlocal, LOCKMODE_NONE, 64, true)) { releaseChainAfterFailure(); break; } if (!isReadConsistent()) { releaseChainAfterFailure(); break; } if (k == refs.length - 1) { return; } } } } private void assertReadConsistent() { long version = tranlocals[0].version; for (int k = 1; k < tranlocals.length; k++) { if (version != tranlocals[k].version) { System.out.println("Inconsistency detected"); inconstencyDetected.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.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(boolean write) { for (Tranlocal tranlocal : tranlocals) { BaseGammaTxnRef owner = tranlocal.owner; if (write) { owner.releaseAfterUpdate(tranlocal, pool); } else { owner.releaseAfterReading(tranlocal, pool); } tranlocal.owner = owner; } } } class FixedReadingThread extends TestThread { private FatFixedLengthGammaTxn tx = new FatFixedLengthGammaTxn( new GammaTxnConfig(stm, refs.length) .setMaximumPoorMansConflictScanLength(0) .setDirtyCheckEnabled(false) ); public FixedReadingThread(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) { refs[k].openForWrite(tx, LOCKMODE_NONE); } else { Tranlocal tranlocal = refs[k].openForRead(tx, LOCKMODE_NONE); tranlocal.long_value = tranlocal.version + 1; } if (k == refs.length - 1) { return; } } } catch (ReadWriteConflict expected) { tx.attempt = 1; tx.softReset(); } } } } class VariableReadingThread extends TestThread { private FatVariableLengthGammaTxn tx = new FatVariableLengthGammaTxn( new GammaTxnConfig(stm, refs.length) .setMaximumPoorMansConflictScanLength(0) .setDirtyCheckEnabled(false) ); public VariableReadingThread(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 FatVariableLengthTransactionWithBlockThread extends TestThread { private LeanGammaTxnExecutor executor; public FatVariableLengthTransactionWithBlockThread(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 % 1000 == 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) { long value = refs[0].get(tx); for (int k = 0; k < refs.length; k++) { assertEquals(value, refs[k].openForWrite(tx, LOCKMODE_NONE).long_value); } } } private void assertReadConsistent(GammaTxn tx) { long version = tx.getRefTranlocal(refs[0]).version; long value = refs[0].get(tx); for (int k = 1; k < refs.length; k++) { boolean b = version == tx.getRefTranlocal(refs[k]).version && value == refs[k].get(tx); if (!b) { System.out.println("Inconsistency detected"); inconstencyDetected.compareAndSet(false, true); stop = true; break; } } } }