/** * Copyright 2005-2012 Akiban Technologies, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.io.PrintStream; import java.util.Random; import com.persistit.Exchange; import com.persistit.Key; import com.persistit.Persistit; import com.persistit.Transaction; import com.persistit.TransactionRunnable; import com.persistit.exception.PersistitException; import com.persistit.exception.RollbackException; import com.persistit.exception.TransactionFailedException; /** * Demonstrates the use of Persisit Transactions. This demo runs multiple * threads that transfer "money" between accounts. At all times the sum of all * balances should remain unchanged. * * @version 1.0 */ public class SimpleTransaction implements Runnable { final static Object LOCK = new Object(); static int _threadCount = 0; static int _rolledBackTransactionCount = 0; static int _committedTransactionCount = 0; static int _failedTransactionCount = 0; int _threadIndex = 0; int _iterationsPerThread; int _accounts; final static Persistit persistit = new Persistit(); public static void main(String[] args) throws Exception { try { int threads = 10; int iterationsPerThread = 100000; int accounts = 10000; if (args.length > 0) threads = Integer.parseInt(args[0]); if (args.length > 1) iterationsPerThread = Integer.parseInt(args[1]); if (args.length > 2) accounts = Integer.parseInt(args[2]); // // Read configuration from persistit.properties file. // persistit.initialize(); // // An Exchange for the Tree containing account "balances". // Exchange accountEx = new Exchange(persistit, "txndemo", "account", true); // // Get the starting "balance", that is, the sum of the amounts in // each account. // System.out.println("Computing balance"); int startingBalance = balance(accountEx); System.out.println("Starting balance is " + startingBalance); // // Create the threads // Thread[] threadArray = new Thread[threads]; for (int index = 0; index < threads; index++) { threadArray[index] = new Thread(new SimpleTransaction(iterationsPerThread, accounts)); } // // Start them all and measure the time until the last thread ends // long time = System.currentTimeMillis(); System.out.println("Starting transaction threads"); for (int index = 0; index < threads; index++) { threadArray[index].start(); } System.out.println("Waiting for threads to end"); for (int index = 0; index < threads; index++) { threadArray[index].join(); } // // All done // time = System.currentTimeMillis() - time; System.out.println("All threads ended at " + time + " ms"); System.out.println("Completed transactions: " + _committedTransactionCount); System.out.println("Failed transactions: " + _failedTransactionCount); System.out.println("Retried transactions: " + _rolledBackTransactionCount); System.out.println("Average completed transactions rate: " + (_committedTransactionCount * 1000 / time) + " per second"); int endingBalance = balance(accountEx); System.out.print("Ending balance is " + endingBalance + " which "); System.out.println(endingBalance == startingBalance ? "AGREES" : "DISAGREES"); } catch (NumberFormatException e) { usage(); } finally { // // Always close Persistit. // persistit.close(); } } static void usage() { PrintStream o = System.out; o.println("Demonstrates Persistit transactions. This program"); o.println("transfers random amounts of \"money\" between "); o.println("randomly chosen \"accounts.\" The parameters specify "); o.println("how many threads, how many iterations per threads, "); o.println("and the total number of accounts to receive these"); o.println("transfers. Usage:"); o.println(); o.println("java SimpleTransaction <nthreads> <itersPerThread> <accounts>"); o.println(); } SimpleTransaction(int iterations, int accounts) { _iterationsPerThread = iterations; _accounts = accounts; synchronized (LOCK) { _threadIndex = _threadCount++; } } public String toString() { return "SimpleTransaction #" + _threadIndex; } public void run() { try { Exchange accountEx = new Exchange(persistit, "txndemo", "account", true); Random random = new Random(); for (int iterations = 0; iterations < _iterationsPerThread; iterations++) { // Choose the first random "account" number. int accountNo1 = random.nextInt(_accounts); // Choose a different second random "account" number. int accountNo2 = random.nextInt(_accounts - 1); if (accountNo2 >= accountNo1) accountNo2++; // Choose a random amount of "money" int delta = random.nextInt(10000); transfer(accountEx, accountNo1, accountNo2, delta); if (iterations % 25000 == 0) { System.out.println(this + " has finished " + iterations + " iterations"); System.out.flush(); } } Transaction txn = accountEx.getTransaction(); // // Copy per-Transaction counters to global counters. // _committedTransactionCount += txn.getCommittedTransactionCount(); _rolledBackTransactionCount += txn.getRolledBackTransactionCount(); } catch (Exception exception) { _failedTransactionCount++; exception.printStackTrace(); } } void transfer(Exchange ex, int accountNo1, int accountNo2, int delta) throws PersistitException { // A Transaction object is a context in which a transaction scope // begins, commits and ends. // Transaction txn = ex.getTransaction(); int remainingAttempts = 10; // // Retry until successful commit or failure // for (;;) { // Start the scope of a transaction. // txn.begin(); try { // Debit accountNo1. // ex.clear().append(accountNo1).fetch(); int balance1 = ex.getValue().isDefined() ? ex.getValue().getInt() : 0; ex.getValue().put(balance1 - delta); ex.store(); // // Credit accountNo2. // ex.clear().append(accountNo2).fetch(); int balance2 = ex.getValue().isDefined() ? ex.getValue().getInt() : 0; ex.getValue().put(balance2 + delta); ex.store(); // // Commit the transaction and finish the loop. // txn.commit(); break; } catch (RollbackException rollbackException) { if (--remainingAttempts <= 0) { throw new TransactionFailedException(); } } finally { // Every begin() must have a matching call to end(). // txn.end(); } } } // // To illustrate another way to use the Transaction API, this // method uses a TransactionRunnable to perform its logic. The // TransactionRunnable (see the Balancer class, below) encapsulates // all the logic to be performed within the transaction scope. // The Transaction run() method handles provides the transaction scope // and handles retries appropriately. // static int balance(final Exchange ex) throws PersistitException { Transaction txn = ex.getTransaction(); Balancer balancer = new Balancer(ex); txn.run(balancer); return balancer.getTotal(); } // // A TransactionRunnable that encapsulates the logic to be performed within // a transaction. // static class Balancer implements TransactionRunnable { int _total = 0; Exchange _ex; Balancer(Exchange ex) { _ex = ex; } public void runTransaction() throws PersistitException { _ex.clear().append(Key.BEFORE); // // Must reset the total here because due to optimistic scheduling // this code may retry. Only Persistit database updates are reset // on a rollback, not fields and variables. // _total = 0; while (_ex.next()) { int balance = _ex.getValue().getInt(); _total += balance; } } int getTotal() { return _total; } } }