BTM

Rollback does not work with a requires new transaction in the message listener

Details

  • Type: Bug Bug
  • Status: Resolved Resolved
  • Priority: Critical Critical
  • Resolution: Not A Bug
  • Affects Version/s: 2.0.0
  • Fix Version/s: None
  • Component/s: None
  • Labels:
    None
  • Environment:
    WinXP, ActiveMQ 5.3.2, Spring 2.5.6
  • Testcase included:
    yes
  • Number of attachments :
    2

Description

I have been able to reproduce this issue quite easily so hope it will be useful to you.

The use case is pretty simple: a message listener receives an incoming message within a transaction handled by bitronix. This message handler creates a REQUIRES_NEW transaction to do some processing (which may fail). Upon the failure, the listener decides to rollback or commit the main transaction. When it decides to rollback, DLQ management is not working anymore (I get an infinite loop).

The project attached reproduces the issue. It also has another message listener that does NOT start a requires new transaction, demonstrating the rollback + DLQ management works as expected in this case.

Activity

Hide
Ludovic Orban added a comment -

Sample application which reproduces the problem

Show
Ludovic Orban added a comment - Sample application which reproduces the problem
Hide
Ludovic Orban added a comment -

This is an ActiveMQ bug, not a BTM one.

Basically, suspending then resuming a transaction in which a message has been read confuses ActiveMQ's redelivery and DLQ mechanism.

If you do this (in pseudo code):

begin
read msg
rollback

with a maximumRedeliveries of 5 then the message will be read 6 times from the queue before being sent the the DLQ, as expected. But if you do this instead:

begin
read msg
suspend
resume
rollback

then the message gets continuously redelivered no matter the maximumRedeliveries. I guess this is caused by the XAResource.end(TMSUCCESS) / XAResource.start(TMJOIN) calls sent by BTM when suspend / resume are called. Those two calls are perfectly legal in this context, the problem is that ActiveMQ seems to mess its state up when they're used.

See the attached BTM-87.zip sample application to get a working example.

Show
Ludovic Orban added a comment - This is an ActiveMQ bug, not a BTM one. Basically, suspending then resuming a transaction in which a message has been read confuses ActiveMQ's redelivery and DLQ mechanism. If you do this (in pseudo code): begin read msg rollback with a maximumRedeliveries of 5 then the message will be read 6 times from the queue before being sent the the DLQ, as expected. But if you do this instead: begin read msg suspend resume rollback then the message gets continuously redelivered no matter the maximumRedeliveries. I guess this is caused by the XAResource.end(TMSUCCESS) / XAResource.start(TMJOIN) calls sent by BTM when suspend / resume are called. Those two calls are perfectly legal in this context, the problem is that ActiveMQ seems to mess its state up when they're used. See the attached BTM-87.zip sample application to get a working example.
Hide
Stephane Nicoll added a comment - - edited

For the record, I cannot reproduce the issue by switching to LRC (which is consistent with your explanation)

The config to replace in the attached project is the following

