Chapter 3. General Transaction Issues

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,
BasicList list);
};
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:
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 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 com.arjuna.TxCore.Atomic.TxStats class:
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:
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();

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 com.arjuna.ats.arjuna.coordinator.asyncCommit to 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: Independent Top-Level Action
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 save_state and 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.

3.1.8. Example

If we consider the Array example given previously, the set and get operations could be implemented as shown below.

Note

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();

    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;
}

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.