Jonas Transaction Fundamentals
Date: April 14, 1999
Author: Ph. Durieux
Translation: Christophe Ney
JTA version: JTA 1.0 Feb 25, 1999.
TOPICS
- Goal
- JTA interfaces in javax.transaction
- Transaction boundary demarcation by client or bean
- Transaction Manager Implementation
- Interface Transaction Implementation
- Interposition - SubCoordinator
- RMI Transaction Context Propagation
- Use of JTA in EJB Servers
- Pseudo driver JDBC-XA
- javax.sql package (JDBC2.0 ext)
- XA DataSource implementation
- Database Access
- Timers Management
- Recovery
- Support for Distributed Scenarios
- 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 wont 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 wouldnt 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:
- One hash-table to retrieve the Transaction object of a
given thread.
- 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 hasnt 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 wont 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 havent
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
hasnt 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
dont use real JDBC-XA drivers, and that may be
removed later on. For now, since we dont 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 shouldnt 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:
- 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.
- 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 wont 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.
|