/** * 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. */ package com.persistit.stress.unit; import com.persistit.Accumulator.SumAccumulator; import com.persistit.Exchange; import com.persistit.Key; import com.persistit.Transaction; import com.persistit.TransactionRunnable; import com.persistit.exception.PersistitException; import com.persistit.util.ArgParser; import com.persistit.util.Debug; /** * @version 1.0 */ public class Stress8txn extends StressBase { private final static int MOD_A = 25; private final static int MOD_B = 5; private final static int MOD_C = 5; // Constraint: no larger than maximum // number of Accumulators in a Tree public Stress8txn(final String argsString) { super(argsString); } private final static String[] ARGS_TEMPLATE = { "repeat|int:1:0:1000000000|Repetitions", "count|int:100:0:100000|Number of iterations per cycle", "size|int:1000:1:100000000|Number of 'C' accounts", "seed|int:1:1:20000|Random seed", }; static boolean _consistencyCheckDone; int _size; int _seed; int _mvvReports; @Override public void setUp() throws Exception { super.setUp(); _ap = new ArgParser("com.persistit.Stress8txn", _args, ARGS_TEMPLATE).strict(); _total = _ap.getIntValue("count"); _repeatTotal = _ap.getIntValue("repeat"); _size = _ap.getIntValue("size"); _seed = _ap.getIntValue("seed"); seed(_seed); try { _exs = getPersistit().getExchange("persistit", "shared", true); } catch (final Exception ex) { handleThrowable(ex); } } /** * <p> * Implements tests with "accounts" to be updated transactionally There is a * hierarchy of accounts categories A, B and C. A accounts contain B * accounts which contain C accounts. At all times, the sums of C accounts * must match the total in their containing B account, and so on. The * overall sum of every account must always be 0. Operations are: * <ol> * <li>"transfer" (add/subtract) an amount from a C account to another C * account within the same B.</li> * <li>"transfer" (add/subtract) an amount from a C account to a C account * in a different B account, resulting in changes to B and possibly A * account totals.</li> * <li>Consistency check - determining that the sub-accounts total to the * containing account total.</li> * </ol> * </p> * <p> * As a wrinkle, a few "account" totals are represented by strings of a * length that represents the account total, rather than by an integer. This * is to test long record management during transactions. * </p> * <p> * The expected result is that each consistency check will match, no matter * what. This includes the result of abruptly stopping and restarting the * JVM. The first thread starting this test performs a consistency check * across the entire database to make sure that the result of any recovery * operation is correct. * </p> */ @Override public void executeTest() { final Transaction txn = _exs.getTransaction(); synchronized (Stress8txn.class) { if (!_consistencyCheckDone) { _consistencyCheckDone = true; try { txn.begin(); if (totalConsistencyCheck()) { System.out.println("Consistency check completed successfully"); } txn.commit(); } catch (final PersistitException pe) { fail(pe); } finally { txn.end(); } } } final Operation[] ops = new Operation[6]; ops[0] = new Operation0(); ops[1] = new Operation1(); ops[2] = new Operation2(); ops[3] = new Operation3(); ops[4] = new Operation4(); ops[5] = new Operation5(); for (_repeat = 0; (_repeat < _repeatTotal || isUntilStopped()) && !isStopped(); _repeat++) { for (_count = 0; (_count < _total) && !isStopped(); _count++) { try { final int selector = select(); final Operation op = ops[selector]; final int acct1 = random(0, _size); final int acct2 = random(0, _size); op.setup(acct1, acct2); final int passes = txn.run(op, 100, 5, getPersistit().getDefaultTransactionCommitPolicy()); Debug.$assert1.t(passes <= 90); } catch (final Exception pe) { fail(pe); } } } } private int select() { final int r = random(0, 1000); if (r < 500) { return 0; } if (r < 800) { return 1; } if (r < 900) { return 2; } if (r < 950) { return 3; } if (r < 990) { return 4; } return 5; } private abstract class Operation implements TransactionRunnable { int _a1, _b1, _c1, _a2, _b2, _c2; void setup(final int acct1, final int acct2) { _a1 = (acct1 / MOD_A); _b1 = (acct1 / MOD_B) % MOD_B; _c1 = (acct1 % MOD_C); _a2 = (acct2 / MOD_A); _b2 = (acct2 / MOD_B) % MOD_B; _c2 = (acct2 % MOD_C); } } private class Operation0 extends Operation { /** * Transfers from one C account to another within the same B */ @Override public void runTransaction() throws PersistitException { final int delta = random(-1000, 1000); if (_c1 != _c2) { _exs.clear().append("stress8txn").append(_a1).append(_b1).append(_c1).fetch(); putAccountValue(_exs, getAccountValue(_exs) + delta, _c1 == 1); _exs.store(); addWork(1); _exs.clear().append("stress8txn").append(_a1).append(_b1).append(_c2).fetch(); putAccountValue(_exs, getAccountValue(_exs) - delta, _c2 == 1); _exs.store(); addWork(1); } final SumAccumulator acc1 = _exs.getTree().getSumAccumulator(_c1); final SumAccumulator acc2 = _exs.getTree().getSumAccumulator(_c2); acc1.add(delta); acc2.add(-delta); } } private class Operation1 extends Operation { /* * Transfers from one C account to another in possibly a different B * account */ @Override public void runTransaction() throws PersistitException { final int delta = random(-1000, 1000); if ((_c1 != _c2) || (_b1 != _b2) || (_a1 != _a2)) { _exs.clear().append("stress8txn").append(_a1).append(_b1).append(_c1).fetch(); putAccountValue(_exs, getAccountValue(_exs) + delta, _c1 == 1); _exs.store(); addWork(1); _exs.clear().append("stress8txn").append(_a2).append(_b2).append(_c2).fetch(); putAccountValue(_exs, getAccountValue(_exs) - delta, _c2 == 1); _exs.store(); addWork(1); } if ((_b1 != _b2) || (_a1 != _a2)) { _exs.clear().append("stress8txn").append(_a1).append(_b1).fetch(); putAccountValue(_exs, getAccountValue(_exs) + delta, _b1 == 1); _exs.store(); addWork(1); _exs.clear().append("stress8txn").append(_a2).append(_b2).fetch(); putAccountValue(_exs, getAccountValue(_exs) - delta, _b1 == 1); _exs.store(); addWork(1); } if (_a1 != _a2) { _exs.clear().append("stress8txn").append(_a1).fetch(); putAccountValue(_exs, getAccountValue(_exs) + delta, _a1 == 1); _exs.store(); addWork(1); _exs.clear().append("stress8txn").append(_a2).fetch(); putAccountValue(_exs, getAccountValue(_exs) - delta, _a1 == 1); _exs.store(); addWork(1); } final SumAccumulator acc1 = _exs.getTree().getSumAccumulator(_c1); final SumAccumulator acc2 = _exs.getTree().getSumAccumulator(_c2); acc1.add(delta); acc2.add(-delta); } } private class Operation2 extends Operation { /** * Perform consistency check across a B account */ @Override public void runTransaction() throws PersistitException { _exs.clear().append("stress8txn").append(_a1).append(_b1).fetch(); addWork(1); final int valueB = getAccountValue(_exs); final int totalC = accountTotal(_exs); if (valueB != totalC) { fail("totalC=" + totalC + " valueB=" + valueB + " at " + _exs); } } } private class Operation3 extends Operation { /** * Perform consistency check across an A account */ @Override public void runTransaction() throws PersistitException { _exs.clear().append("stress8txn").append(_a1).fetch(); addWork(1); final int valueA = getAccountValue(_exs); final int totalB = accountTotal(_exs); if (valueA != totalB) { fail("totalB=" + totalB + " valueA=" + valueA + " at " + _exs); } } } private class Operation4 extends Operation { /** * Perform consistency check across an A account */ @Override public void runTransaction() throws PersistitException { _exs.clear().append("stress8txn"); final int totalA = accountTotal(_exs); if (totalA != 0) { fail("totalA=" + totalA + " at " + _exs); } } } private class Operation5 extends Operation { /** * Perform consistency check across all accounts */ @Override public void runTransaction() throws PersistitException { totalConsistencyCheck(); if ((_mvvReports++ % 1000) == 0) { System.out.println("Consistency check passed"); } } } private int accountTotal(final Exchange ex) throws PersistitException { int total = 0; ex.append(Key.BEFORE); while (ex.next()) { addWork(1); final int value = getAccountValue(ex); total += value; } ex.cut(); return total; } private boolean totalConsistencyCheck() throws PersistitException { int totalA = 0; final Exchange exa = new Exchange(_exs); final Exchange exb = new Exchange(_exs); final Exchange exc = new Exchange(_exs); int countA = 0; exa.clear().append("stress8txn").append(Key.BEFORE); while (exa.next()) { addWork(1); countA++; exa.fetch(); final int valueA = getAccountValue(exa); final int valueAA = getAccountValue(exa); Debug.$assert1.t(valueA == valueAA); totalA += valueA; int totalB = 0; exa.getKey().copyTo(exb.getKey()); exb.append(Key.BEFORE); int countB = 0; while (exb.next()) { countB++; exb.fetch(); addWork(1); final int valueB = getAccountValue(exb); final int valueBB = getAccountValue(exb); Debug.$assert1.t(valueB == valueBB); totalB += valueB; int totalC = 0; exb.getKey().copyTo(exc.getKey()); exc.append(Key.BEFORE); int countC = 0; while (exc.next()) { addWork(1); countC++; final int valueC = getAccountValue(exc); exc.fetch(); addWork(1); final int valueCC = getAccountValue(exc); Debug.$assert1.t(valueC == valueCC); totalC += valueC; } if (totalC != valueB) { int totalC1 = 0; int countC1 = 0; while (exc.next()) { countC1++; final int valueC1 = getAccountValue(exc); exc.fetch(); addWork(1); final int valueCC1 = getAccountValue(exc); Debug.$assert1.t(valueC1 == valueCC1); totalC1 += valueC1; } fail("totalC=" + totalC + " valueB=" + valueB + " at " + exb); return false; } } if (totalB != valueA) { fail("totalB=" + totalB + " valueA=" + valueA + " at " + exa); return false; } } if (totalA != 0) { fail("totalA=" + totalA + " at " + exa); return false; } int totalAcc = 0; for (int i = 0; i < 25; i++) { final SumAccumulator acc = _exs.getTree().getSumAccumulator(i); totalAcc += acc.getSnapshotValue(); } if (totalAcc != 0) { fail("totalAcc=" + totalAcc); } return true; } private int getAccountValue(final Exchange ex) { if (!ex.getValue().isDefined()) { return 0; } try { if (ex.getValue().isType(String.class)) { _sb.setLength(0); ex.getValue().getString(_sb); return _sb.length(); } else { return ex.getValue().getInt(); } } catch (final NullPointerException npe) { npe.printStackTrace(); try { Thread.sleep(10000); } catch (final InterruptedException ie) { } throw npe; } } private void putAccountValue(final Exchange ex, final int value, final boolean string) { if ((value > 0) && (value < 20000) && ((random(0, 100) == 0) || string)) { _sb.setLength(0); int i = 0; for (i = 100; i < value; i += 100) { _sb.append("......... ......... ......... ......... ......... " + "......... ......... ......... ......... "); int k = i; for (int j = 1; (k != 0) && (j < 10); j++) { _sb.setCharAt(i - j, (char) ('0' + (k % 10))); k /= 10; } } for (i = i - 100; i < value; i++) { _sb.append("."); } if (_sb.length() != value) { throw new RuntimeException("oops"); } ex.getValue().putString(_sb); } else { ex.getValue().put(value); } } }