Chapter 11. General Transaction Issues

11.1. Advanced Transaction Issues with JBoss Transaction Service

Transactions are used by both application programmers and class developers. You can make entire operations, or parts of operations, transactional. This chapter covers some of the more subtle issues involved with using transactions in general and, some particulars about JBoss Transaction Service.

11.1.1. Checking Transactions

In a multi-threaded application, multiple threads may be associated with a transaction during its lifetime, sharing the same context. Also, if one thread terminates a transaction, other threads may still be active within it. In a distributed environment, it is difficult to guarantee that no threads need a transaction when it is terminated. By default, JBoss Transaction Service issues a warning if a thread terminates a transaction when other threads are using it. However, it still allows the transaction to be terminated. Another possible behavior is to block terminating thread until all other threads have disassociated themselves from the transaction context. Therefore, JBoss Transaction Service provides the com.arjuna.ats.arjuna.coordinator.CheckedAction class, which allows you to override the thread/transaction termination policy. Each transaction has an instance of this class associated with it, and you can provide your own implementations on a per-transaction basis.
public class CheckedAction
{
    public CheckedAction ();

    public synchronized void check (boolean isCommit, Uid actUid,
				    BasicList list);
};
When a thread tries to terminate a transaction and there are active threads within it, the system invokes the check method on the transaction’s CheckedAction object. The parameters to the check method are:
isCommit
Indicates whether the transaction is in the process of committing or rolling back.
actUid
The transaction identifier.
list
A list of all of the threads currently active within this transaction.
When the check method returns, the transaction is terminated. The state of the transaction may have changed from when the check method was called.

11.1.2. Gathering Statistics

By default, the JBoss Transaction Service does not maintain any historical information about transactions. You can turn on history tracking by setting the com.arjuna.ats.arjuna.coordinator.enableStatistics property variable to YES. This information includes the number of transactions created, and the outcomes of the transactions. You can request this information during the execution of a transactional application via the com.arjuna.TxCore.Atomic.TxStats class, as in Example 11.1, “Transaction Statistics”.

Example 11.1. Transaction Statistics

public class TxStats
{

    // Returns the number of transactions (top-level and nested)
    // created so far.

    public static int numberOfTransactions ();

    // Returns the number of nested (sub) transactions created so far.

    public static int numberOfNestedTransactions ();

    // Returns the number of transactions which have terminated with
    // heuristic outcomes.

    public static int numberOfHeuristics ();

    // Returns the number of committed transactions.

    public static int numberOfCommittedTransactions ();

    // Returns the number of transactions which have rolled back.

    public static int numberOfAbortedTransactions ();

}

11.1.3. Last resource commit optimization

In some cases, you may need enlist participants that are not two-phase commit aware into a two-phase commit transaction. If there is only a single resource, you do not need two-phase commit.If the transaction contains multiple transactions, the Last Resource Commit Optimization (LRCO) becomes relevant. A single resource that is one-phase aware can only commit or roll back, with no prepare phase. Such a resource may be enlisted in a transaction with two-phase commit aware resources. The coordinator treats the one-phase aware resource slightly differently, by executing the prepare phase on all other resource first. Then,the one-phase aware transaction needs to commit the transaction, the coordinator passes control to it. If it commits, the coordinator logs the decision to commit and attempts to commit the other resources as well.
To use the LRCO, your participant must implement the com.arjuna.ats.arjuna.coordinator.OnePhase interface and be registered with the transaction through the BasicAction.add method. Since this operation expects instances of AbstractRecord, you need to create an instance of the com.arjuna.ats.arjuna.LastResourceRecord class and pass your participant as the constructor parameter, as shown in the Example 11.2, “BasicAction.add Example”.

Example 11.2. BasicAction.add Example

try
    {
	boolean success = false;
	AtomicAction A = new AtomicAction();
	OnePhase opRes = new OnePhase();  // used OnePhase interface
      
	System.err.println("Starting top-level action.");
      
	A.begin();
	A.add(new LastResourceRecord(opRes));
	A.add(new ShutdownRecord(ShutdownRecord.FAIL_IN_PREPARE));
      
	A.commit();
    }

11.1.4. Nested Transactions

You do not need to do anything special to nest transactions. If an action is begun while another action is running, it is automatically nested. This provides a modular structure to applications,meaning that the programmer of the object does not need to be concerned about whether the applications to use the object are also transactional.
If a nested action is aborted then all of its work is rolled back. However, strict two-phase locking means that any locks the terminating transaction holds are retained until the top-level action commits or aborts. If a nested action commits, the work it has performed is only committed by the system if the top-level action commits. If the top-level action aborts, all the work is rolled back.
Committing or aborting a nested action does not automatically affect the outcome of its parent action. You can choose to implement this behavior programmatically, controlling the way faults are contained or work is undone, for instance.

11.1.5. Asynchronously Committing a Transaction

By default, JBoss Transaction Service executes the commit protocol of a top-level transaction in a synchronous, single-threaded manner. All registered resources are directed to prepare in order by a single thread, and then to commit or rollback. This has several possible disadvantages.
  • Often, the prepare operating is logically be invoked in parallel on each resource. The disadvantage is that if an early resource in the list of registered resource forces a rollback during prepare, many unnecessary prepare operations may have been performed.
  • If your application does not need heuristic reporting, the second phase of the commit protocol can be called asynchronously, since its success or failure is not important.
