MDP JMS Transaction rolls back then reprocesses message in an endless loop - java

If I enable transaction management on my DefaultMessageListenerContainer by specifying sessionTransacted=true or transactionManager=jmsTransactionManager, whenever an exception occurs in the MDP, the transaction is rolled back and the message is placed back on the queue. That then causes the message to be processed again, the transaction to roll back again over and over again so that it creates an endless loop.
I guess my question is ... what am I missing here? Why would you want the message to go back on the queue if it just means it will be processed over and over again?
<!-- jms connection factory -->
<bean name="jmsConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:ConnectionFactory" />
</bean>
<!-- jms transaction manager -->
<bean id="jmsTransactionManager" class="org.springframework.jms.connection.JmsTransactionManager">
<property name="connectionFactory" ref="jmsConnectionFactory" />
</bean>
<!-- Destination for Inbound_Email_Q -->
<bean name="inboundEmailDestination" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="queue/inbound_Email_Queue" />
</bean>
<!-- JmsTemplate for Inbound_Email_Q -->
<bean name="jmsInboundEmailTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="jmsConnectionFactory" />
<property name="defaultDestination" ref="inboundEmailDestination" />
<property name="messageConverter" ref="xmlMessageConverter" />
</bean>
<!-- jms asynchronous listener -->
<bean id="emailMessageServiceMdp" class="org.site.wso.core.jms.EmailMessageServiceMdp" />
<bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="jmsConnectionFactory"/>
<!-- <property name="transactionManager" ref="jmsTransactionManager" /> -->
<!-- <property name="sessionTransacted" value="true"/> -->
<property name="destination" ref="inboundEmailDestination"/>
<property name="messageListener" ref="messageListener"/>
</bean>
<!-- jms message listener adapter -->
<bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
<constructor-arg>
<bean class="org.site.wso.core.jms.EmailMessageServiceMdp"/>
</constructor-arg>
<property name="messageConverter" ref="xmlMessageConverter"/>
</bean>
And here is my MDP:
public class EmailMessageServiceMdp implements MessageDelegate {
public void handleMessage(Object object) {
EmailMessageRequestVO requestVO = (EmailMessageRequestVO) object;
try {
//Service call that throw exception
} catch (Exception e) {
throw new ApplicationException(e);
}
}
}

The message redelivery is simply the default behaviour for your [configured] JMS implementation. It's up for endless debate what the relative usefulness of this is, but it would seem that rather than discard a message with some potentially unrecoverable data, some sort of retry is a sensible and conservative approach. For example, in your case, you appear to be converting a JMS message to an email message and dispatching to an SMTP server. If the SMTP gateway is down, you might want to hold onto the JMS messages and reprocess them when the gateway comes back up.
In general I would say your options for handling a message that failed processing are (depending on the JMS implementation):
Ditch the message.
Write the message to an error queue.
Redeliver the message after a delay of n seconds.
Redeliver the message n times and then write the message to an error queue.
Redeliver the message after a delay of n seconds x times and then write the message to an error queue.
If you prefer #1, then simply suppress the exception, commit the transaction and wave good bye to the message. For the rest, the JMS configuration (or the destination specific configuration) should handle those.
Additionally, if you want something more specific, you can interrogate the message's getJMSRedelivered() and/or the implementation specific message header property that indicates how many times the message has been redelivered (supported by most JMS implementations, but not standard) and dispose of the message accordingly.

Related

Spring JMS Message doesn't retry or goes to Backout Queue on Error

