<chapter id="transactions">
<title>Transactions and Concurrency</title>
<section>
	<title>Concurrent Access</title>

    <para>JBoss Cache uses a pessimistic locking scheme by default to prevent concurrent
    access to the same data. Optimistic locking may alternatively be used, and is discussed later.  </para>

	<section><title>Locks</title>
	    <para>Locking is done internally, on a node-level.  For example when we
	    want to access "/a/b/c", a lock will be acquired for nodes "a", "b" and
	    "c". When the same transaction wants to access "/a/b/c/d", since we
	    already hold locks for "a", "b" and "c", we only need to acquire a lock
	    for "d".</para>		
	    <para>Lock owners are either transactions (call is made within the scope of an existing transaction)
	    or threads (no transaction associated with the call).
	    Regardless, a transaction or a thread is internally transformed into
	    an instance of <literal>GlobalTransaction</literal>, which is used as a globally unique ID
	    for modifications across a cluster. E.g. when we run a two-phase commit
	    protocol (see below) across the cluster, the <literal>GlobalTransaction</literal> uniquely
	    identifies the unit of work across a cluster.</para>
	
		<para>Locks can be read or write locks. Write locks serialize read and
	    write access, whereas read-only locks only serialize read access. When a
	    write lock is held, no other write or read locks can be acquired. When a
	    read lock is held, others can acquire read locks. However, to acquire
	    write locks, one has to wait until all read locks have been released. When
	    scheduled concurrently, write locks always have precedence over read
	    locks. Note that (if enabled) read locks can be upgraded to write
	    locks.</para>

	    <para>Using read-write locks helps in the following scenario: consider a
	    tree with entries "/a/b/n1" and "/a/b/n2". With write-locks, when Tx1
	    accesses "/a/b/n1", Tx2 cannot access "/a/b/n2" until Tx1 has completed
	    and released its locks. However, with read-write locks this is possible,
	    because Tx1 acquires read-locks for "/a/b" and a read-write lock for
	    "/a/b/n1". Tx2 is then able to acquire read-locks for "/a/b" as well, plus
	    a read-write lock for "/a/b/n2". This allows for more concurrency in
	    accessing the cache.</para>
	</section>


	<section><title>Pessimistic locking</title>
	<para>By default, JBoss Cache uses pessimistic locking.  Locking is not exposed directly to user. Instead, a transaction isolation level which provides different locking behaviour is configurable.</para>
		<section><title>Isolation levels</title>
		    <para>JBoss Cache supports the following transaction isolation levels, analogous to database ACID isolation levels. A user can configure an instance-wide isolation level of NONE, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, or SERIALIZABLE. REPEATABLE_READ is the default isolation level used.</para>

		    <orderedlist>
		      <listitem>
		        <para>NONE. No transaction support is needed. There is no locking at
		        this level, e.g., users will have to manage the data integrity.
		        Implementations use no locks.</para>
		      </listitem>

		      <listitem>
		        <para>READ_UNCOMMITTED. Data can be read anytime while write
		        operations are exclusive. Note that this level doesn't prevent the
		        so-called "dirty read" where data modified in Tx1 can be read in Tx2
		        before Tx1 commits. In other words, if you have the following
		        sequence,
		<programlisting>
			Tx1   Tx2
		     W
		           R
		</programlisting>
		
		using this isolation level will not Tx2 read operation.
		        Implementations typically use an exclusive lock for writes while reads
		        don't need to acquire a lock.</para>
		      </listitem>

		      <listitem>
		        <para>READ_COMMITTED. Data can be read any time as long as there is no
		        write. This level prevents the dirty read. But it doesn’t prevent the
		        so-called ‘non-repeatable read’ where one thread reads the data twice
		        can produce different results. For example, if you have the following
		        sequence, 
		<programlisting>
		Tx1   Tx2
		 R
		       W
		 R
		</programlisting></para>

		        <para>where the second read in Tx1 thread will produce different
		        result.</para>

		        <para>Implementations usually use a read-write lock; reads succeed
		        acquiring the lock when there are only reads, writes have to wait
		        until there are no more readers holding the lock, and readers are
		        blocked acquiring the lock until there are no more writers holding the
		        lock. Reads typically release the read-lock when done, so that a
		        subsequent read to the same data has to re-acquire a read-lock; this
		        leads to nonrepeatable reads, where 2 reads of the same data might
		        return different values. Note that, the write only applies regardless
		        of transaction state (whether it has been committed or not).</para>
		      </listitem>

		      <listitem>
		        <para>REPEATABLE_READ. Data can be read while there is no write and
		        vice versa. This level prevents "non-repeatable read" but it does not
		        prevent the so-called "phantom read" where new data can be inserted
		        into the tree from the other transaction. Implementations typically
		        use a read-write lock.  This is the default isolation level used.</para>
		      </listitem>

		      <listitem>
		        <para>SERIALIZABLE. Data access is synchronized with exclusive locks.
		        Only 1 writer or reader can have the lock at any given time. Locks are
		        released at the end of the transaction.  Regarded as very poor for performance and thread/transaction concurrency.</para>
		      </listitem>
		    </orderedlist>
		
		</section>
         
         <section>
             <title>Insertion and Removal of Nodes</title>
         
	         <para>
	         By default, before inserting a new node into the tree or removing an existing node from the
	         tree, JBoss Cache will only attempt to acquire a read lock on the new node's parent node.  
	         This approach does not treat child nodes as an integral part of a parent node's state. 
	         This approach allows greater concurrency if nodes are frequently added or removed, but
	         at a cost of lesser correctness. For use cases where greater correctness is necessary, JBoss
	         Cache provides a configuration option <literal>LockParentForChildInsertRemove</literal>.
	         If this is set to <literal>true</literal>, insertions and removals of child nodes 
	         require the acquisition of a <emphasis>write lock</emphasis> on the parent node.
	         </para>
         </section>
         
	</section>

	<section><title>Optimistic locking</title>
	<para>The motivation for optimistic locking is to improve concurrency.  When a lot of threads have a lot of contention for access to the data tree, it can be inefficient to lock portions of the tree - for reading or writing - for the entire duration of a transaction as we do in pessimistic locking.  Optimistic locking allows for greater concurrency of threads and transactions by using a technique called data versioning, explained here.  Note that isolation levels (if configured) are ignored if optimistic locking is enabled.</para>
	<section><title>Architecture</title>
	<para>Optimistic locking treats all method calls as transactional<footnote><para>Because of this requirement, you must always have a transaction manager configured when using optimistic locking.</para></footnote>.  Even if you do not invoke a call within the scope of an ongoing transaction, JBoss Cache creates an implicit transaction and commits this transaction when the invocation completes.  Each transaction maintains a transaction workspace, which contains a copy of the data used within the transaction.</para><para>For example, if a transaction calls get("/a/b/c"), nodes a, b and c are copied from the main data tree and into the workspace.  The data is versioned and all calls in the transaction work on the copy of the data rather than the actual data.  When the transaction commits, it's workspace is merged back into the underlying tree by matching versions.  If there is a version mismatch - such as when the actual data tree has a higher version than the workspace, perhaps if another transaction were to access the same data, change it and commit before the first transaction can finish - the transaction throws a <literal>RollbackException</literal> when committing and the commit fails.</para>
	<para>Optimistic locking uses the same locks we speak of above, but the locks are only held for a very short duration - at the start of a transaction to build a workspace, and when the transaction commits and has to merge data back into the tree.</para>
	<para>
		So while optimistic locking may occasionally fail if version validations fail or may run slightly slower than pessimistic locking due to the inevitable overhead and extra processing of maintaining workspaces, versioned data and validating on commit, it does buy you a near-SERIALIZABLE degree of data integrity while maintaining a very high level of concurrency.
	</para>
	</section>
	<section><title>Configuration</title>
		Optimistic locking is enabled by using the NodeLockingScheme XML attribute, and setting it to "OPTIMISTIC":
		<programlisting>
			...
			&lt;!--
				Node locking scheme:
			    OPTIMISTIC
			    PESSIMISTIC (default)
			--&gt;
			&lt;attribute name="NodeLockingScheme"&gt;OPTIMISTIC&lt;/attribute&gt;
			...
		</programlisting>
	</section>
	</section>
</section>


  <section>
    <title>Transactional Support</title>

    <para>JBoss Cache can be configured to use transactions to bundle units of
    work, which can then be replicated as one unit. Alternatively, if
    transaction support is disabled, it is equivalent to setting AutoCommit to
    on where modifications are potentially<footnote>
        <para>Depending on whether interval-based asynchronous replication is used</para>
      </footnote> replicated after every change (if replication is
    enabled).</para>

    <para>What JBoss Cache does on every incoming call (e.g. put()) is:</para>
<orderedlist>
	<listitem><para>get the transaction associated with the thread</para></listitem>
	<listitem><para>register (if not already done) with the transaction manager to be notified when a transaction commits or is rolled back.</para></listitem>
</orderedlist>
	<para>
		In order to do this, the cache has
    to be configured with an instance of a <literal>TransactionManagerLookup</literal> which
    returns a <literal>javax.transaction.TransactionManager</literal>.</para>

    <para>JBoss Cache ships with <literal>JBossTransactionManagerLookup</literal> and <literal>GenericTransactionManagerLookup</literal>.  The <literal>JBossTransactionManagerLookup</literal> is able to bind to a running JBoss Application Server and retrieve a <literal>TransactionManager</literal> while the <literal>GenericTransactionManagerLookup</literal> is able to bind to most popular Java EE application servers and provide the same functionality. A dummy implementation - <literal>DummyTransactionManagerLookup</literal> - is also provided, which may be used for standalone JBoss Cache applications and unit tests running outside a Java EE Application Server. Being a dummy, however, this is just for demo and testing purposes and is not recommended for production use.</para>