<bean id="connectionFactory" 
      class="bitronix.tm.resource.jms.PoolingConnectionFactory" init-method="init"
          destroy-method="close" depends-on="activeMqBrokerService">
        <property name="className" value="bitronix.tm.resource.jms.lrc.LrcXAConnectionFactory"/>
        <property name="uniqueName" value="activemq"/>
        <property name="maxPoolSize" value="20"/>
        <property name="driverProperties">
            <props>
                <prop key="connectionFactoryClassName">org.apache.activemq.ActiveMQConnectionFactory</prop>
                <prop key="properties.brokerURL">
                    <![CDATA[vm://broker?create=false&waitForStart=5000&jms.redeliveryPolicy.maximumRedeliveries=2]]>
                </prop>
            </props>
        </property>
    </bean>
Show
Stephane Nicoll added a comment - - edited For the record, I cannot reproduce the issue by switching to LRC (which is consistent with your explanation) The config to replace in the attached project is the following
<bean id="connectionFactory" 
      class="bitronix.tm.resource.jms.PoolingConnectionFactory" init-method="init"
          destroy-method="close" depends-on="activeMqBrokerService">
        <property name="className" value="bitronix.tm.resource.jms.lrc.LrcXAConnectionFactory"/>
        <property name="uniqueName" value="activemq"/>
        <property name="maxPoolSize" value="20"/>
        <property name="driverProperties">
            <props>
                <prop key="connectionFactoryClassName">org.apache.activemq.ActiveMQConnectionFactory</prop>
                <prop key="properties.brokerURL">
                    <![CDATA[vm://broker?create=false&waitForStart=5000&jms.redeliveryPolicy.maximumRedeliveries=2]]>
                </prop>
            </props>
        </property>
    </bean>
Hide
Bruce Snyder added a comment -

I am looking into this issue in ActiveMQ and although it looks like there may actually be a problem in the ActiveMQ, there appears to be a problem in Bitronix as well.

When a transaction is suspended, Bitronix ends the transactions with a TMSUCCESS flag instead of a TMSUSPEND flag. Because of this, ActiveMQ just thinks that the transaction has ended successfully. I have verified this against two other transaction managers and each one sends a TMSUSPEND flag.

Show
Bruce Snyder added a comment - I am looking into this issue in ActiveMQ and although it looks like there may actually be a problem in the ActiveMQ, there appears to be a problem in Bitronix as well. When a transaction is suspended, Bitronix ends the transactions with a TMSUCCESS flag instead of a TMSUSPEND flag. Because of this, ActiveMQ just thinks that the transaction has ended successfully. I have verified this against two other transaction managers and each one sends a TMSUSPEND flag.
Hide
Ludovic Orban added a comment -

Bruce,

Which other transaction manager did you try out?

Nothing in the XA and JTA specs prevents ending a transaction with XAResource.end(TMSUCCESS) and restart working on it after a XAResource(TMJOIN) call.

I may agree that this way of implementing transaction suspension can be surprising but AFAIK it's perfectly legal.

Show
Ludovic Orban added a comment - Bruce, Which other transaction manager did you try out? Nothing in the XA and JTA specs prevents ending a transaction with XAResource.end(TMSUCCESS) and restart working on it after a XAResource(TMJOIN) call. I may agree that this way of implementing transaction suspension can be surprising but AFAIK it's perfectly legal.
Hide
Bruce Snyder added a comment - - edited

Atomikos for one and it uses the TMSUSPEND/TMRESUME flags. I'm not here to debate what is legal and what is not. If a user of the resource manager is not given the right flags, how can it be expected to behave correctly? The bottom line is that ActiveMQ does not support transaction suspend/resume.

Show
Bruce Snyder added a comment - - edited Atomikos for one and it uses the TMSUSPEND/TMRESUME flags. I'm not here to debate what is legal and what is not. If a user of the resource manager is not given the right flags, how can it be expected to behave correctly? The bottom line is that ActiveMQ does not support transaction suspend/resume.
Hide
Ludovic Orban added a comment -

I know Atomikos implemented suspend/resume differently and I had my reasons to implement it the way I did. I also agree with you that debating over the way suspension should be implemented or not isn't the point here.

If ActiveMQ does not support transaction suspend/resume I guess we can end the conversation here as this is exactly what Stephane attempted to do and as it turned out, it doesn't work because - once again - it's not supported.

Show
Ludovic Orban added a comment - I know Atomikos implemented suspend/resume differently and I had my reasons to implement it the way I did. I also agree with you that debating over the way suspension should be implemented or not isn't the point here. If ActiveMQ does not support transaction suspend/resume I guess we can end the conversation here as this is exactly what Stephane attempted to do and as it turned out, it doesn't work because - once again - it's not supported.

People

Vote (0)
Watch (0)

Dates

  • Created:
    Updated:
    Resolved: