/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
licenses@blazegraph.com
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Created on Oct 24, 2006
*/
package com.bigdata.btree.isolation;
import junit.framework.TestCase;
import org.apache.log4j.Logger;
/**
* This test case demonstrates a state-based validation technique described in
* http://www.cs.brown.edu/~mph/Herlihy90a/p96-herlihy.pdf for a "bank account"
* data type.
*
* @todo There are several things that are different about this approach from my
* preconceptions.<br>
* First, there is a distinct between stable state (the account balance)
* and transaction local state (the low, high, and change for that account
* within the transaction).<br>
* This notion of validation is in terms of the objects API (credit and
* debit in this case) rather than in terms of writes of opaque state that
* are then unpacked when a write-write conflict is detected.<br>
* In the atomic commit protocol, validation examines transaction local
* state as well as the global state and updates the global state
* atomically iff validation succeeds.<br>
* This does not appear to depend on the notion of version counters to
* trigger state-based
* This raises lots of questions. I need to think through how a
* transaction containing multiple objects could be modeled, how this
* relates to what is already implemented, and the relationship between
* this approach and the notions that I have for handling link set
* membership and clustered index membership changes with high
* concurrency.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @version $Id$
*/
public class TestAccount extends TestCase {
private static final Logger log = Logger.getLogger(TestAccount.class);
/**
*
*/
public TestAccount() {
}
/**
* @param arg0
*/
public TestAccount(String arg0) {
super(arg0);
}
/**
* An implementation of a bank account data type used to test state-based
* validation. The basic data type just carries the account balance. An
* instance of that data type may be wrapped up in transactional state (low,
* high, and change). A transactional instance knows how to validate against
* the basic data type and on commit it updates the basic data type.
*
* @see http://www.cs.brown.edu/~mph/Herlihy90a/p96-herlihy.pdf, section 5.2
* page 112.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @version $Id$
*/
public static class Account {
/**
* The account's persistent state (in cents).
*/
long bal = 0;
}
/**
* A transactional view of an {@link Account}. Each transaction has its own
* transaction local state (low, high, and change). All operations within a
* transaction are applied to the {@link TxAccount}. If the transaction
* validates and commits, then the net <i>change</i> in the balance due to
* the operations on the transaction is applied to the {@link Account}
* balance.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @version $Id$
*/
public static class TxAccount {
final Account account;
public TxAccount(Account account) {
assert account != null;
this.account = account;
}
/**
* The observed lower bound on the current balance (initially zero).
*/
long low = 0;
/**
* The observed upper bound on the current balance (initially infinity).
*/
long high = Long.MAX_VALUE;
/**
* The transaction's net change to the balance (initially zero).
*/
long change = 0;
/**
* Credit the account.
*
* @param cents
* The amount.
*/
public void credit( long cents ) {
change = change + cents;
}
/**
* Debit the account.
*
* @param cents
* The amount.
* @throws OverdraftException
* iff an overdraft must result.
*/
public void debit( long cents ) throws OverdraftException {
// 5 + 6 > 10
if( account.bal + change >= cents ) {
// 4 = max( 0, 10 - 6 )
low = Math.max(low, cents - change);
// -4 = 6 - 10
change = change - cents;
} else {
/*
* overdraft.
*/
high = Math.min( high, cents - change );
throw new OverdraftException();
}
}
public String toString() {
return "bal="+account.bal+", low="+low+", high="+high+", change="+change;
}
/**
* <p>
* Validate the transaction against the current account balance.
* </p>
* <p>
* Note: Validation is currently invoked from within the {@link #commit()},
* which handles atomicity with respect to the account balance.
* </p>
*/
public boolean validate() {
if(log.isInfoEnabled())
log.info("validate: " + toString());
if (low <= account.bal && account.bal < high) {
// valid.
return true;
}
return false;
}
/**
* <p>
* Validate against the current account balance and commit the change to
* the account.
* </p>
* <p>
* Note: This validate + commit operation needs to be atomic. That is
* achieved here by synchronizing on the {@link #account}. However that
* technique will not work in a distributed system.
* </p>
*/
public void commit() {
synchronized (account) {
if (!validate()) {
throw new RuntimeException("Validation error.");
}
account.bal = account.bal + change;
}
}
public static class OverdraftException extends RuntimeException {
private static final long serialVersionUID = 1L;
}
}
/**
* <p>
* Runs a schedule and verifies the intermediate and stable states for an
* {@link Account}.
* </p>
* <p>
* The schedule is from page 101 of
* http://www.cs.brown.edu/~mph/Herlihy90a/p96-herlihy.pdf. This schedule
* interleaves two transactions, P and Q. There is an initial balance of $0.
* There is a $5 credit on P followed by a $6 credit on Q. P then validates
* and commits (validation occurs during the commit protocol), with a
* resulting stable balance of $5. A $10 debit is then made on Q and Q
* validates and commits (again, validation is part of the commit). The
* final stable balance is $1.
* </p>
*
* <pre>
* a Credit($5)/Ok( ) P
* a Credit($6)/Ok( ) Q
* a Commit P
* a Debit($lO)/Ok( ) Q
* a Commit Q
* </pre>
*/
public void test_Schedule01() {
Account a = new Account();
TxAccount p = new TxAccount(a);
TxAccount q = new TxAccount(a);
assertEquals("bal", 0, a.bal);
assertEquals("low", 0, p.low );
assertEquals("high", Long.MAX_VALUE, p.high );
assertEquals("change", 0, p.change );
assertEquals("low", 0, q.low );
assertEquals("high", Long.MAX_VALUE, q.high );
assertEquals("change", 0, q.change );
p.credit(5);
assertEquals("low", 0, p.low );
assertEquals("high", Long.MAX_VALUE, p.high );
assertEquals("change", 5, p.change);
q.credit(6);
assertEquals("low", 0, q.low );
assertEquals("high", Long.MAX_VALUE, q.high );
assertEquals("change", 6, q.change);
assertEquals("bal", 0, a.bal);
// assertTrue(p.validate());
p.commit();
assertEquals("bal", 5, a.bal);
q.debit(10);
assertEquals("low", 4, q.low );
assertEquals("high", Long.MAX_VALUE, q.high );
assertEquals("change", -4, q.change);
assertEquals("bal", 5, a.bal);
// assertTrue(q.validate());
q.commit();
assertEquals("bal", 1, a.bal);
}
}