I'm using Spring JMS and MQ to Send and Receive Messages. While reading messages, I want to make sure that on any Error, the messages will be re-delivered at least 3 times and then later send them to the Backout Queue. The Main Queue has Backout Threshold of 3.
I am using the Transacted set to True and sessionAcknowledgeMode to CLIENT_ACKNOWLEDGE (value is 2).
And in the Message Listener, i'm also doing message.acknowledge();
However, it's not working. Am I missing anything?
<jee:jndi-lookup id="QConnectionFactory" jndi-name="jndi/QCF"/>
<jee:jndi-lookup id="MainQ" jndi-name="jndi/MainQ"/>
<jee:jndi-lookup id="MainQBO" jndi-name="jndi/MainQBO"/>
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory">
<ref local="QConnectionFactory"/>
</property>
<property name="sessionAcknowledgeMode" value="2" />
<property name="sessionTransacted" value="true" />
</bean>
<bean id="msgHandler" class="myservice.MyMessageHandler">
<property name="jmsTemplate" ref="jmsTemplate"/>
<property name="MainQ" ref="MainQ"/>
<property name="MainQBO" ref="MainQBO"/>
</bean>
<bean id="messageListener" class="myservice.MyMessageListener" />
<bean id="jmsContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="QConnectionFactory"/>
<property name="destination" ref="MainQ"/>
<property name="messageListener" ref="messageListener" />
<property name="sessionTransacted" value="true"/>
<property name="sessionAcknowledgeMode" value="2"/>
</bean>
Listener Class ...
public void onMessage(Message message) {
try{
... some code ...
message.acknowledge();
}catch(Exception E){
logger.erro(e);
}
}
"Not working" is never enough information.
That said
}catch(Exception E){
logger.erro(e);
}
You are catching and eating the exception; the listener needs to throw an exception to requeue the message.
I was able to fix the problem using the following approach. I hope it will help others.
Configuration
<bean id="messageListener" class="messageListenerClass" />
<bean id="fixedBackOff" class="org.springframework.util.backoff.FixedBackOff">
<constructor-arg index="0" value="30000" />
<constructor-arg index="1" value="3" />
</bean>
<bean id="jmsContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactoryBean" />
<property name="destination" ref="destinationQueue" />
<property name="messageListener" ref="messageListener" />
<property name="sessionTransacted" value="true" />
<property name="backOff" ref="fixedBackOff" />
</bean>
Listener Class
#Override
public void onMessage(Message message) {
if (message instanceof TextMessage) {
try {
String msg = ((TextMessage) message).getText();
........
........
} catch (JMSException e) {
logger.error("JMSException occured while reading the JMSMessage : {}", e);
throw new RuntimeException();
} catch (SomeException e) {
logger.error("SomeException ... : {}", e);
throw new RuntimeException();
} catch (SomeException e) {
logger.error("SomeException ... : {}", e);
throw new RuntimeException();
}
} else {
logger.error("Message must be of type TextMessage");
throw new IllegalArgumentException("Message must be of type TextMessage");
}
}
This may be a late answer to the OP question, but in case you have management access to the IBM MQ server (the queue manager) the application connects to, it is possible to essentially delegate all the message retry to MQ own algorithms, called "message backout policy" for poison messages i.e. messages that cannot be properly handled by the consuming application for any reason.
Refer to https://www.ibm.com/docs/en/ibm-mq/9.1?topic=applications-handling-poison-messages-in-mq-classes-jms for details.
Note that MQ will happily handle the backout counting (i.e. how many times the same message has been re-delivered), backout threshold (how many failures in a row equal to a message being "poisonous"), and the backout queue (a soft dead-letter queue where poison messages will be routed to once they exceed the backout threshold) configuration for you, BUT this mechanism provides no convenient means for a) delaying the message re-delivery, and b) selective message re-delivery (which messages to attempt again, and which ones should be rejected right away). At least in IBM MQ Series 9 as the time of writing this. If this is something required by your business flow, the application has to implement it.
P.S. And do not forget about setting the sessionTransacted property of JMS message listener container to true so it will properly backout messages on exceptions raised by application!

Rabbitmq : Failed message being reprocessed in an infinite loop

This is the rabbitmq configuration that I have :
<rabbit:admin connection-factory="rmqConnectionFactory"/>
<bean id="**retryAdvice**" class="org.springframework.amqp.rabbit.config.StatefulRetryOperationsInterceptorFactoryBean">
<property name="retryOperations" ref="retryTemplate"/>
</bean>
<bean id="**retryTemplate**" class="org.springframework.retry.support.RetryTemplate">
<property name="retryPolicy" ref="simpleRetryPolicy"/>
<property name="backOffPolicy">
<bean class="org.springframework.retry.backoff.FixedBackOffPolicy">
<property name="backOffPeriod" value="5000"/>
</bean>
</property>
<property name="retryContextCache" ref="retryContext"/>
</bean>
<bean id="**retryContext**" class="org.springframework.retry.policy.MapRetryContextCache"/>
<bean id="**simpleRetryPolicy**" class="org.springframework.retry.policy.SimpleRetryPolicy">
<property name="maxAttempts" value="3"/>
</bean>
<!-- Spring AMQP Template -->
<bean id="**rabbitTemplate**" class="org.springframework.amqp.rabbit.core.RabbitTemplate">
<property name="connectionFactory" ref="rmqConnectionFactory"/>
<property name="messageConverter" ref="stdJsonMessageConverter"/>
</bean>
<bean id="**stdJsonMessageConverter**" class="org.springframework.amqp.support.converter.JsonMessageConverter">
<property name="createMessageIds" value="true"/>
</bean>
And my queue is configured as follows :
<rabbit:queue name="${queue}" durable="true">
<rabbit:queue-arguments>
<entry key="x-ha-policy" value="all"/>
</rabbit:queue-arguments>
</rabbit:queue>
<rabbit:direct-exchange name="${exchange}">
<rabbit:bindings>
<rabbit:binding queue="${queue}" key="${routingKey}"/>
</rabbit:bindings>
</rabbit:direct-exchange>
When I published a message on the exchange for the first time, the listener failed with null ID exception. I purged the queue which had the bad message as well. Inspite of that, everytime I start my service, the failed message processing is retried and it fails continuously until there is a RetryCacheCapacityExceeded exception.
Has my failed message been cached somewhere? Is there a way to clear that?
Also, why do retries continue even though my retrytemplate suggest 3 reattempts at that interval of 5 secs?
When you use stateful retry, the retry state for each message id is kept in a cached (so we know when to stop).
If there's no ID, the message will fail (and keep being delivered) unless you add a MissingMessageIdAdvice to the advice chain (before the retry interceptor), which will allow 1 retry for messages with no id.

