package org.corfudb.samples; import org.corfudb.runtime.collections.ISMRMap; import org.corfudb.runtime.collections.SMRMap; import org.corfudb.runtime.exceptions.TransactionAbortedException; import org.corfudb.runtime.object.transactions.TransactionType; import java.util.ArrayList; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; /** * This class demonstrates transactions with write-write isolation level. * * By default, Corfu transaction wrapped between TXBegin()/TXEnd() guarantee serializabilty. * The other supported isolation levels are: * - write-write: Check that modification by this transaction do not overwrite concurrent modifications * - snapshot: Always commits, only guarantees execution against a consistent snapshot * * All transactions execute against a consistent snapshot, but have different conflict resolution criteria. * By default, the snapshot time of a transaction is the time of its first object accessor. * Corfu supports explicitly designating snapshot time (a log offset, which is returned by object mutators). * * Below, the utilitiy method {@link WriteWriteTXs ::TXBegin()} illustrates how to replace TXBegin() * with a transasction-builder indicating write-write isolation. * * The utility method {@link WriteWriteTXs ::TXBegin(long snapTime)} illustrates how to additionally set * a transasction snapshot-time to a specific position. * * Created by dalia on 12/30/16. */ public class WriteWriteTXs extends BaseCorfuAppUtils { /** * main() and standard setup methods are deferred to BaseCorfuAppUtils * @return */ static BaseCorfuAppUtils selfFactory() { return new WriteWriteTXs(); } public static void main(String[] args) { selfFactory().start(args); } /** * test parameters */ private final int NUM_BATCHES = 10; private final int BATCH_SZ = 1_000; private final int numTasks = NUM_BATCHES * BATCH_SZ; private final int TOTAL_PERCENT = 100; /** * This overrides TXBegin in order to set the transaction type to WRITE_AFTER_WRITE */ @Override protected void TXBegin() { getCorfuRuntime().getObjectsView().TXBuild() .setType(TransactionType.WRITE_AFTER_WRITE) .begin(); } /** * This variant of TXBegin sets a snapshot-point, * as well as sets the transaction type to WRITE_AFTER_WRITE */ protected void TXBegin(long snapTime) { getCorfuRuntime().getObjectsView().TXBuild() .setSnapshot(snapTime) .setType(TransactionType.WRITE_AFTER_WRITE) .begin(); } /** * This is where activity is started */ @Override void action() { final int NUM_THREADS = 2; final long THREAD_TIMEOUT = 300_000; // 5 minutes final int READ_PERCENT = 80; ArrayList<Thread> tList = new ArrayList<>(); // generate the maps and populate with elements generateMaps(); // first workload: // start NUM_THREADS threads and trigger activity in each for (int t = 0; t < NUM_THREADS; t++) { final int threadNum = t; //disabled. // tList.add(new Thread(() -> mixedReadWriteLoad2(threadNum, // READ_PERCENT))); tList.get(t).start(); } tList.forEach(th -> { try { th.join(THREAD_TIMEOUT); } catch (InterruptedException ie) { System.out.println("Thread timeout!"); } }); // second workload: tList.clear(); // start NUM_THREADS threads and trigger activity in each for (int t = 0; t < NUM_THREADS; t++) { final int threadNum = t; tList.add(new Thread(() -> mixedReadWriteLoad1(threadNum, READ_PERCENT))); tList.get(t).start(); } tList.forEach(th -> { try { th.join(THREAD_TIMEOUT); } catch (InterruptedException ie) { System.out.println("Thread timeout!"); } }); } /** * This workload operates over three distinct maps */ ISMRMap<String, Integer> map1, map2, map3; /** * Set up a repeatable PRNG */ final int SEED = 434343; Random rand = new Random(SEED); /** * This method initiates all the data structures for this program */ void generateMaps() { /** * Instantiate three streams with three SMRmap objects */ map1 = instantiateCorfuObject(SMRMap.class, "A"); map2 = instantiateCorfuObject(SMRMap.class, "B"); map3 = instantiateCorfuObject(SMRMap.class, "C"); // populate maps System.out.print("generating maps.."); for (int i = 0; i < NUM_BATCHES; i++) { System.out.print("."); TXBegin(); for (int j = 0; j < BATCH_SZ; j++) { map1.put("m1" + (i * BATCH_SZ + j), i); map2.put("m2" + (i * BATCH_SZ + j), i); map3.put("m3" + (i * BATCH_SZ + j), i); } TXEnd(); } System.out.println("END"); } /** * generate a workload with mixed R/W TX's * - NUM_BATCHES TX's * - each TX performs BATCH_SZ operations, mixing R/W according to the specified ratio. * * @param readPrecent ratio of reads (to 100) */ void mixedReadWriteLoad1(int threadNum, int readPrecent) { System.out.print("running mixedRWload1.."); long startt = System.currentTimeMillis(); AtomicInteger aborts = new AtomicInteger(0); for (int i = 0; i < NUM_BATCHES; i++) { System.out.print("."); TXBegin(); int accumulator = 0; for (int j = 0; j < BATCH_SZ; j++) { // perform random get()'s with probability 'readPercent/100' if (rand.nextInt(TOTAL_PERCENT) < readPrecent) { int r1 = rand.nextInt(numTasks); int r2 = rand.nextInt(numTasks); int r3 = rand.nextInt(numTasks); try { accumulator += map1.get("m1" + r1); accumulator += map2.get("m2" + r2); accumulator += map3.get("m3" + r3); } catch (Exception e) { System.out.println(threadNum + ": exception on iteration " + i + "," + j + ", r=" + r1 + "," + r2 + "," + r3); e.printStackTrace(); return; } } // otherwise, perform a random put() else { if (rand.nextInt(2) == 1) map2.put("m2" + rand.nextInt(numTasks), accumulator); else map3.put("m3" + rand.nextInt(numTasks), accumulator); } } try { TXEnd(); } catch (TransactionAbortedException te) { aborts.getAndIncrement(); } } System.out.println("END"); long endt = System.currentTimeMillis(); System.out.println(threadNum + ": mixedReadWriteLoad elapsed time (msecs): " + (endt - startt)); System.out.println(threadNum + ": #aborts/#TXs: " + aborts.get() + "/" + numTasks); } /** * This is a similar workload to mixedReadWriteLoad1, with an important optimization. * * All the transactions indicate an explicit snapshot timestamp to read from. * This prevents concurrent threads from flip-flopping between snapshots. * In order to prevent a transaction from viewing stale snapshots, transactions touch the entries they get(). * This causes abort if a transaction get()'s an entry which is modified by another. * * @param readPrecent ratio of reads (to 100) */ // This sample is currently not working, because snapshots for non read-only // transactions don't work. /* void mixedReadWriteLoad2(int threadNum, int readPrecent) { System.out.print("running mixedRWload2.."); long startt = System.currentTimeMillis(); AtomicInteger aborts = new AtomicInteger(0); // obtain the current snapshot time, before any transaction activity starts long snapTime = getCorfuRuntime() .getStreamsView() .get(getCorfuRuntime().getStreamID("A")) .; for (int i = 0; i < NUM_BATCHES; i++) synchronized (map1) { System.out.print("."); TXBegin(snapTime); // use the snapshot we kept as the starting snapshot int accumulator = 0; for (int j = 0; j < BATCH_SZ; j++) { // perform random get()'s with probability 'readPercent/100' if (rand.nextInt(TOTAL_PERCENT) < readPrecent) { int r1 = rand.nextInt(numTasks); int r2 = rand.nextInt(numTasks); int r3 = rand.nextInt(numTasks); try { // accumulator += map1.computeIfPresent("m1" + r1, (K,V) -> V); // accumulator += map2.computeIfPresent("m2" + r2, (K,V) -> V); // accumulator += map3.computeIfPresent("m3" + r3, (K,V) -> V); accumulator += map1.get("m1" + r1); accumulator += map2.get("m2" + r2); accumulator += map3.get("m3" + r3); } catch (Exception e) { System.out.println(threadNum + ": exception on iteration " + i + "," + j + ", r=" + r1 + "," + r2 + "," + r3); e.printStackTrace(); return; } } // otherwise, perform a random put() else { if (rand.nextInt(2) == 1) map2.put("m2" + rand.nextInt(numTasks), accumulator); else map3.put("m3" + rand.nextInt(numTasks), accumulator); } } try { TXEnd(); } catch (TransactionAbortedException te) { aborts.getAndIncrement(); } } System.out.println("END"); long endt = System.currentTimeMillis(); System.out.println(threadNum + ": mixedReadWriteLoad elapsed time (msecs): " + (endt - startt)); System.out.println(threadNum + ": #aborts/#TXs: " + aborts.get() + "/" + numTasks); } */ }