Therefore, JBoss Transaction Service provides runtime options to enable two threading optimizations, by setting specific variables.
com.arjuna.ats.arjuna.coordinator.asyncPrepare
If set to YES, a separate thread is created during the prepare phase, for each registered participant within the transaction.
com.arjuna.ats.arjuna.coordinator.asyncCommit
If set to YES, a separate thread is created to complete the second phase of the transaction if you do not need information about heuristics outcomes.

11.1.6. Independent Top-Level Transactions

In addition to normal top-level and nested transactions, JBoss Transaction Service also supports independent top-level actions, which you can use to relax strict serializability in a controlled way. You can execute an independent top-level action from anywhere within another transaction, and it behaves exactly like a normal top-level action. Its results are made permanent when it commits and are not undone if any of its parent actions abort.
Independent Top-Level Actions

Figure 11.1. Independent Top-Level Actions

Figure 11.1, “Independent Top-Level Actions” shows a typical nesting of transactions, where action B is nested within action A. Transaction C is logically nested within action B, because its Begin operation is invoked while B is active. However, because it is an independent top-level action, it commits or aborts independently of the other actions within the structure. Because of the nature of independent top-level actions, use them with caution and only after careful testing and observation.
You can use top-level actions within an application by declaring and using instances of the TopLevelTransaction class, and using them in exactly the same way as other transactions.

11.1.7. Transactions Within the save_state and restore_state Methods

Exercise caution when you write the save_state and restore_state methods. Make sure not to start any atomic actions, either explicitly in the method, or implicitly through use of some other operation. The reason for this caution is that JBoss Transaction Service may invoke the restore_state method when it commits, resulting in an attempt to execute a transaction during the commit or abort phase of another action. This might violate the atomicity properties of the action being committed or aborted, so it is strongly discouraged.

11.1.8. Example

The code in Example 11.3, “Nested Transaction Example” is based on the Example 10.7, “Saving and Restoring an Object's State” example presented earlier, and implements the set and get methods. The code is simplified, and ignores error conditions and exceptions.

Example 11.3. Nested Transaction Example

public boolean set (int index, int value)
{
    boolean result = false;
    AtomicAction A = new AtomicAction();

    A.begin();

    // We need to set a WRITE lock as we want to modify the state.

    if (setlock(new Lock(LockMode.WRITE), 0) == LockResult.GRANTED)
	{
	    elements[index] = value;
	    if ((value > 0) && (index > highestIndex))
		highestIndex = index;
	    A.commit(true);
	    result = true;
	}
    else
	A.rollback();

    return result;
}

public int get (int index)  // assume -1 means error
{
    AtomicAction A = new AtomicAction();

    A.begin();

    // We only need a READ lock as the state is unchanged.

    if (setlock(new Lock(LockMode.READ), 0) == LockResult.GRANTED)
	{
	    A.commit(true);

	    return elements[index];
	}
    else
	A.rollback();

    return -1;
}

11.1.9. Garbage Collecting Objects

Java objects are deleted by the garbage collector when they are no longer needed. Caution must be used when deleting an object that is currently under the control of a transaction. If the object is being manipulated within a transaction, the transaction controls what happens to it. Therefore, regardless of the references to a transactional object maintained by an application, JBoss Transaction Service always retains its own references to make sure that the object is not deleted by the garbage collector until all involved transactions have terminated.

11.1.10. Transaction Timeouts

By default, transactions live until the application which created them deletes them, or a failure occurs. However, you can to set a timeout on a per-transaction basis, causing transactions which do not terminate before the timeout expires to be rolled back. The timeout value is expressed in seconds.
In JBoss Transaction Service, the timeout value is provided as a parameter to the AtomicAction constructor. If the default value of AtomicAction.NO_TIMEOUT is provided, the transaction is never automatically timed out. Any other positive value represents the number of seconds to wait for the transaction to terminate. A value of 0 is interpreted as a global default timeout, which you can provide using the property com.arjuna.ats.arjuna.coordinator.defaultTimeout. This property's default value is 60, or one minute.
When a top-level transaction is created with a non-zero timeout, it will be rolled back if it times out. JBossTS uses a separate reaper thread to monitor all locally created transactions, forcing them to roll back if their timeouts elapse. To prevent the reaper thread from consuming application time, it only runs periodically. The default checking period is 120000 milliseconds, but you can override it by setting the com.arjuna.ats.arjuna.coordinator.txReaperTimeout property to another value, in microseconds. Alternatively, if the com.arjuna.ats.arjuna.coordinator.txReaperMode property is set to DYNAMIC, the transaction reaper wakes up whenever a transaction times out. This has the advantage of terminating transactions early, but may continually reschedule the reaper thread.
If the timeout value is 0 for a top-level transaction, or no timeout is specified, JBoss Transaction Service does not impose any timeout on the transaction, and it is allowed to run indefinitely. You can override this default timeout by setting the com.arjuna.ats.arjuna.coordinator.defaultTimeout property.