<para>The implementation of the <literal>JBossTransactionManagerLookup</literal> is as follows:</para>

    <programlisting>public class JBossTransactionManagerLookup implements TransactionManagerLookup {

    public JBossTransactionManagerLookup() {}

    public TransactionManager getTransactionManager() throws Exception {
       Object tmp=new InitialContext().lookup("java:/TransactionManager");
       return (TransactionManager)tmp;
    }
}</programlisting>

    <para>The implementation looks up the JBoss Transaction Manager from
    JNDI and returns it.</para>

    <para>When a call comes in, the <literal>TreeCache</literal> gets the current transaction and
    records the modification under the transaction as key. (If there is no
    transaction, the modification is applied immediately and possibly
    replicated). So over the lifetime of the transaction all modifications
    will be recorded and associated with the transaction. Also, the <literal>TreeCache</literal>
    registers with the transaction to be notified of transaction committed or
    aborted when it first encounters the transaction.</para>

    <para>When a transaction rolls back, we undo the changes in the cache and
    release all locks.</para>

    <para>When the transaction commits, we initiate a two-phase commit
    protocol<footnote>
        <para>Only with synchronous replication or invalidation.</para>
      </footnote> : in the first phase, a PREPARE containing all modifications
    for the current transaction is sent to all nodes in the cluster. Each node
    acquires all necessary locks and applies the changes, and then sends back
    a success message. If a node in a cluster cannot acquire all locks, or
    fails otherwise, it sends back a failure message.</para>

    <para>The coordinator of the two-phase commit protocol waits for all
    responses (or a timeout, whichever occurs first). If one of the nodes in
    the cluster responds with FAIL (or we hit the timeout), then a rollback
    phase is initiated: a ROLLBACK message is sent to all nodes in the
    cluster. On reception of the ROLLBACK message, every node undoes the
    changes for the given transaction, and releases all locks held for the
    transaction.</para>

    <para>If all responses are OK, a COMMIT message is sent to all nodes in
    the cluster. On reception of a COMMIT message, each node applies the
    changes for the given transaction and releases all locks associated with
    the transaction.</para>

    <para>When we referred to 'transaction', we actually mean a global
    representation of a local transaction, which uniquely identifies a
    transaction across a cluster.</para>

    <section>
      <title>Example</title>

      <para>Let's look at an example of how to use JBoss Cache in a standalone (i.e.
      outside an application server) fashion with dummy transactions:</para>

      <programlisting>Properties prop = new Properties();
prop.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.cache.transaction.DummyContextFactory");
User Transaction tx=(UserTransaction)new InitialContext(prop).lookup("UserTransaction");
TreeCache tree = new TreeCache();
PropertyConfigurator config = new PropertyConfigurator();
config.configure(tree, "META-INF/replSync-service.xml");
tree.createService(); // not necessary
tree.startService(); // kick start tree cache

try {
   tx.begin();
   tree.put("/classes/cs-101", "description", "the basics");
   tree.put("/classes/cs-101", "teacher", "Ben");
   tx.commit();
}
catch(Throwable ex) {
   try { tx.rollback(); } catch(Throwable t) {}
}</programlisting>

      <para>The first lines obtain a user transaction using the 'JEE way' via
      JNDI. Note that we could also say</para>

      <para>
        <programlisting>UserTransaction tx = new DummyUserTransaction(DummyTransactionManager.getInstance());</programlisting>
      </para>

      <para>Then we create a new TreeCache and configure it using a
      PropertyConfigurator class and a configuration XML file (see below for a
      list of all configuration options).</para>

      <para>Next we start the cache. Then, we start a transaction (and
      associate it with the current thread internally). Any methods invoked on
      the cache will now be collected and only applied when the transaction is
      committed. In the above case, we create a node "/classes/cs-101" and add
      2 elements to its map. Assuming that the cache is configured to use
      synchronous replication, on transaction commit the modifications are
      replicated. If there is an exception in the methods (e.g. lock
      acquisition failed), or in the two-phase commit protocol applying the
      modifications to all nodes in the cluster, the transaction is rolled
      back.</para>
    </section>
  </section>
</chapter>