Title: Jonas Transaction Fundamentals

Jonas Transaction Fundamentals

Date: April 14, 1999
Author: Ph. Durieux
Translation: Christophe Ney
JTA version: JTA 1.0 – Feb 25, 1999.

TOPICS

  1. Goal
  2. JTA interfaces in javax.transaction
  3. Transaction boundary demarcation by client or bean
  4. Transaction Manager Implementation
  5. Interface Transaction Implementation
  6. Interposition - SubCoordinator
  7. RMI Transaction Context Propagation
  8. Use of JTA in EJB Servers
  9. Pseudo driver JDBC-XA
  10. javax.sql package (JDBC2.0 ext)
  11. XA DataSource implementation
  12. Database Access
  13. Timers Management
  14. Recovery
  15. Support for Distributed Scenarios
  16. Use of JTM for distributed transactions

Goal

  • Full implementation of the new JTA interface with support for JDBC-XA drivers when available.
  • Optimized implementation that maximize the use of local objects for local transactions (within one EJB server)
  • Support for all cases of distributed transactions with the use of the Jonas JTM and possible use of the UserTransaction within a client applet (through JNDI)

JTA interfaces in javax.transaction

See. Java Transaction API (JTA) specifications for more details

UserTransaction
Used by the client for transaction boundary demarcation or by a bean that explicitly manages its transactions (TX_BEAN_MANAGED). Major functions allow Begin, and Commit or Rollback, a few other minor functions are provided
Status
Defines possible status values (int).
TransactionManager
Interface used by the container to manage server transactions. Includes all UserTransaction methods and provide a few additional functions like suspend/resume or getTransaction. These 3 last handle Transaction objects (see below)
Transaction
Object that represents a transaction. Methods are commit/rollback, getStatus, and methods to associate XAResources or Synchronization objects to the transaction.
Synchronization
Synchronization object called before and after the commit/rollback phase.
xa.XAResource
Mapping of XOpen (start/end, prepare/commit/rollback, ...) XA interfaces.
See XA specifications
Distributed Transaction Processing: The XA Specification.

Transaction boundary demarcation by client or bean

Developers are supposed to be able to demarcate transaction boundaries using the UserTransaction interface. The access to the object implementing UserTransaction is not the same for a call from a bean (that uses the EJBContext interface) and for a call from an applet that uses JNDI.

The object implementing UserTransaction will be the same in the 2 cases for coherence reasons and to simplify the propagation of the transactional context.

The non-bean client uses JNDI to create the implementing object. A lookup call with a well-known name (javax.transation.UserTransaction) returns the appropriate implementation. This is usually the EJB server hosting the JTM that should register the UserTranscation object in JNDI. There is no need to get the TMFactory here, since we won’t use it from the client. (see below)

The bean that demarcates transaction boundaries uses the EJBContext interface to get the object implementing both UserTransaction and TransactionManager. With this unique object we get the same results for transaction demarcations done by the container and the bean.

It is important to use the same implementation of the TransactionManager in the container and interposition classes and for UserTransaction in bean-managed transactions. Otherwise incoherence would appear: There wouldn’t be a unique Transaction object to register XAResources objects.

Client and bean use the same implementation of the UserTransaction.

TransactionManager Implementation

The client as UserTransaction uses this implementation.

The Transaction Manager managed 2 hash-tables:

  1. One hash-table to retrieve the Transaction object of a given thread.
  2. One hash-table to retrieve the Transaction object of a given Xid.

Standard Interface Methods Analysis:

begin
Transaction object creation. Its associated PropagationContext has a valid Xid and a null remoteCoord (local transaction by default)
Transaction and Thread Association
Transaction and Xid Association
getTransaction
returns the Transaction object for the current thread.
commit
tx = getTransaction
tx.commit
Thread and Transaction no longer associated
Xid and Transaction no longer associated (when apply)
rollback
tx = getTransaction
tx.rollback
Thread and Transaction no longer associated
Xid and Transaction no longer associated (when apply)
getStatus
tx = getTransaction
tx.getStatus
setTransactionTimeout
set the default timeout value.
setRollbackOnly
tx = getTransaction
tx.setRollbackOnly
suspend
tx = getTransaction
Thread and Transaction no longer associated
return tx
resume
Associate the thread to the given transaction (argument)

