package org.multiverse.stms.gamma.integration.isolation.writeskew;
import org.junit.Before;
import org.junit.Test;
import org.multiverse.TestThread;
import org.multiverse.api.TxnExecutor;
import org.multiverse.api.IsolationLevel;
import org.multiverse.api.LockMode;
import org.multiverse.api.Txn;
import org.multiverse.api.callables.TxnVoidCallable;
import org.multiverse.stms.gamma.GammaStm;
import org.multiverse.stms.gamma.transactionalobjects.GammaTxnLong;
import org.multiverse.stms.gamma.transactions.GammaTxn;
import java.util.concurrent.atomic.AtomicBoolean;
import static java.lang.String.format;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.multiverse.TestUtils.*;
import static org.multiverse.api.GlobalStmInstance.getGlobalStmInstance;
import static org.multiverse.api.TxnThreadLocal.clearThreadLocalTxn;
/**
* A Test that checks if the writeskew problem is happening. When
* pessimisticRead/LockLevel.Read/writeskew=false is used, no writeskew is possible. Otherwise
* it can happen.
*
* @author Peter Veentjer.
*/
public class WriteSkewStressTest {
private volatile boolean stop;
private Customer customer1;
private Customer customer2;
enum Mode {
snapshot,
pessimisticReadLevel,
pessimisticWriteLevel,
pessimisticReads,
pessimisticWrites,
serialized
}
private Mode mode;
private TransferThread[] threads;
private AtomicBoolean writeSkewEncountered = new AtomicBoolean();
private GammaStm stm;
private int threadCount = 8;
@Before
public void setUp() {
stm = (GammaStm) getGlobalStmInstance();
clearThreadLocalTxn();
customer1 = new Customer();
customer2 = new Customer();
stop = false;
writeSkewEncountered.set(false);
threads = new TransferThread[threadCount];
for (int k = 0; k < threads.length; k++) {
threads[k] = new TransferThread(k);
}
GammaTxnLong account = customer1.getRandomAccount();
GammaTxn tx = stm.newDefaultTxn();
account.openForWrite(tx, LOCKMODE_NONE).long_value = 1000;
tx.commit();
}
@Test
public void whenPessimisticRead_thenNoWriteSkewPossible() {
mode = Mode.pessimisticReads;
startAll(threads);
sleepMs(getStressTestDurationMs(30 * 1000));
stop = true;
joinAll(threads);
System.out.println("User1: " + customer1);
System.out.println("User2: " + customer2);
assertFalse("writeskew detected", writeSkewEncountered.get());
}
/**
* If this test fails, the anomaly we are looking for, hasn't occurred yet. Try increasing the
* running time.
*/
@Test
public void whenPessimisticWrite_thenWriteSkewPossible() {
mode = Mode.pessimisticWrites;
startAll(threads);
sleepMs(getStressTestDurationMs(30 * 1000));
stop = true;
joinAll(threads);
System.out.println("User1: " + customer1);
System.out.println("User2: " + customer2);
assertTrue("no writeskew detected", writeSkewEncountered.get());
}
@Test
public void whenPessimisticWriteSanityTest() {
Customer customer1 = new Customer();
Customer customer2 = new Customer();
GammaTxn tx1 = stm.newDefaultTxn();
GammaTxn tx2 = stm.newDefaultTxn();
customer1.account1.get(tx1);
customer1.account2.get(tx1);
customer1.account2.get(tx1);
customer2.account2.get(tx1);
customer1.account1.get(tx2);
customer1.account2.get(tx2);
customer2.account1.get(tx2);
customer2.account2.get(tx2);
customer1.account1.openForWrite(tx1, LOCKMODE_READ).long_value++;
customer2.account1.openForWrite(tx1, LOCKMODE_READ).long_value++;
customer1.account2.openForWrite(tx2, LOCKMODE_READ).long_value++;
customer2.account2.openForWrite(tx2, LOCKMODE_READ).long_value++;
tx1.commit();
tx2.commit();
}
@Test
public void whenPessimisticWriteLevel_thenWriteSkewPossible() {
mode = Mode.pessimisticWriteLevel;
startAll(threads);
sleepMs(getStressTestDurationMs(30 * 1000));
stop = true;
joinAll(threads);
System.out.println("User1: " + customer1);
System.out.println("User2: " + customer2);
assertTrue("no writeskew detected", writeSkewEncountered.get());
}
@Test
public void whenPessimisticWriteLevelSanityTest() {
Customer customer1 = new Customer();
Customer customer2 = new Customer();
GammaTxn tx1 = stm.newTxnFactoryBuilder()
.setSpeculative(false)
.setWriteLockMode(LockMode.Read)
.newTransactionFactory()
.newTxn();
GammaTxn tx2 = stm.newTxnFactoryBuilder()
.setSpeculative(false)
.setWriteLockMode(LockMode.Read)
.newTransactionFactory()
.newTxn();
customer1.account1.get(tx1);
customer1.account2.get(tx1);
customer1.account2.get(tx1);
customer2.account2.get(tx1);
customer1.account1.get(tx2);
customer1.account2.get(tx2);
customer2.account1.get(tx2);
customer2.account2.get(tx2);
customer1.account1.openForWrite(tx1, LOCKMODE_NONE).long_value++;
customer2.account1.openForWrite(tx1, LOCKMODE_NONE).long_value++;
customer1.account2.openForWrite(tx2, LOCKMODE_NONE).long_value++;
customer2.account2.openForWrite(tx2, LOCKMODE_NONE).long_value++;
tx1.commit();
tx2.commit();
}
@Test
public void whenPessimisticReadLevel_thenNoWriteSkewPossible() {
mode = Mode.pessimisticReadLevel;
startAll(threads);
sleepMs(getStressTestDurationMs(30 * 1000));
stop = true;
joinAll(threads);
System.out.println("User1: " + customer1);
System.out.println("User2: " + customer2);
assertFalse("writeskew detected", writeSkewEncountered.get());
}
@Test
public void whenSnapshotIsolation_thenWriteSkewPossible() {
mode = Mode.snapshot;
startAll(threads);
sleepMs(getStressTestDurationMs(30 * 1000));
stop = true;
joinAll(threads);
System.out.println("User1: " + customer1);
System.out.println("User2: " + customer2);
System.out.println("User1.account1: " + customer1.account1.toDebugString());
System.out.println("User1.account2: " + customer1.account2.toDebugString());
System.out.println("User2.account1: " + customer2.account1.toDebugString());
System.out.println("User2.account2: " + customer2.account2.toDebugString());
assertTrue(writeSkewEncountered.get());
}
@Test
public void whenSerializedIsolationLevel_thenWriteSkewNotPossible() {
mode = Mode.serialized;
startAll(threads);
sleepMs(getStressTestDurationMs(30 * 1000));
stop = true;
joinAll(threads);
System.out.println("User1: " + customer1);
System.out.println("User2: " + customer2);
assertFalse("writeskew detected", writeSkewEncountered.get());
}
public class TransferThread extends TestThread {
private final TxnExecutor snapshotBlock = stm.newTxnFactoryBuilder()
.setSpeculative(false)
.setMaxRetries(10000)
.newTxnExecutor();
private final TxnExecutor serializedBlock = stm.newTxnFactoryBuilder()
.setSpeculative(false)
.setIsolationLevel(IsolationLevel.Serializable)
.setMaxRetries(10000)
.newTxnExecutor();
private final TxnExecutor pessimisticReadsBlock = stm.newTxnFactoryBuilder()
.setSpeculative(false)
.setReadLockMode(LockMode.Read)
.setMaxRetries(10000)
.newTxnExecutor();
private final TxnExecutor pessimisticWritesBlock = stm.newTxnFactoryBuilder()
.setSpeculative(false)
.setWriteLockMode(LockMode.Read)
.setMaxRetries(10000)
.newTxnExecutor();
public TransferThread(int id) {
super("TransferThread-" + id);
}
@Override
public void doRun() throws Exception {
int k = 0;
while (!stop) {
if (k % 100 == 0) {
System.out.printf("%s is at %s\n", getName(), k);
}
switch (mode) {
case snapshot:
runWithSnapshotIsolation();
break;
case serialized:
runWithSerializedIsolation();
break;
case pessimisticReadLevel:
runWithPessimisticReadLevel();
break;
case pessimisticWriteLevel:
runWithPessimisticWriteLevel();
break;
case pessimisticReads:
runWithPessimisticReads();
break;
case pessimisticWrites:
runWithPessimisticWrites();
break;
default:
throw new IllegalStateException();
}
k++;
}
}
private void runWithPessimisticReadLevel() {
pessimisticReadsBlock.execute(new TxnVoidCallable() {
@Override
public void call(Txn tx) throws Exception {
GammaTxn btx = (GammaTxn) tx;
run(btx, LockMode.None, LockMode.None);
}
});
}
private void runWithPessimisticWriteLevel() {
pessimisticWritesBlock.execute(new TxnVoidCallable() {
@Override
public void call(Txn tx) throws Exception {
GammaTxn btx = (GammaTxn) tx;
run(btx, LockMode.None, LockMode.None);
}
});
}
private void runWithSerializedIsolation() {
serializedBlock.execute(new TxnVoidCallable() {
@Override
public void call(Txn tx) throws Exception {
GammaTxn btx = (GammaTxn) tx;
run(btx, LockMode.None, LockMode.None);
}
});
}
private void runWithSnapshotIsolation() {
snapshotBlock.execute(new TxnVoidCallable() {
@Override
public void call(Txn tx) throws Exception {
GammaTxn btx = (GammaTxn) tx;
run(btx, LockMode.None, LockMode.None);
}
});
}
private void runWithPessimisticReads() {
snapshotBlock.execute(new TxnVoidCallable() {
@Override
public void call(Txn tx) throws Exception {
GammaTxn btx = (GammaTxn) tx;
run(btx, LockMode.Read, LockMode.None);
}
});
}
private void runWithPessimisticWrites() {
snapshotBlock.execute(new TxnVoidCallable() {
@Override
public void call(Txn tx) throws Exception {
GammaTxn btx = (GammaTxn) tx;
run(btx, LockMode.None, LockMode.Read);
}
});
}
public void run(GammaTxn tx, LockMode readLockMode, LockMode writeLockMode) {
int amount = randomInt(100);
Customer from = random(customer1, customer2);
Customer to = random(customer1, customer2);
long sum = from.account1.openForRead(tx, readLockMode.asInt()).long_value
+ from.account2.openForRead(tx, readLockMode.asInt()).long_value;
if (sum < 0) {
if (writeSkewEncountered.compareAndSet(false, true)) {
System.out.println("writeskew detected");
}
}
sleepRandomMs(5);
if (sum >= amount) {
GammaTxnLong fromAccount = from.getRandomAccount();
fromAccount.openForWrite(tx, writeLockMode.asInt()).long_value -= amount;
GammaTxnLong toAccount = to.getRandomAccount();
toAccount.openForWrite(tx, writeLockMode.asInt()).long_value += amount;
}
sleepRandomMs(5);
}
}
public Customer random(Customer customer1, Customer customer2) {
return randomBoolean() ? customer1 : customer2;
}
public class Customer {
private GammaTxnLong account1 = new GammaTxnLong(stm);
private GammaTxnLong account2 = new GammaTxnLong(stm);
public GammaTxnLong getRandomAccount() {
return randomBoolean() ? account1 : account2;
}
public String toString() {
return format("User(account1 = %s, account2 = %s, sum=%s)",
account1.atomicToString(), account2.atomicToString(), account1.atomicGet() + account2.atomicGet());
}
}
}