package org.multiverse.stms.gamma.integration.isolation; import org.junit.After; import org.junit.Before; import org.multiverse.TestThread; import org.multiverse.api.Txn; import org.multiverse.api.TxnExecutor; import org.multiverse.api.callables.TxnVoidCallable; import org.multiverse.stms.gamma.GammaStm; import org.multiverse.stms.gamma.transactionalobjects.GammaTxnLong; import org.multiverse.stms.gamma.transactionalobjects.Tranlocal; import org.multiverse.stms.gamma.transactions.GammaTxn; import static org.junit.Assert.assertEquals; import static org.multiverse.TestUtils.*; import static org.multiverse.api.GlobalStmInstance.getGlobalStmInstance; import static org.multiverse.api.TxnThreadLocal.clearThreadLocalTxn; //todo: testing of different lock modes //todo: testing if multiple transfers are done public abstract class MoneyTransfer_AbstractTest { private volatile boolean stop; private GammaTxnLong[] accounts; protected GammaStm stm; @Before public void setUp() { clearThreadLocalTxn(); stop = false; stm = (GammaStm) getGlobalStmInstance(); } @After public void tearDown() { System.out.println("Stm.GlobalConflictCount: " + stm.getGlobalConflictCounter().count()); for (GammaTxnLong ref : accounts) { System.out.println(ref.toDebugString()); } } protected abstract TxnExecutor newTxnExecutor(); public void run(int accountCount, int threadCount) { accounts = new GammaTxnLong[accountCount]; long initialAmount = 0; for (int k = 0; k < accountCount; k++) { long amount = randomInt(1000); initialAmount += amount; accounts[k] = new GammaTxnLong(stm, amount); } TransferThread[] threads = createThreads(threadCount); startAll(threads); sleepMs(30 * 1000); stop = true; joinAll(threads); assertEquals(initialAmount, getTotal()); } private long getTotal() { long sum = 0; for (GammaTxnLong account : accounts) { sum += account.atomicGet(); } return sum; } private TransferThread[] createThreads(int threadCount) { TransferThread[] threads = new TransferThread[threadCount]; for (int k = 0; k < threads.length; k++) { threads[k] = new TransferThread(k); } return threads; } private class TransferThread extends TestThread { public TransferThread(int id) { super("TransferThread-" + id); } public void doRun() { TxnExecutor executor = newTxnExecutor(); TxnVoidCallable callable = new TxnVoidCallable() { @Override public void call(Txn tx) throws Exception { GammaTxn btx = (GammaTxn) tx; GammaTxnLong from = accounts[randomInt(accounts.length)]; GammaTxnLong to = accounts[randomInt(accounts.length)]; int amount = randomInt(100); to.openForWrite(btx, LOCKMODE_NONE).long_value += amount; sleepRandomMs(10); Tranlocal toTranlocal = from.openForWrite(btx, LOCKMODE_NONE); if (toTranlocal.long_value < 0) { throw new NotEnoughMoneyException(); } toTranlocal.long_value -= amount; } }; int k = 0; while (!stop) { try { executor.execute(callable); if ((k % 500) == 0) { System.out.printf("%s is at iteration %s\n", getName(), k); } k++; } catch (NotEnoughMoneyException ignore) { } } } } private static class NotEnoughMoneyException extends RuntimeException { } }