Unlike what is suggested in the JTA specs, there is no enlist/delist of Resources done by the resume/suspend methods. This is simply done by the getConnection and close methods.

We have the need for 3 additional methods that are not part of the standard interface. We use indeed the setPropagationContext to setup the interposition mechanism during the first call of a remote bean. The 3 methods are listed below

getPropagationContext
Get the propagation context of the transaction associated with the current thread or null if there is no associated transaction for the current context.
tx = getTransaction
tx.getPropagationContext
setPropagationContext
Dissociate the current thread to any existing transaction for a null argument.
Return the Transaction object associated with the Xid contained in the given Propagation Context if available. Creates a Transaction object with this Propagation context if there is no existing Transaction object. We flag this object as a SubCoordinator, but we wait for its use in the transaction to start the interposition.
Associate Transaction and Xid
Propagation context update
Associate Transaction and Thread (= resume)
getCurrent
Get the current object implementing UserTransaction (for an applet) TransactionManager (for the container). Returns null for a client that hasn’t used the UserTransaction to demarcate transaction boundaries.

Interface Transaction Implementation

The transaction object is a local object that represents a transaction within a JVM. It is created either in an EJBServer, a client or an applet. In the case of an EJBServer, a SubCoordinator object (with remote interface) is added to do the server local 2PhaseCommit. For distributed transactions the SubCoordinator can be registered as a resource for the JTM (interposition mechanism described in OTS).

For efficiency reasons, we try to have only one transaction object per Xid and per EJBServer. However, we could have several threads using the same transaction (sometime the same transaction object?). The Xid/Transaction association hash-table managed by the Transaction Manager is used to do so.

The SubCoordinator object manages a XAResource list and a Synchronization list (JTA interfaces). So, we won’t use the local Control (JTM) object for this since it uses other interfaces (JTM proprietary, close to the JTS). The Design choice is to use the JTM only for transaction distributed among several JVM.

The Transaction object can be used in 4 different states depending on its possible association with a local coordinator (SubCoordinator), and with a possible remoteCoord (JTM ControlImpl).

In all cases, a Xid must exist to identify the transaction: we use it to associate the Transaction object with the Transaction represented by the Xid in the hash-table.

Initial State: localCoord = 0, remoteCoord = 0
This is the minimal implementation. We haven’t registered any resources or synchro, we are not connected to JTM.
The commit will be a passthrough in this case.
Local Coordinator: valid localCoord, remoteCoord = 0
We can manage some transactions locally, This is the usual case for transactions that occur in only one EJBServer.
The commit is done with the localCoord.
Proxy: localCoord = 0, valid remoteCoord
The Transaction object sends the 2PC to the JTM. This is the case of a client that wants to demarcate a distributed transaction.
The commit is redirected to the remoteCoord.
SubCoordinator: valid localCoord, valid remoteCoord
We can manage the branch of the local transaction locally but the global distributed transaction is managed by JTM. This is the standard way for a SubCoordinator as described in OTS. The localCoord is registered here as a resource for the remoteCoord.
The commit is redirected to the remoteControl. We should have a commit only for a bean that having started a transaction, has propagated the transaction to the bean of another EJBServer, making the transaction a distributed one. If the bean has not started the transaction, the commit is not valid since the principal coordinator must do it.
The Transaction object can change state during the life of the Transaction: A transaction will not have resources at first, then we will have enlistResources, it will not be distributed by default, then we will call another EJBServer, and so on

Short analysis of Transaction object methods:

getStatus
Returns the Transaction state (see Status interface) that is obtained locally for local Transaction and from the JTM for a distributed transaction.
registerSynchronization
add Synchronization to the Synchro list.
enlistResource
For a new ResourceFactory only: Add the XAResource to the resources list. Notice that we register only one XAResource by BD instance since we do only one prepare/commit.
Send "Start" to the XAResource.
delistResource
Send "End" with the flag as argument to the XAResource
commit
If we have a valid remoteCoord, this is a distributed transaction and we delegate the 2-phase-commit to this remoteCoord (JTM). In the other case, we call the one-phase commit of the localCoord, which is equivalent to a local 2-phase commit in the server.
rollback
If we have a valid remoteCoord, this is a distributed transaction and we ask this remoteCoord to do the rollback of the transaction. In the other case, we simply call rollback of the localCoord object.
setRollbackOnly
Add a flag rollback_only in the Transaction object.

The following methods are needed for internal use, (see above):

getPropagationContext
Returns the PropagationContext associated with this transaction.
setPropagationContext
Update of the transaction propagation context. For example in the case of a local transaction that became distributed because of a remote call.
This should be used when creating the Transaction Object since the propagation context is updated later in the object constructor.

Interposition - SubCoordinator

The management of the transaction itself, (i.e. the 2 phase commit is delegated to a SubCoordinator object which in the case of a distributed transaction can be accessed remotely by the JTM as a resource (JTM interface). This is the interposition mechanism described in the OTS specifications and used in the OrbTP.

In the case of pure local transactions, the JTM is not involved and the SubCoordinator object is if fact a regular coordinator, as the JTM Control is, but the JTM control manages Synchronization objects and XAResource defined in the JTA interface.

Analysis of the Resource interface implementing methods:

prepare
Returns vote rollback, if the transaction is a rollback_only.
Send beforeCompletion to registered Synchronizations
Send prepare to XAResource and get all results
Return the resulting vote.
commit
send commit to XAResources
send afterCompletion(OK) to Synchronizations
rollback
send rollback to XAResources
send afterCompletion(KO) to Synchronizations
commit_one_phase
prepare (optimize for 1 XAResource only...)
if OK commit, else rollback
forget
call forget on XAResources

RMI Transaction Context Propagation

The PropagationContext contains 3 fields:

Control
The JTM object that controls the distributed transaction. Equals to null when the JTM is not involved in the transaction (yet), i.e. the transaction is local to the server.
Xid
Transaction identifier (unique).
Timeout
Transaction timeout

The PropagationContext is automatically passed during the rmi calls (see rmi patch in make rules)

The retrieval of the Propagation Context is done in the RemoteStub and Skeleton classes (com.bull.jtm.util.)

In both server and client we do a Current.getCurrent() to check that there is a Current object in the process. So, we do need access to the Current class in the client. If the client hasn’t retrieved a UserTransaction (through JNDI) we will load the class Current without a corresponding instance and getCurrent will return null.

Invoke a remote method:

In the stub we do the following:

  • If there is no Current object, there is no transaction we pass null.
  • If there is a Current object: getPropagationContext
  • Add this context to the list of arguments
  • Call the remote method
  • Get the PropagationContext in the arguments
  • setPropagationContext

In the skeleton:

We do the following:

  • get PropagationContext in the arguments
  • setPropagationContext
  • call the method (the interposition class in fact)
  • getPropagationContext
  • call this context in the argument list

To cover the case of a bean calling another bean from a different JVM, we also have to return a Control object

Use of JTA in EJBServers

Each EJBServer has its own unique implementation of TransactionManager (Current). Each client that is not an EJBServer will have an identical Current retrieved with JNDI that it sees as a UserTransaction.

Optionally (properties or start-up arguments) an EJBServer will host a JTM. There will be 1 JTM per host, because of the rmiregistry (1 per host too). If several EJBServer register to the naming service, this is the last one that is used (there is a rebind). Each EJBServer will have a reference to a local or remote TMFactory depending on the case (stored in the Current object). An EJBServer mainly uses the TransactionManager interface to manage the association between Transaction and threads/beans

JDBC-XA Pseudo driver

To fully implement the JTA interface, we must have JDBC-XA drivers that implement XAResources and are registered with the Transaction objects. Such a driver is not available today for Oracle or Instantdb. So, we have to write a pseudo driver JDBC-XA that wraps the standard driver.

JDBC-XA specifications and user guide can be found in the: JDBC 2.0 Standard Extension API documents.

A JDBC-XA driver implements the following interface:

XADataSource
This is the XAConnection object factory.
Inherit of ConnectionPooledDataSource: This XADataSource is a ConnectionPooledDataSource that also managed XAResources.
Mainly implements a getXAConnection() method.
XAConnection
This is a PooledConnection plus a XAResource.
This encapsulates 2 objects implementing the Connection and XAResource interfaces.
Connection
This is the Connection object used by the developer (or container) to make SQL request. The difference with the physical connection is the close method. Instead of closing the connection, the close method raises an event to the connection manager. (see next pages). The other methods or identical to those of the physical connection.
XAResource
This XAResource is registered to the Transaction object. It implements the standard XA interface (start, end, commit, prepare, rollback, ...)

Note: this pseudo driver being written on top of a standard driver does not handle the 2PC or the XA interface in general. There is only a minimum set of controls that are made at the Xid levels. This is said, we return an OK, and a commit will indeed call the standard driver commit.

It is written in the JDBC Std Ext spec that the XADataSource must be registered with JNDI. This has not been done since we did see the use (to study) The only DataSource registered with JNDI, is the "standard" DataSource that use this driver and manage a pool of XAConnections. This is the one that the bean (or container) must use.

javax.sql (JDBC Std Ext. 2.0) package

We need javax.sql for the interface detailed below and for the ConnectionEvent class. There is a coherency issue in the current version since it uses javax.jts (instead of javax.transaction). We did have to patch the source to solve this.

Among all interfaces defined in the package we use the 4 following interface.

DataSource
Connections Factory.
XADataSource
For JDBC-XA drivers (see above)
XAConnection
For JDBC-XA drivers (see above)
ConnectionEventListener
Interface implemented by objects that want to receive an event when the driver JDBC-XA connection is closed.

This package also contains the following class:

ConnectionEvent
Event sends to the ConnectionEventListener.

Implémentation of a DataSource for XA

An EJB platform must implement a DataSource dedicated to JDBC-XA drivers management. All persistence operations must go through this DataSource if we want to respect the JTA norm for the use of XAResources.

This DataSource must managed a pool of XAConnections to avoid open and close of physical connection (very costly). The implementation of a Connection pool is described in the JDBC 2.0 Std Ext document. We detail only platform specific points here.

Constructor
The constructor of the DataSource builds the underlying XADataSource used by the DataSource
Properties
A DataSource has a number of properties including the list given in the JDBC 2.0 Std Ext spec. We have defined other properties that we need for our platform.
We should study if we could use standard properties instead of our proprietary ones
As today, defined properties are:
dataSourceName JNDI name of the DataSource
url L'URL of the instance (for the XADataSource)
ClassName Standard JDBC driver (for XADataSource)
UserName Default user (for the XADataSource)
Password Default password (for the XADataSource)
getConnection
The principal method of the DataSource interface is getConnection. It is used by the developer to get a JDBC connection.
The DataSource manage a pool of free connections. When a connection is requested it starts looking into the pool before asking the XADataSource for a new connection. A connection is always dedicated to a user. It is not possible to close a connection opened by another user. In our implementation, we associate the transaction to the connection so that we return always the same connection within a given transaction. This is a design choice.
This choice seems to be needed as long as we don’t use real JDBC-XA drivers, and that may be removed later on. For now, since we don’t have real 2PC on XAResources, it is better to have a minimum set of XAConnections per transaction (to minimize incoherence). In most of the cases we will have only one connection and the commit will be a one_phase commit in the database. This works today!
If we are in a transaction, we need to do a enlistResource to the Transaction object, which call a "xa.start" on the resource. If it is the first call on this DataSource, we also have to "register resource" on the local coordinator.
ConnectionEventListener
The DataSource, as a Connection manager, register itself as a ConnectionEventListener. It will be notified of a connection close call. In fact, when a closeEvent event is received, the connection is not put back into the pool of free connections (not as specified in the JDBC 2.0 std ext specs)., but the Transaction object is notified and can perform its "xa.end". It uses the delistResource for this purpose.
When to free XAConnections
A connection can be effectively freed when the transaction is done. This is the coordinator associated to the Transaction the has to decide when to free the XAConnections. We added a the "cleanTransaction(tx)" method to the implementation of DataSource to allow this operation for all corresponding XAResource objects.
Access to DataSource without transaction
In case of access to a datasource outside any transaction, the setAutoCommit(true) is called by the container and each jdbc request is considered by the DB as a mini transaction, local to the database. Since no global transaction is known by the container, the jdbc connection is returned to the pool at close time

Database Access

The database access is done by the container (implicit persistency) or by the bean itself (explicit persistency). In the 2 cases, the same rules must be used for this to work.

Get the DataSource through JNDI
We use JNDI in the EJBServer to build the DataSource object and get the instance of the logical database.
Access to the Database
The schema to for database access is:
  • getConnection
  • SQL request on the connection
  • Connection close (Must be done)
  • Transactions Demarcation
    A connection must be close before a commit (or rollback) is called on the corresponding Transaction (begin must be call before the getConnection). Breaking this protocol generates errors when calling close or commit.

    Timer Management

    Each Transaction object as an associated timer that automatically rollback the transaction when expiring. The timer is transmitted with the propagation context, this propagates it among EJBServers. In the case of distributed transactions, the timer is managed by the JTM and there is no need to create a local timer is that case. In all case the timer shouldn’t do the rollback, but it should call the setRollbackOnly.

    The timer manager is part of an EJBServer and is used for the JTM too when hosted by the EJBServer. It will be in a standalone package.

    The TimerManager is an object that starts 2 threads:

    1. clock: decreases every second the value of all timer registered in a list. If a timer has expired, it is moved to a to-do list and the other thread is notified of a to-do job.
    2. batch: handle in arriving order the to-do list of expired timers.

    The choice of using 2 thread instead of one has been done so that the clock won’t be affected by a long treatment of an expired timer.

    The TimerManager interface is very simple:

    TimerEvent = addTimer(TimerEventListener, timeout, arg)

    removeTimer(TimerEvent)

    Recovery

    TBD

    Supported distribution Scenarios

    All possible scenarios are supported:

    • Local only: We have only one EJB server that contains all beans and the client does not demarcate transaction boundaries. In this case there is only one Transaction object (for each transaction) and the 2PC is done locally. The JTM is not used even though we might have several databases.
    • Several EJBServers: All beans from the same transaction live in several EJBServers. We have one Transaction object for each EJBServer that will act as a SubCoordinator with the principal coordinator managed by the JTM. We have here a distributed transaction.
    • Client that demarcates the transaction: Usually, the client wants to access several EJBServers contributing to the same transaction. This case is similar to the previous one, it is a distributed transaction.

    In addition, we must consider the case of both implicit and explicit persistence, and that the session beans might inherit from SessionSynchronization or not, and that beans can have different kind of transactional attributes in their descriptor (TX_BEAN_MANAGED, TX_SUPPORTS, TX_REQUIRED, ...). A lot of possible combinations!

     

    Use of JTM for distributed transactions

    The JTM is mainly a TMFactory registered in JNDI that manage a set of ControlImpl objects. Each ControlImpl object manage a distributed transaction and implements the following OTS interfaces:

    • Control
    • Coordinator
    • RecoveryCoordinator
    • Terminator
    • Resource (for SubCoordinator objects)

    Timers are those of the EJBServer (timer package)

    The decision of converting a local transaction into a distributed one is always made by one EJBServer. In fact, a transaction is distributed when a 2nd bean is involved, or when a client has demarcated a transaction and calls a bean. In both cases we are in the EJBServer and the client (applet or application) does not need to know about the JTM.

    Each EJBServer must be attached to a TMFactory that it calls to distribute transactions. This factory is either local or remote depending on the value of the bullejb.tm.remote property (true/false). In the remote case, we use JNDI to retrieve the TMFactory, in the local case the TMFactory is created and registered to JNDI. In this later, we register also a Current object to provide visibility on the UserTransaction to the client that uses it.

    In the case of several EJBServers with a local JTM for each one. This is the last registrant that erases previous ones, those can be used by the server where they are hosted, but only the one registered by JNDI is used by EJBServers without local JTM.