3.1. Advanced transaction issues with TxCore
Atomic actions (transactions) can be used by both application programmers and class developers. Thus entire operations (or parts of operations) can be made atomic as required by the semantics of a particular operation. This chapter will describe some of the more subtle issues involved with using transactions in general and TxCore in particular.
3.1.1. Checking transactions
In a multi-threaded application, multiple threads may be associated with a transaction during its lifetime, i.e., the thread’s share the context. In addition, it is possible that if one thread terminates a transaction other threads may still be active within it. In a distributed environment, it can be difficult to guarantee that all threads have finished with a transaction when it is terminated. By default, TxCore will issue a warning if a thread terminates a transaction when other threads are still active within it; however, it will allow the transaction termination to continue. Other solutions to this problem are possible, e.g., blocking the thread which is terminating the transaction until all other threads have disassociated themselves from the transaction context. Therefore, TxCore provides the
com.arjuna.ats.arjuna.coordinator.CheckedAction class, which allows the thread/transaction termination policy to be overridden. Each transaction has an instance of this class associated with it, and application programmers can provide their own implementations on a per transaction basis.
public class CheckedAction
public CheckedAction ();
public synchronized void check (boolean isCommit, Uid actUid,
When a thread attempts to terminate the transaction and there are active threads within it, the system will invoke the check method on the transaction’s
CheckedAction object. The parameters to the check method are:
Indicates whether the transaction is in the process of committing or rolling back.
The transaction identifier.
a list of all of the threads currently marked as active within this transaction.
When check returns, the transaction termination will continue. Obviously the state of the transaction at this point may be different from that when check was called, e.g., the transaction may subsequently have been committed.
3.1.2. Statistics gathering
By default, the JBossTS does not maintain any history information about transactions. However, by setting the
com.arjuna.ats.arjuna.coordinator.enableStatistics property variable to YES, the transaction service will maintain information about the number of transactions created, and their outcomes. This information can be obtained during the execution of a transactional application via the
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 ();
3.1.3. Last resource commit optimisation
In some cases it may be necessary to enlist participants that aren’t two-phase commit aware into a two-phase commit transaction. If there is only a single resource then there is no need for two-phase commit. However, what if there are multiple resources in the transaction? In this case, the Last Resource Commit optimization (LRCO) comes into play. It is possible for a single resource that is one-phase aware (i.e., can only commit or roll back, with no prepare), to be enlisted in a transaction with two-phase commit aware resources. The coordinator treats the one-phase aware resource slightly differently, in that it executes the prepare phase on all other resource first, and if it then intends to commit the transaction it passes control to the one-phase aware resource. If it commits, then the coordinator logs the decision to commit and attempts to commit the other resources as well.
In order to utilise the LRCO, your participant must implement the
com.arjuna.ats.arjuna.coordinator.OnePhase interface and be registered with the transaction through the
BasicAction.add operation; since this operation expects instances of
AbstractRecord, you must create an instance
com.arjuna.ats.arjuna.LastResourceRecord and give your participant as the constructor parameter, as shown below:
boolean success = false;
AtomicAction A = new AtomicAction();
OnePhase opRes = new OnePhase(); // used OnePhase interface
System.err.println("Starting top-level action.");
3.1.4. Nested transactions
There are no special constructs for nesting of transactions: if an action is begun while another action is running then it is automatically nested. This allows for a modular structure to applications, whereby objects can be implemented using atomic actions within their operations without the application programmer having to worry about the applications which use them, i.e., whether or not the applications will use atomic actions as well. Thus, in some applications actions may be top-level, whereas in others they may be nested. Objects written in this way can then be shared between application programmers, and TxCore will guarantee their consistency.
If a nested action is aborted then all of its work will be undone, although strict two-phase locking means that any locks it may have obtained will be retained until the top-level action commits or aborts. If a nested action commits then the work it has performed will only be committed by the system if the top-level action commits; if the top-level action aborts then all of the work will be undone.
The committing or aborting of a nested action does not automatically affect the outcome of the action within which it is nested. This is application dependant, and allows a programmer to structure atomic actions to contain faults, undo work, etc.
3.1.5. Asynchronously committing a transaction
By default, JBossTS executes the commit protocol of a top-level transaction in a synchronous manner, i.e., all registered resources will be told to prepare in order by a single thread, and then they will be told to commit or rollback. This has several possible disadvantages:
In the case of many registered resources, the prepare operating can 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, possibly many prepare operations will have been made needlessly.
In the case where heuristic reporting is not required by the application, the second phase of the commit protocol can be done asynchronously, since its success or failure is not important.
Therefore, JBossTS provides runtime options to enable possible threading optimizations. By setting the
com.arjuna.ats.arjuna.coordinator.asyncPrepare environment variable to
YES, during the prepare phase a separate thread will be created for each registered participant within the transaction. By setting
YES, a separate thread will be created to complete the second phase of the transaction if knowledge about heuristics outcomes is not required.
3.1.6. Independent top-level transactions
In addition to normal top-level and nested atomic actions TxCore also supports independent top-level actions, which can be used to relax strict serialisability in a controlled manner. An independent top-level action can be executed from anywhere within another atomic action and behaves exactly like a normal top-level action, that is, its results are made permanent when it commits and will not be undone if any of the actions within which it was originally nested abort.
Figure 5 shows a typical nesting of atomic actions, where action B is nested within action A. Although atomic action C is logically nested within action B (it had its Begin operation invoked while B was active) because it is an independent top-level action, it will commit or abort independently of the other actions within the structure. Because of the nature of independent top-level actions they should be used with caution and only in situations where their use has been carefully examined.
Top-level actions can be used within an application by declaring and using instances of the class
TopLevelTransaction. They are used in exactly the same way as other transactions.
3.1.7. Transactions within save_state and restore_state
Caution must be exercised when writing the
restore_state operations to ensure that no atomic actions are started (either explicitly in the operation or implicitly through use of some other operation). This restriction arises due to the fact that TxCore may invoke
restore_state as part of its commit processing resulting in the attempt to execute an atomic action during the commit or abort phase of another action. This might violate the atomicity properties of the action being committed (aborted) and is thus discouraged.
If we consider the Array example given previously, the set and get operations could be implemented as shown below.
This is a simplification of the code, ignoring error conditions and exceptions.
public boolean set (int index, int value)
boolean result = false;
AtomicAction A = new AtomicAction();
// 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;
result = true;
public int get (int index) // assume -1 means error
AtomicAction A = new AtomicAction();
// We only need a READ lock as the state is unchanged.
if (setlock(new Lock(LockMode.READ), 0) == LockResult.GRANTED)
3.1.9. Garbage collecting objects
Java objects are deleted when the garbage collector determines that they are no longer required. Deleting an object that is currently under the control of a transaction must be approached with caution since if the object is being manipulated within a transaction its fate is effectively determined by the transaction. Therefore, regardless of the references to a transactional object maintained by an application, TxCore will always retain its own references to ensure that the object is not garbage collected until after any transaction has terminated.
3.1.10. Transaction timeouts
By default transactions live until they are terminated by the application that created them or a failure occurs. However, it is possible to set a timeout (in seconds) on a per transaction basis such that if the transaction has not terminated before the timeout expires it will be automatically rolled back.
In TxCore, the timeout value is provided as a parameter to the
AtomicAction constructor. If a value of
AtomicAction.NO_TIMEOUT is provided (the default) then the transaction will not be automatically timed out. Any other positive value is assumed to the timeout for the transaction (in seconds). A value of zero is taken to be a global default timeout, which can be provided by the property
com.arjuna.ats.arjuna.coordinator.defaultTimeout. Unless changed the default value is 60 seconds.
When a top-level transaction is created with a non-zero timeout, it is subject to being rolled back if it has not completed within the specified number of seconds. JBossTS uses a separate reaper thread which monitors all locally created transactions, and forces them to roll back if their timeouts elapse. To prevent this thread from consuming application time, it only runs periodically. The default checking period is 120000 milliseconds, but can be overridden by setting the
com.arjuna.ats.arjuna.coordinator.txReaperTimeout property variable to another valid value, in microseconds. Alternatively, if the
com.arjuna.ats.arjuna.coordinator.txReaperMode is set to
DYNAMIC, the transaction reaper will wake whenever a transaction times out. This has the advantage of terminating transactions early, but may suffer from continually rescheduling the reaper thread.
If a value of 0 is specified for the timeout of a top-level transaction (or no timeout is specified), then JBossTS will not impose any timeout on the transaction, i.e., it will be allowed to run indefinitely. This default timeout can be overridden by setting the
com.arjuna.ats.arjuna.coordinator.defaultTimeout property variable when using ArjunaCore or ArjunaJTS, or
com.arjuna.ats.jts.defaultTimeout if using ArjunaJTS, to the required timeout value in seconds.