I would like to clarify behavior of the Tibco bus (6.1) used via Spring (4.1) JMS config - with and without ErrorHandler specified, and for a specific case of the TransactionRolledBackException.
I configure JMS listener with the "transacted" mode as following:
<jms:listener-container connection-factory="singleConnectionFactory"
acknowledge="transacted" task-executor="myTaskExecutor" concurrency="${my.queue.concurrency}">
<jms:listener destination="${queue.destination}" ref="myProcessor" method="processMyMessage" />
</jms:listener-container>
and also set 'maxRedelivery' for the queue to 2.
Here are the scenarios of processing an event in the myProcessor.processMyMessage():
message is processed successfully; Result: message is ACK'ed and removed from the queue
a Runtime exception is thrown , either by business logic or by the Spring container (say OOM or HectorException); Result: message is not ACK'ed, put back on the bus and redelivered up to 2 times
org.springframework.jms.TransactionRolledBackException is thrown (by Spring, I assume) - since it is a Runtime exception the resulting behavior is the same as in 2. (is this correct?)
Now, I am adding an explicit ErrorHandler to better log some runtime exceptions:
<jms:listener-container connection-factory="singleConnectionFactory"
acknowledge="transacted" task-executor="myTaskExecutor" concurrency="${my.queue.concurrency}"
error-handler="myErrorHandler">
<jms:listener destination="${queue.destination}" ref="myProcessor" method="processMyMessage" />
</jms:listener-container>
And the following implementation of the ErrorHandler, where we swallow TransactionRolledBackException:
import org.springframework.jms.TransactionRolledBackException;
import org.springframework.util.ErrorHandler;
public class JMSListenerErrorHandler implements ErrorHandler {
#Override
public void handleError(Throwable t) {
if (t.getCause() instanceof TransactionRolledBackException) {
log.warn("JMS transaction rollback; reason: " + t.getMessage(), t);
} else {
log.error(t.getMessage(), t);
throw new RuntimeException(t);
}
}
What is going to happen for each of the use cases? Here is my guess:
no change
no change - this Runtime exception is re-thrown, so the message will not be ACK'ed and will be redelivered up to 2 time
TransactionRolledBackException: when I handle this exception in the ErrorHandler - is this happening AFTER the TX was already rolled back (as the name RolledBack suggests) and message was marked as not ACK'ed, thus, causing it to be redelivered as in the case 2. ? Or do I effectively cause this messaged to be ACK'ed by swallowing this exception?
Couple more questions:
under what conditions would Spring throw an org.springframework.jms.TransactionRolledBackException ?
is TransactionRolledBackException treated any different than any other Runtime exception?
Thank you,
Marina
Spring JMS only throws that exception when the underlying JMS client throws a javax.jms.TransactionRolledBackException - it is simply an unchecked wrapper exception - see JmxUtils.convertJmsAccessException().
Take a look at the stack trace and you will see where/why it was thrown.
In general, i.e. if it was thrown on the container's session, catching it won't have any effect - the transaction is already rolled back; but the stack trace is the key.
Related
I'm using Spring Boot 1.5.3 and Atomikos 4.0.6. I have a DefaultMessageListenerContainer setup to listen a JMS queue and handle incoming messages in a transaction using the Spring JTATransactionManager configured to use Atomikos. The message handler calls a transactional service that tries to process the message inside a try-catch-block and in case of exceptions calls other transactional methods for logging purposes. The idea is that the transaction should be rolled back only after everything is logged and the encountered RuntimeException is thrown from the catch-block.
#Transactional
public void handleMessage(UnmarshalledMessage message) {
try {
Thing thing = repository.find(message.getId());
...
}
catch (Exception e) {
// NoResultException translated into EmptyResultDataAccessException
logger.logUsingTransactions(e.getMessage());
throw e;
}
}
However, what happens that the transaction is rolled back immediately after it's originally thrown inside repository.find(). When attempting to read from the database inside the catch-block, an exception is thrown since the transaction has been marked as rollback only:
c.atomikos.jdbc.AtomikosSQLException - Transaction is marked for rollback only or has timed out
com.atomikos.datasource.xa.session.InvalidSessionHandleStateException: Transaction is marked for rollback only or has timed out
at com.atomikos.datasource.xa.session.NotInBranchStateHandler.checkEnlistBeforeUse(NotInBranchStateHandler.java:39)
at com.atomikos.datasource.xa.session.TransactionContext.checkEnlistBeforeUse(TransactionContext.java:70)
at com.atomikos.datasource.xa.session.SessionHandleState.notifyBeforeUse(SessionHandleState.java:160)
at com.atomikos.jdbc.AtomikosConnectionProxy.enlist(AtomikosConnectionProxy.java:207)
at com.atomikos.jdbc.AtomikosConnectionProxy.invoke(AtomikosConnectionProxy.java:122)
at com.sun.proxy.$Proxy129.prepareStatement(Unknown Source)
at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.prepareStatement(DatabaseAccessor.java:1565)
at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.prepareStatement(DatabaseAccessor.java:1514)
at org.eclipse.persistence.internal.databaseaccess.DatabaseCall.prepareStatement(DatabaseCall.java:778)
at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.basicExecuteCall(DatabaseAccessor.java:621)
at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeCall(DatabaseAccessor.java:560)
at org.eclipse.persistence.internal.sessions.AbstractSession.basicExecuteCall(AbstractSession.java:2055)
at org.eclipse.persistence.sessions.server.ServerSession.executeCall(ServerSession.java:570)
at org.eclipse.persistence.sessions.server.ClientSession.executeCall(ClientSession.java:258)
at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeCall(DatasourceCallQueryMechanism.java:242)
at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeCall(DatasourceCallQueryMechanism.java:228)
at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.selectOneRow(DatasourceCallQueryMechanism.java:714)
at org.eclipse.persistence.internal.queries.ExpressionQueryMechanism.selectOneRowFromTable(ExpressionQueryMechanism.java:2803)
at org.eclipse.persistence.internal.queries.ExpressionQueryMechanism.selectOneRow(ExpressionQueryMechanism.java:2756)
at org.eclipse.persistence.queries.ReadObjectQuery.executeObjectLevelReadQuery(ReadObjectQuery.java:555)
at org.eclipse.persistence.queries.ObjectLevelReadQuery.executeDatabaseQuery(ObjectLevelReadQuery.java:1175)
at org.eclipse.persistence.queries.DatabaseQuery.execute(DatabaseQuery.java:904)
at org.eclipse.persistence.queries.ObjectLevelReadQuery.execute(ObjectLevelReadQuery.java:1134)
at org.eclipse.persistence.queries.ReadObjectQuery.execute(ReadObjectQuery.java:441)
at org.eclipse.persistence.queries.ObjectLevelReadQuery.executeInUnitOfWork(ObjectLevelReadQuery.java:1222)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.internalExecuteQuery(UnitOfWorkImpl.java:2896)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1857)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1839)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1790)
at org.eclipse.persistence.internal.jpa.EntityManagerImpl.executeQuery(EntityManagerImpl.java:911)
at org.eclipse.persistence.internal.jpa.EntityManagerImpl.findInternal(EntityManagerImpl.java:854)
at org.eclipse.persistence.internal.jpa.EntityManagerImpl.find(EntityManagerImpl.java:730)
at org.eclipse.persistence.internal.jpa.EntityManagerImpl.find(EntityManagerImpl.java:599)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
I'd like to know what is causing this behavior and how to resolve the issue. Note that this setup has been working correctly when run in Weblogic. For additional information, here is a transaction trace log when the exception is first encountered inside the repository method.
DEBUG o.s.t.jta.JtaTransactionManager - Initiating transaction commit
DEBUG o.s.t.jta.JtaTransactionManager - Creating new transaction with name [myMessageListenerContainer]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
DEBUG o.s.t.jta.JtaTransactionManager - Participating in existing transaction
TRACE o.s.t.i.TransactionInterceptor - Getting transaction for [my.transactional.messagehandler.handleMessage]
DEBUG o.s.t.jta.JtaTransactionManager - Participating in existing transaction
TRACE o.s.t.i.TransactionInterceptor - Getting transaction for [my.repository.class.method]
TRACE o.s.t.i.TransactionInterceptor - Completing transaction for [my.repository.class.method] after exception: org.springframework.dao.EmptyResultDataAccessException: ProcessableMessage with id 443e73e7-0905-416b-9e03-4aaa2bbf09fb; nested exception is javax.persistence.NoResultException: ProcessableMessage with id [message-id]
TRACE o.s.t.i.RuleBasedTransactionAttribute - Applying rules to determine whether transaction should rollback on org.springframework.dao.EmptyResultDataAccessException: ProcessableMessage with id 443e73e7-0905-416b-9e03-4aaa2bbf09fb; nested exception is javax.persistence.NoResultException: ProcessableMessage with id [message-id]
TRACE o.s.t.i.RuleBasedTransactionAttribute - Winning rollback rule is: null
TRACE o.s.t.i.RuleBasedTransactionAttribute - No relevant rollback rule found: applying default rules
DEBUG o.s.t.jta.JtaTransactionManager - Participating transaction failed - marking existing transaction as rollback-only
DEBUG o.s.t.jta.JtaTransactionManager - Setting JTA transaction rollback-only
EDIT:
I am using JPA and the NoResultException is initially thrown in a following way:
public static <T> T mandatoryFind(EntityManager em, Class<T> type, Object id) throws NoResultException {
T found = em.find(type, id);
if (found == null) {
throw new NoResultException(type.getSimpleName() +" with id "+ id);
}
return found;
}
Which in turn is called from a class annotated with #Transactional(noRollbackFor = NoResultException.class). The exception is raised in a fairly normal use case - what I'm troubled with is why the transaction is rolled back before I can handle the exception?
It sounds like you do use JPA under the hood (based on your debug logs).
This is a typical behaviour then you do query a single result.
NoResultException:
Thrown by the persistence provider when Query.getSingleResult() or TypedQuery.getSingleResult()is executed on a query and there is no result to return. This exception will not cause the current transaction, if one is active, to be marked for rollback.
In order to avoid that behaviour you do query result as a List.
I am using Spring Integration with JTA support through Atomikos and JMS bound to different Webshpere MQ and a Oracle Database.
Obviosly for others it seems to be the same thread as in Spring Integration JMS with JTA rollback when message goes to errorChannel but it isn't not at all.
The flow is the following:
message-driven-channel-adapter receive the message within transaction
some transformations
ServiceActivator with deeper business logic
DataBase Update
Everything works really fine expect for one szenario:
If an unchecked exception occurs (maybe due to inkonsistent data which shouldn't be) within the ServiceActivator the message is rethrown and handled within an ErrorHandler (via ErrorChannel).
There - in some cases - the orgininal incoming message should be send to a DeadLetter Queue (Webshere MQ). This is done with outbound-channel-adapter.
See enclosed configuration:
<jms:message-driven-channel-adapter id="midxMessageDrivenAdapter"
channel="midxReplyChannel"
acknowledge="auto"
transaction-manager="transactionManager"
connection-factory="connectionFactory"
concurrent-consumers="1"
error-channel="errorChannel"
max-concurrent-consumers="${cmab.integration.midx.max.consumer}"
idle-task-execution-limit="${cmab.integration.midx.idleTaskExecutionLimit}"
receive-timeout="${cmab.integration.midx.receiveTimeout}"
destination="midxReplyQueue"/>
................
<int:service-activator input-channel="midxReplyProcessChannel" ref="processMidxReplyDbWriter" />
<int:service-activator input-channel="errorChannel" ref="inputErrorHandler" />
<jms:outbound-channel-adapter id="deadLetterOutboundChannelAdapter"
channel="errorDlChannel" destination="deadLetterQueue"
delivery-persistent="true" connection-factory="nonXAConnectionFactory"/>
Some important hints:
message-driven-channel-adapter:
connectionFactory is a MQXAQueueConnectionFactory wihtin an AtomikosConnectionFactoryBean
transaction-manager is Spring JtaTransactionManager
outbound-channel-adapter:
connection-factory is a nonXAConnectionFactory.
CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory();
MQQueueConnectionFactory mqQueueConnectionFactory = new MQQueueConnectionFactory();
.....
cachingConnectionFactory.setTargetConnectionFactory(mqQueueConnectionFactory);
return cachingConnectionFactory;
And now the error which I observed:
Although I handled the unchecked exception in my ErrorHandler (ref="inputErrorHandler"; send the message to DeadLetter-Queue) an rollback is initiated and the message-driven-channel-adapter receives the message again and again.
Curious is the fact that indeed the message is delivered to the DeadLetterQueue via the outbound-channel-adapter. The destination (deadLetterQueue) contains the failed messages.
Question: What I'm doing wrong? The failed original incoming message is rolled back although I handled the exception in my ErrorHandler.
Any help really appreciate. Many thanks in advance.
concerning to my comment see enclose code of my InputErrorHandler:
if (throwable instanceof MessageHandlingException) {
MessageHandlingException messageHandlingException = (MessageHandlingException) throwable;
if (messageHandlingException.getCause() != null
&& (messageHandlingException.getCause() instanceof CmabProcessDbException
|| messageHandlingException.getCause() instanceof CmabReplyFormatException)) {
String appMessage = ((CmabException) messageHandlingException.getCause()).getAppMessagesAsString();
LOGGER.error(String.format("cmab rollback exception occured: %s", appMessage));
LOGGER.error("******** initiate rollback *********");
throw throwable.getCause();
} else {
ErrorDto payload = fillMessagePayload(messageHandlingException, throwableClassName);
sendMessageToTargetQueue(payload);
}
As I mentioned the "business" ServiceActivator throws an unchecked exception so in this case the ELSE-Statements are calling.
Within that I build up a Message with MessagBuilder and sends it ti the errorDlChannel (s. above the outbound-channel-adapter!).
private void sendMessageToDeadLetterQueue(Message<?> message, String description, String cause) {
.......
Message<?> mb =
MessageBuilder.withPayload(message.getPayload())
.copyHeaders(message.getHeaders())
.setHeaderIfAbsent("senderObject", this.getClass().getName())
.setHeaderIfAbsent("errorText", description)
.setHeaderIfAbsent("errorCause", errorCause).build();
errorDlChannel.send(mb);
}
That's all. This is for this case the last statement. There is nothing anything else in the main method of my ErrorHandler. No rethrow or other stuff.
So this is the reason of my confusion. For me the exception is handled by sending it to the errorDlChannel (outbound-channel-adapter -> DeadLetterQueue).
I saw the message on the DeadLetter Queue but nevertheless a jta rollback occurs...and IMO this shouldn't bee.
I am using Spring AMQP for processing the messages in RabbitMQ. Below is the issue:
1. (say) there are 3 messages in ready state inside RabbitMQ
2. First one is picked up by MessageListener and starts processing. (say) It ends up throwing an exception
3. In this case, the container remains up but the remaining 2 messages are not processed until i restart the container. Also the first messages stays in unacknowledged state.
Is it the expected behavior? If not, how to make sure that other 2 messages will be processed irrespective first one failed processing?
MQ configuraion:
<rabbit:connection-factory id="connectionFactory" host="localhost" username="guest" password="guest" />
<rabbit:admin connection-factory="connectionFactory" />
<rabbit:listener-container
connection-factory="connectionFactory"
concurrency="1"
acknowledge="auto">
<rabbit:listener queue-names="testQueue" ref="myProcessorListener " />
</rabbit:listener-container>
MessageListener class:
public class MyProcessorListener implements MessageListener{
....
#Override
public void onMessage(Message message) {
try{
...Some logic...
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
The message is redelivered over and over again; in order to reject it (and discard or route to a dead letter queue), you need to throw AmqpRejectAndDontRequeueException or set the container's requeue-rejected property to false. When configuring with Java it's defaultRequeueRejected.
You can also use a custom error handler.
This is all explained in the reference manual.
MQ version 7.5.0.4
Is there a way of getting a notification when the connection is lost. What I can se I only get an error when the timeout has been reached. (JMSWMQ1107: A problem with this connection has occurred.)
mqcf.setConnectionNameList(host); // "host1(1414),host2(1414)";
mqcf.setClientReconnectOptions(WMQConstants.WMQ_CLIENT_RECONNECT);
mqcf.setClientReconnectTimeout(100); // seconds
ExceptionListener exceptionListener = new ExceptionListener(){
#Override
public void onException(JMSException e) {
System.out.println(e.getErrorCode() + " " + e.getMessage());
}
};
// need to reconnect on exception..!
connection.setExceptionListener(exceptionListener);
Using the Exception Listener for events (even reconnection) is outside of the scope of the JMS Specification. The strict definition is that that should be fired only for when a connection is broken - and the correct course of action is to recreate the connection. (note that various vendors MQ included do provide extensions - so it's always worth raising the requirement!)
In the JavaSE environment you have here, it's possible to set the autoreconnect. In managed environments that's not supported so you do actually have to listen for the connection broken and then the applicaiton needs to redrive createConnection(). The connectionNameList is then 'walked' to find a QM that is running.
Use the linked exception to get more details. In my tested I ended the queue manager without the reconnect option, hence the MQRC 2161 reason code.
code:
#Override
public void onException(JMSException e) {
System.out.println(e);//e.getErrorCode() + " " + e.getMessage());
if(e.getLinkedException() != null)
System.out.println(e.getLinkedException());
}
Exception details.
com.ibm.msg.client.jms.DetailedIllegalStateException: JMSWMQ1107: A problem with this connection has occurred.
An error has occurred with the WebSphere MQ JMS connection.
Use the linked exception to determine the cause of this error.
com.ibm.mq.MQException: JMSCMQ0001: WebSphere MQ call failed with compcode '2' ('MQCC_FAILED') reason '2161' ('MQRC_Q_MGR_QUIESCING').
I am connecting to IBM MQ using apache camel Jms component using below code and configuration. When MQ manager goes down by any reason while message polling or is down at the time of camel route startup, my errorHandler or exceptionListener is not invoked.
jmsComponenet = JmsComponent.jmsComponentAutoAcknowledge((ConnectionFactory) obj);
camelContext.addComponent("ibm-mq", jmsComponenet);
camelContext.addRoutes(new RouteBuilder() {
#Override
public void configure() throws Exception {
from("ibm-mq:queue:PE_OUTBOUND?concurrentConsumers=5&exceptionListener=#exceptionListener&errorHandler=#errorHandler").to(
"mqprocessor");
}
});
Spring Application-Context :
<bean id="exceptionListener" class="com.*****.JMSExceptionListener" />
<bean id="errorHandler" class="com.*****.JMSConnectionErrorHandler" />
The classess implements required interface
javax.jms.ExceptionListener and org.springframework.util.ErrorHandler
Despite of specifying Handler and Listener still MQ Connection error is just logged as WARN message in log and controll do not reach these classes.
I am missing / doing anything incorrect here?
Here is DEBUG log -
11:18:20,783 DEBUG [org.springframework.beans.factory.support.DefaultListableBeanFactory] (http-localhost/127.0.0.1:8080-1) Returning cached instance of singleton bean 'errorHandler'
11:18:20,784 DEBUG [org.apache.camel.util.IntrospectionSupport] (http-localhost/127.0.0.1:8080-1) Configured property: errorHandler on bean: org.apache.camel.component.jms.JmsConfiguration#17b86db4 with value: com.manh.processors.JMSConnectionErrorHandler#4d2a5096
11:18:20,784 DEBUG [org.springframework.beans.factory.support.DefaultListableBeanFactory] (http-localhost/127.0.0.1:8080-1) Returning cached instance of singleton bean 'exceptionListener'
11:18:20,784 DEBUG [org.apache.camel.util.IntrospectionSupport] (http-localhost/127.0.0.1:8080-1) Configured property: exceptionListener on bean: org.apache.camel.component.jms.JmsConfiguration#17b86db4 with value: com.manh.processors.JMSExceptionListener#6c8b8e49
11:18:20,785 DEBUG [org.apache.camel.spring.SpringCamelContext] (http-localhost/127.0.0.1:8080-1) ibm-mq://queue:PE_OUTBOUND?concurrentConsumers=5&errorHandler=%23errorHandler&exceptionListener=%23exceptionListener converted to endpoint: Endpoint[ibm-mq://queue:PE_OUTBOUND?concurrentConsumers=5&errorHandler=%23errorHandler&exceptionListener=%23exceptionListener] by component: org.apache.camel.component.jms.JmsComponent#fb03c67
11:18:20,785 TRACE [org.apache.camel.management.DefaultManagementLifecycleStrategy] (http-localhost/127.0.0.1:8080-1) Checking whether to register Endpoint[ibm-mq://queue:PE_OUTBOUND?concurrentConsumers=5&errorHandler=%23errorHandler&exceptionListener=%23exceptionListener] from route: null
default testConnectionOnStartup = false. I set it to ture and got exception on startup of route.
ErrorHandler come into play only if exception occurs while processing of message.
Thank you for the help Daniel.
Logging with a warn message is default when an error handler cannot be found, so I suspect Camel is not able to find an instance of the provided bean. Try setting the camel log level to debug and see what it says when the route is started, I would expect some kind of message saying that the referenced beans cannot be found.