SimpleMessageListenerContainer method should stop processing after timeout

I have JVM1 sending requests to JVM2 synchronously via MQ. This is achieved using JmsInvokerServiceExporter ,SimpleMessageListenerContainer and JmsInvokerProxyFactoryBean. The listener method in JVM2 is written such that should any exception arise during processing, it will fabricate a default response indicating that something went wrong. If everything goes fine, it will send successful response.
Now what I am looking for is if the listener method is taking more time than a predefined period, an exception be thrown so that the listener can stop further procesing and send the default response back to JVM1. Basically JVM2 listener should not process beyond time X sec and should be able to send default response back.
I can make the JVM1 time out after X secs but then I won't get the default response that I am expecting from JVM2. Can someone please let me know if such a thing can be achieved or not? The code snippet for setting up the communication between jvm1 and 2 is given below.
Expose a pojo Calculator.java service for remoting over JMS using JMSInvokerServiceExporter
<bean id="calculatorService" class="com.spring.remoting.service.impl.SalaryCalculator" />
<bean id="remoteCalculatorService" class="org.springframework.jms.remoting.JmsInvokerServiceExporter">
<property name="service" ref="calculatorService">
<property name="serviceInterface" value="com.spring.remoting.service.Calculator" />
</bean>
Listener and Invoker
<bean class="org.springframework.jms.listener.SimpleMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="myQueue"/>
<property name="concurrentConsumers" value="3"/>
<property name="messageListener" ref="remoteCalculatorService"/>
</bean>
<bean id="remoteCalculatorService" class="org.springframework.jms.remoting.JmsInvokerProxyFactoryBean">
<property name="serviceInterface" value="com.spring.remoting.service.Calculator" />
<property name="connectionFactory" ref="connectionFactory"/>
<property name="queue" ref="myQueue"/>
</bean>

JMS QUEUE: how to store message in the queue until flag is set to true

I was trying a sample application of JMS queue.. I want the message coming in the queue to stay there until my flag is set to true. I am using spring framework and an MDP Listener with following configuration:
Server-context.xml:
<bean id="MDPListener" class="controller.MDPListener"></bean>
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL">
<value>tcp://localhost:61616</value>
</property>
</bean>
<bean id="dataQueue" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg value="dataQueue15"></constructor-arg>
</bean>
<bean id="container" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="messageListener" ref="MDPListener"/>
<property name="destination" ref="dataQueue"/>
<property name="sessionTransacted" value="true"/>
</bean>
My onMessage has the following code:
public void onMessage(Message message,Session session) {
System.out.println("The session: "+session.toString());
System.out.println("New Message arrived part2 .. Passing to Controller");
Boolean g=false;
if(g==true)
{
System.out.println("Data true..session committed!!");
}
else
{
System.out.println("in the queue");
throw new RuntimeException("Saved");
}
}
Now when an exception is thrown, the message stays back in the queue and the control goes back to the same listener which listens to the same message all over again and stops after sometime. This results in dead queue. I am unable to store that message. I want my listener to listen to the queue but not to the previous message but next one. Please help!
JMS doesn't work that way - you have to move the message to another queue in your code.
Or, with ActiveMQ, you can configure the redelivery policy to send the message to the DLQ faster (I believe the default is 6 retries).

Transaction Support for Springs onMessage handler

So I have some boilerplate code that consumes messages from a topic:
public void onMessage(Message message )
{
try
{
// try my conversion
}
catch(MyConversionException e)
{
//catch conversion error but still consume off topic
}
//Any other error i.e. runtime errors will not cause the message to be consumed from topic. So it can be retried
}
I wish to be able to try to convert the message into another object. If this causes an error I will catch it with my own exception handling and write it to an error queue.
My question is, how do I set up Springs messageListenerContainer bean to be Transactional and only consume if this has taken place successfully???
[EDIT] Here is the bean so far:
<!-- MESSAGE LISTENER CONTAINER -->
<bean id="ListenerContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="messageListener" ref="MessageListener" />
<property name="connectionFactory" ref="Tcf" />
<property name="destinationResolver" ref="JmsDestinationResolver" />
<property name="receiveTimeout" value="${jms-timeout}" />
<property name="destinationName" value="${jms-topic}" />
<property name="concurrency" value="1" />
<property name="pubSubDomain" value="true" />
<property name="subscriptionDurable" value="${jms-durable-flag}"/>
<property name="durableSubscriptionName" value="${jms-durable-name}" />
<property name="clientId" value="${jms-client-id}"/>
</bean>
Is not recommended to do so, but you can call TransactionStatus.setRollbackOnly().
Also you may want to consider to be consistent with the transaction model, and if you want to rollback, then do it through an exception... In that case... You need to throw a RuntimeException so that Spring rollbacks the message, if you catch the exception and only logs it, Spring has no clue that something went wrong...

Categories