Testing XA rollback…or not

In our last episode, we got our XA example working… we thought.  But after discussing it with Keith, he said, let’s take out the XA-enabled transaction manager and see something be different when the rollback happens.

Nothing was different — both transactions still rolled back when the messagelistener threw a RuntimeException.

Why My Test Didn’t Conclusively Demonstrate an XA Rollback

It took us a minute to realize why nothing changed, but the reason is this:

Our test always throws the exception before either the JMS message receive or the database write commits.

The whole help of XA transactions is so you don’t have a situation where you’ve already committed changes to one resource and then find yourself needing to roll back changes to the other resource.

So what I want to do is see about throwing an exception after one resource commits* but before the other one does.  I would expect the output of that to be different with XA enabled vs. disabled.

*(phase 1 of the commit anyway)

How to Get At the Transaction Guts in Spring

Currently, Spring is handling all of the JTA/XA stuff behind the scenes, and we just have this line in our Spring beans file:

<tx:jta-transaction-manager />

To be able to do the test of which we speak, we need to use a lower-level transaction strategy, methinks.

Spring 2.5 Manual Section 9.6. Programmatic transaction management starts out like this:

The Spring Framework provides two means of programmatic transaction management:

  • Using the TransactionTemplate.
  • Using a PlatformTransactionManager implementation directly.

TransactionTemplate: Apparently not what we need

Looking at that section of the Spring manual, it looks if we use the TransactionTemplate , all we get is a doInTransaction method to implement.  This is not enough… we need the commit to start happening and then go wrong.

The PlatformTransactionManager, then?

The TransactionTemplate class’s execute() method eventually calls this.transactionManager.commit(), and that that transactionManager is a PlatformTransactionManager (in our case, transactionManager will specifically be an instance of JtaTransactionManager, which implements the PlatformTransactionManager interface).

JtaTransactionManager extends AbstractPlatformTransactionManager, and there’s quite a bit of interesting code in the latter class.  At the bottom of the commit method, once it is determined that none of the possible scenarios requiring rollback have occurred, a private processCommit method is called which eventually calls the doCommit that is overridden by JtaTransactionManager.  (I think I’m following myself so far, but what will I think when I look back on these paragraphs a few months from now? ;)

I guess I’m looking for where it’s looping through all the resources that are participating in the global transaction, asking them each if they’re ready and then committing them.  Is it in JtaTransactionManager’s doCommit?

Deeper Still

It’s not directly in there: eventually, doCommit calls txObject.getUserTransaction().commit() (getUserTransaction returns a javax.transaction.UserTransaction).  So it’s apparently the UserTransaction where the two-phase commit happens.  Yipes, I’d have to fiddle with the UserTransaction class that’s in JBoss’s server\default\lib directory inside the jboss-j2ee.jar?  Hmmm… that won’t happen this afternoon, and this afternoon’s all I have left for this.

Puntage

Failing actually showing the XA rollback working differently than a non-XA rollback, let’s see if we can get elevate our confidence level that we’re actually seeing an XA rollback by running our test in XA-enabled-we-hope mode, changing the configuration to local JMS and Hibernate transactions, re-running the test and seeing if the JBoss server log looks any different between these two runs.

We’ll do the checking this way:

  1. Go to a command prompt in JBoss’s server\default\log directory
  2. tail -f server.log > filename.log (e.g. xa.log)
  3. Use soapUI to send message containing “boom” to the Web service
  4. Ctrl+C to stop capturing the server logging.

We’ll test with the transaction Spring beans this way first, for XA rollback:


        <tx:jta-transaction-manager />
<!--    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">-->
<!--
<property name="sessionFactory" ref="sessionFactory" />-->
<!--    </bean>-->
        <jms:listener-container transaction-manager="transactionManager" connection-factory="queueConnectionFactory"
            acknowledge="transacted">
            <jms:listener destination="myTest.Queue" ref="messageProcessor" method="processIt" />
        </jms:listener-container>
<!--    <jms:listener-container connection-factory="queueConnectionFactory" acknowledge="transacted">-->
<!--        <jms:listener destination="${com.ontsys.xa.example.queue.name}" ref="messageProcessor" method="processIt" />-->
<!--    </jms:listener-container>-->

…and then this way for local transaction rollback:


<!--        <tx:jta-transaction-manager />-->
    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
    </bean>
<!--        <jms:listener-container transaction-manager="transactionManager" connection-factory="queueConnectionFactory"-->
<!--            acknowledge="transacted">-->
<!--            <jms:listener destination="myTest.Queue" ref="messageProcessor" method="processIt" />-->
<!--        </jms:listener-container>-->

The logs look like this:

Server Log: XA Rollback

xa-rollback-jboss-server-log

Server Log: Local Transaction Rollback

local-rollback-jboss-server-log.pdf

Analysis of the Logs

Notice how…

  • The XA rollback resulted in almost twice the log file output of the local transaction rollback
  • Strings with Jta are in there all over the place in the XA rollback scenario log, but not in the local transaction rollback scenario log.

Also, in the XA log, 4 out of 7 places where the words “rolling” or “rollback” occur (lines 5, 6, 11, and 15), it’s a JtaTransactionManager talking:

