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;
}
}
}
}