In the following snippet:
Connection connection=getConnection();
try {
Session session=connection.createSession();
try {
Queue queue=session.createQueue("foobar");
MessageConsumer consumer=null;
try {
consumer = session.createConsumer(queue);
}
catch(JMSException e) {
// Under what circumstances does this happen?
}
try {
// ...
}
finally {
if(consumer != null) consumer.close();
}
}
finally {
session.close();
}
}
finally {
connection.close();
}
Under what circumstances is the caught JMSException thrown? When it happens, what is the proper way to handle the JMSException? I've read the relevant bits of JMS and ActiveMQ documentation and it doesn't seem to give any guidance on this point (other than use try/catch, obviously.) Also please forgive the contrived code example!
For example, does a JMSException happen if session has "gone bad" in some way, so it's The Right Thing to tear down consumer and session and start over? Or does it mean connection has gone bad, and so I should tear down and rebuild everything in the application that is based on that connection? Or can a call to createConsumer() fail in a transient way, and retrying the call with the same session could succeed?
Similarly, when would these lines throw a JMSException, ignoring something like a closed Session or Connection:
Message message=consumer.receive();
producer.send(message);
I'd like to understand how JMSExceptions should be handled in JMS in general, but answers specific to ActiveMQ are fine, too. (In fact, it's likely that any answer may have to be specific to a specific implementation, since JMS is just a spec.)
The exceptions can be thrown for a number of reasons. The most common would be that the connection was lost due to a down network or broker. Other reasons can be resource exhaustion on the broker, security violations, etc.
To avoid the exceptions caused by network connection issues you can use ActiveMQ's failover transport to have the client do automatic reconnection. There are a number of exception types in JMS so you can test for a given error, like checking to see if consumer creation or producer send failed because of a security exception. If you don't know the actual causes then the most common thing to do is tear down and rebuild your connection resources. This is why using failover is better as you can avoid a lot of work by letting the client handle detecting and responding to this.
Related
I have an application with a main thread and a JMS thread which talk to each other through ActiveMQ 5.15.11. I am able to send messages just fine, however I would like a way to send back status or errors. I noticed that the MessageListener allows for onSuccess() and onException(ex) as two events to listen for, however I am finding that only onSuccess() is getting called.
Here are snippets of my code.
JMS Thread:
ConnectionFactory factory = super.getConnectionFactory();
Connection connection = factory.createConnection();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue(super.getQueue());
MessageConsumer consumer = session.createConsumer(queue);
consumer.setMessageListener(m -> {
try {
super.processRmbnConfigMsg(m);
} catch (JMSException | IOException e) {
LOG.error(e.getMessage(), e);
// I can only use RuntimeException.
// Also this exception is what I am expecting to get passed to the onException(..)
// call in the main thread.
throw new RuntimeException(e);
}
});
connection.start();
Main thread (sending messages to JMS):
sendMessage(xml, new AsyncCallback() {
#Override
public void onException(JMSException e) {
// I am expecting this to be that RuntimeException from the JMS thread.
LOG.error("Error", e);
doSomethingWithException(e);
}
#Override
public void onSuccess() {
LOG.info("Success");
}
});
What I am expecting is that the exceptions thrown in the new RuntimeException(e) will get picked up on the onException(JMSException e) event listener, in some way, even if the RuntimeException is wrapped.
Instead, I am always getting onSuccess() events. I suppose the onException(..) event happens during communication issues, but I would like a way to send back to the caller exceptions.
How do I accomplish that goal of collecting errors in the JMS thread and sending it back to my calling thread?
Your expectation is based on a fundamental misunderstanding of JMS.
One of the basic tenets of brokered messaging is that producers and consumers are logically disconnected from each other. In other words...A producer sends a message to a broker and it doesn't necessarily care if it is consumed successfully or not, and it certainly won't know who consumes it or have any guarantee when it will be consumed. Likewise, a consumer doesn't necessarily know when or why the message was sent or who sent it. This provides great flexibility between producers and consumers. JMS adheres to this tenet of disconnected producers and consumers.
There is no direct way for a consumer to inform a producer about a problem with the consumption of the message it sent. That said, you can employ what's called a "request/response pattern" so that the consumer can provide some kind of feedback to the producer. You can find an explanation of this pattern along with example code here.
Also, the AsyncCallback class you're using is not part of JMS. I believe it's org.apache.activemq.AsyncCallback provided exclusively by ActiveMQ itself and it only provides callbacks for success or failure for the actual send operation (i.e. not for the consumption of the message).
Lastly, you should know that throwing a RuntimeException from the onMessage method of a javax.jms.MessageListener is considered a "programming error" by the JMS specification and should be avoided. Section 8.7 of the JMS 2 specification states:
It is possible for a listener to throw a RuntimeException; however, this is considered a client programming error. Well behaved listeners should catch such exceptions and attempt to divert messages causing them to some form of application-specific 'unprocessable message' destination.
The result of a listener throwing a RuntimeException depends on the session's acknowledgment mode.
AUTO_ACKNOWLEDGE or DUPS_OK_ACKNOWLEDGE - the message will be immediately redelivered. The number of times a JMS provider will redeliver the same message before giving up is provider-dependent. The JMSRedelivered message header field will be set, and the JMSXDeliveryCount message property incremented, for a message redelivered under these circumstances.
CLIENT_ACKNOWLEDGE - the next message for the listener is delivered. If a client wishes to have the previous unacknowledged message redelivered, it must manually recover the session.
Transacted Session - the next message for the listener is delivered. The client can either commit or roll back the session (in other words, a RuntimeException does not automatically rollback the session).
I have got a Spring MDP implemented using Spring DefaultMessageListenderContainer listening to an input queue on WebSphere MQ v7.1. If there is a bad message coming in (that causes RuntimeException), what currently happens is that, the transaction is rolled back, and the message is put back into the queue. However the MDP goes into an infinite loop.
Question 1: For my requirements I would like to be able to shut down the processing the moment it sees a bad message. No retries needed. Is it possible to shutdown the message listener gracefully in case it sees a bad message (as opposed to crude System.exit() or methods of that sort)? I definitely don't like it to go into an infinite loop.
Edit:
Question 2: Is there a way to stop or suspend the listener container to stop further processing of messages?
The usual way to process this is to have an error queue and when you see a bad message to put it into the error queue.
Some systems handle this for you such as IBM MQ Series. You just need to configure the error queue and how many retries you want ant it will put it there.
An administrator will then look through these queues and take proper action on the messages that are in the queue (i.e. fix and resubmit them)
Actually, System.exit() is too brutal and... won't work. Retrying of failed messages is handled on the broker (WMQ) side so the message will be redelivered once you restart your application.
The problem you are describing is called poison-message and should be handled on the broker side. It seems to be described in Handling poison messages in WMQ manual and in How WebSphere Application Server handles poison messages.
I solved the problem in the following manner, not sure if this is the best way, however it works.
MDP Implements ApplicationContextAware; I also maintain a listener state (enum with OPEN, CLOSE, ERROR values) MDP Code fragment below:
//context
private ConfigurableApplicationContext applicationContext;
//listener state
private ListenerState listenerState = ListenerState.OPEN;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = (ConfigurableApplicationContext) applicationContext;
}
//onMessage method
public void processMessages(....) {
try {
process(...);
} catch (Throwable t) {
listenerState = ListenerState.ERROR;
throw new RuntimeException(...);
}
}
#Override
public void stopContext() {
applicationContext.stop();
}
In the java main that loads the spring context i do this:
//check for errors for exit
Listener listener = (Listener)context.getBean("listener");
listenerContainer listenerContainer =
(ListenerContainer)context.getBean("listenerContainer");
try {
while(true) {
Thread.sleep(1000); //sleep for 1 sec
if(!listener.getListenerState().equals(ListenerState.OPEN)) {
listener.stopContext();
listenerContainer.stop();
System.exit(1);
}> }
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
I got this use case:
Here the class diagram
Here the sequence diagram
This diagram represents an enterprise model. Java EE technology on Weblogic 10.3 with the leverage of spring framework for IoC and AOP, JPA for persistence with spring jpatemplate, spring integration for the interaction frame.
As you can see there is no coupling between the Service and the Gateway since spring integration add all the magic sugar needed.
Now I have to deal with the exception handling. All the chain has no checked exceptions: also the data access has no checked exception since jpatemplate wraps all the sql exception in runtime exceptions.
So the only checked exception that I handle is on the MDB
#Override
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void onMessage(Message message) {
try {
TextMessage textMessage = (TextMessage) message;
String stringMessage = textMessage.getText();
OnlineEventMessage<? extends Serializable> event = eventMessageParser.parse(stringMessage);
legacyEventMessageService.handle(event);
} catch (JMSException e) {
logger.error("si e' verificato un errore JMS nel processamento dell'evento {}", message, e);
}
}
I noticed that if I get a NPE for example on some component of the chain the message is rolled back on the JMS queue and the process is looped back.
Which is the best way to handle exceptions in this scenario?
Catch all the runtimeExceptions in the MDB?
Kind regards
Massimo
Which is the best way to handle exceptions in this scenario? Catch all the runtimeExceptions in the MDB?
It depends on what you want to achieve. If feel from your description that you want to prevent the message being rolled back. Is that right?
In that case catching all run time exceptions gets you only so far. The system could also throw errors, which you won't catch then. So you have to catch Throwable instead. But, then the transaction could still time out, resulting in a roll-back.
In short, do you want your MDB to be transactional at all?
Also note that the transaction context from the sender does not propagate to the MDB.
Slightly off topic, but are you really sure you need jpatemplate? It seems pretty much everybody has agreed that the JPA API is fine by itself and doesn't need any 'enhancements' from Spring, including SpringSource themselves.
Assuming I have one Connection per JVM (implemented as a singleton) as a derivative of the docs which recommend having one Connection as it's a heavy object.
From the docs:
... a connection is a relatively
heavyweight object. Most clients will
do all their messaging with a single
connection... A JMS client
typically creates a connection, one or
more sessions, and a number of message
producers and consumers.
I'm trying to decide what to do with my Sessions, Producers and Consumers with respect to the ExceptionListener which is at the Connection level.
To the best of my understanding it is very reasonable they are no longer usable, when a JMSException is thrown, but I'm not sure what should be done once the above listener is triggered.
My Sessions are kept in a ThreadLocal<Session> which is also kept in a singleton.
I can use this to call MySessionSingleton.closeSession() in the listener but this will only close the Session which is bound to the thread in which the Exception was thrown and not all other Sessions.
In addition this does not take care of the Producers\Consumers and their reconnect.
A possible solution which I saw used and I'm reluctant to imitate is to have a Connection and a Session for every Producer\Consumer and so I can control all of the above.
Would appreciate any thoughts,
Ittai
Clarification:
My current implementation, by a former programmer, is the one I refer to above as being used and the biggest problem it poses for me is that I need several producers and consumers to use the same Session as I have a need for JTA transactions and I think (might be wrong) that I need those Producers\Consumers to share the session.
The connection was a derivative of that decision.
So basically even if I keep the relationship of one session per connection I still have the above problem when one session has multiple Producers\Consumers.
Maybe this is a naive question as I have not much real world experience with JMS, but wouldn't it be the easiest way to call connection.close()?
The JMS API says that "There is no need to close the sessions, producers, and consumers of a closed connection."
Aditionally a global flag could be set as a signal for message handler loops etc.
I still don't see why you need to only have one Connection object :-). I'd let the application server (I assume you're running in one) take care of managing the Connection resources for you.
i.e. Use a ConnectionFactory in order to get your Connections and create your session and consumers and producers off that.
Something roughly like:
public void notify(String errorOccurrence)
{
Connection connection = null;
try
{
connection = connectionFactory.createConnection();
Session session = connection.createSession(true,
javax.jms.Session.AUTO_ACKNOWLEDGE);
TextMessage message = session.createTextMessage(errorOccurrence);
MessageProducer messageProducer =
session.createProducer(errorOccurrenceChannel);
messageProducer.send(message);
}
catch (JMSException e)
{
handleJmsExcption(e);
}
finally
{
if (connection != null)
{
try
{
connection.close();
}
catch (JMSException e)
{
handleJmsExcption(e);
}
}
}
}
We have a Java listener that reads text messages off of a queue in JBossMQ. If we have to reboot JBoss, the listener will not reconnect and start reading messages again. We just get messages in the listener's log file every 2 minutes saying it can't connect. Is there something we're not setting in our code or in JBossMQ? I'm new to JMS so any help will be greatly appreciated. Thanks.
You should implement in your client code javax.jms.ExceptionListener. You will need a method called onException. When the client's connection is lost, you should get a JMSException, and this method will be called automatically. The only thing you have to look out for is if you are intentionally disconnecting from JBossMQ-- that will also throw an exception.
Some code might look like this:
public void onException (JMSException jsme)
{
if (!closeRequested)
{
this.disconnect();
this.establishConnection(connectionProps, queueName, uname, pword, clientID, messageSelector);
}
else
{
//Client requested close so do not try to reconnect
}
}
In your "establishConnection" code, you would then implement a while(!initialized) construct that contains a try/catch inside of it. Until you are sure you have connected and subscribed properly, stay inside the while loop catching all JMS/Naming/etc. exceptions.
We've used this method for years with JBossMQ and it works great. We have never had a problem with our JMS clients not reconnecting after bouncing JBossMQ or losing our network connection.
I'd highly recommend you use the Spring abstractions for JMS such as the MessageListenerContainer to deal with reconnection, transactions and pooling for you. You just need to supply a MessageListener and configure the MessageListenerContainer with the ConnectionFactory and the container does the rest.
If you're purely a listener and do no other JMS calls other than connection setup, then the "onException() handler" answer is correct.
If you do any JMS calls in your code, just using onException() callback isn't sufficient. Problems are relayed from the JMS provider to the app either via an exception on a JMS method call or through the onException() callback. Not both.
So if you call any JMS methods from your code, you'll also want to invoke that reconnection logic if you get any exceptions on those calls.
Piece of advice from personal experience. Upgrade to JBoss Messaging. I've seen it in production for 4 months without problems. It has fully transparent failover - amongst many other features.
Also, if you do go with Spring, be very careful with the JmsTemplate.