...
2008-08-01 15:44:29,897 DEBUG [org.springframework.transaction.interceptor.RuleBasedTransactionAttribute] Applying rules to determine whether transaction should rollback on java.lang.RuntimeException: Boom: boom
2008-08-01 15:44:29,897 DEBUG [org.springframework.transaction.interceptor.RuleBasedTransactionAttribute] Winning rollback rule is: null
2008-08-01 15:44:29,897 DEBUG [org.springframework.transaction.interceptor.RuleBasedTransactionAttribute] No relevant rollback rule found: applying default rules
2008-08-01 15:44:29,897 DEBUG [org.springframework.transaction.jta.JtaTransactionManager] Participating transaction failed - marking existing transaction as rollback-only
2008-08-01 15:44:29,897 DEBUG [org.springframework.transaction.jta.JtaTransactionManager] Setting JTA transaction rollback-only
2008-08-01 15:44:29,897 DEBUG [org.springframework.transaction.support.TransactionSynchronizationManager] Retrieved value [org.springframework.jms.connection.JmsResourceHolder@693a5] for key [org.apache.activemq.ra.ActiveMQConnectionFactory@88e83d] bound to thread [org.springframework.jms.listener.DefaultMessageListenerContainer#0-1]
2008-08-01 15:44:29,897 DEBUG [org.springframework.jms.listener.DefaultMessageListenerContainer] Rolling back transaction because of listener exception thrown: org.springframework.jms.listener.adapter.ListenerExecutionFailedException: Listener method 'processIt' threw exception; nested exception is java.lang.RuntimeException: Boom: boom
2008-08-01 15:44:29,897 WARN  [org.springframework.jms.listener.DefaultMessageListenerContainer] Execution of JMS message listener failed
...
2008-08-01 15:44:29,959 DEBUG [org.springframework.transaction.jta.JtaTransactionManager] Transactional code has requested rollback
2008-08-01 15:44:29,959 DEBUG [org.springframework.transaction.jta.JtaTransactionManager] Triggering beforeCompletion synchronization
2008-08-01 15:44:29,959 DEBUG [org.springframework.transaction.support.TransactionSynchronizationManager] Removed value [org.springframework.orm.hibernate3.SessionHolder@6dbcef] for key [org.hibernate.impl.SessionFactoryImpl@787ee7] from thread [org.springframework.jms.listener.DefaultMessageListenerContainer#0-1]
2008-08-01 15:44:29,959 DEBUG [org.springframework.transaction.support.TransactionSynchronizationManager] Removed value [org.springframework.jms.connection.JmsResourceHolder@693a5] for key [org.apache.activemq.ra.ActiveMQConnectionFactory@88e83d] from thread [org.springframework.jms.listener.DefaultMessageListenerContainer#0-1]
2008-08-01 15:44:29,959 DEBUG [org.springframework.transaction.jta.JtaTransactionManager] Initiating transaction rollback
...

In the local transaction example, though,  it’s a HibernateTransactionManager and a DefaultMessageListenerContainer giving the messages:


...
2008-08-01 16:01:15,193 DEBUG [org.springframework.transaction.interceptor.RuleBasedTransactionAttribute] Applying rules to determine whether transaction should rollback on java.lang.RuntimeException: Boom: localboom
2008-08-01 16:01:15,193 DEBUG [org.springframework.transaction.interceptor.RuleBasedTransactionAttribute] Winning rollback rule is: null
2008-08-01 16:01:15,193 DEBUG [org.springframework.transaction.interceptor.RuleBasedTransactionAttribute] No relevant rollback rule found: applying default rules
2008-08-01 16:01:15,193 DEBUG [org.springframework.orm.hibernate3.HibernateTransactionManager] Triggering beforeCompletion synchronization
2008-08-01 16:01:15,193 DEBUG [org.springframework.orm.hibernate3.HibernateTransactionManager] Initiating transaction rollback
2008-08-01 16:01:15,193 DEBUG [org.springframework.orm.hibernate3.HibernateTransactionManager] Rolling back Hibernate transaction on Session [org.hibernate.impl.SessionImpl@5ef1eb]
2008-08-01 16:01:15,193 DEBUG [org.hibernate.transaction.JDBCTransaction] rollback
2008-08-01 16:01:15,193 DEBUG [org.hibernate.transaction.JDBCTransaction] re-enabling autocommit
2008-08-01 16:01:15,193 DEBUG [org.hibernate.transaction.JDBCTransaction] rolled back JDBC Connection
...
2008-08-01 16:01:15,193 DEBUG [org.springframework.jms.listener.DefaultMessageListenerContainer] Initiating transaction rollback on application exception
org.springframework.jms.listener.adapter.ListenerExecutionFailedException: Listener method 'processIt' threw exception; nested exception is java.lang.RuntimeException: Boom: localboom
...

Thus, I conclude…

∴, best as I can figger, we DID witness with our own eyes a sure-enough bonny-fide XA-type rollback!

Whew!

[Update 8/29/2008: See the XA, JNDI, and Bitronix posts, especially part 4, for further confirmation of gen-u-ine XA transactionhood…  ]

Advertisements

,

  1. Leave a comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s