Spring MDP - how to shut it down on bad message - java

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);
}

Related

Java JMS - Message Listener and onException

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).

Catch-all exception handling for outbound ChannelHandler

In Netty you have the concept of inbound and outbound handlers. A catch-all inbound exception handler is implemented simply by adding a channel handler at the end (the tail) of the pipeline and implementing an exceptionCaught override. The exception happening along the inbound pipeline will travel along the handlers until meeting the last one, if not handled along the way.
There isn't an exact opposite for outgoing handlers. Instead (according to Netty in Action, page 94) you need to either add a listener to the channel's Future or a listener to the Promise passed into the write method of your Handler.
As I am not sure where to insert the former, I thought I'd go for the latter, so I made the following ChannelOutboundHandler:
/**
* Catch and log errors happening in the outgoing direction
*
* #see <p>p94 in "Netty In Action"</p>
*/
private ChannelOutboundHandlerAdapter createOutgoingErrorHandler() {
return new ChannelOutboundHandlerAdapter() {
#Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
logger.info("howdy! (never gets this far)");
final ChannelFutureListener channelFutureListener = future -> {
if (!future.isSuccess()) {
future.cause().printStackTrace();
// ctx.writeAndFlush(serverErrorJSON("an error!"));
future.channel().writeAndFlush(serverErrorJSON("an error!"));
future.channel().close();
}
};
promise.addListener(channelFutureListener);
ctx.write(msg, promise);
}
};
This is added to the head of the pipeline:
#Override
public void addHandlersToPipeline(final ChannelPipeline pipeline) {
pipeline.addLast(
createOutgoingErrorHandler(),
new HttpLoggerHandler(), // an error in this `write` should go "up"
authHandlerFactory.get(),
// etc
The problem is that the write method of my error handler is never called if I throw a runtime exception in the HttpLoggerHandler.write().
How would I make this work? An error in any of the outgoing handlers should "bubble up" to the one attached to the head.
An important thing to note is that I don't merely want to close the channel, I want to write an error message back to the client (as seen from serverErrorJSON('...'). During my trials of shuffling around the order of the handlers (also trying out stuff from this answer), I have gotten the listener activated, but I was unable to write anything. If I used ctx.write() in the listener, it seems as if I got into a loop, while using future.channel().write... didn't do anything.
I found a very simple solution that allows both inbound and outbound exceptions to reach the same exception handler positioned as the last ChannelHandler in the pipeline.
My pipeline is setup as follows:
//Inbound propagation
socketChannel.pipeline()
.addLast(new Decoder())
.addLast(new ExceptionHandler());
//Outbound propagation
socketChannel.pipeline()
.addFirst(new OutboundExceptionRouter())
.addFirst(new Encoder());
This is the content of my ExceptionHandler, it logs caught exceptions:
public class ExceptionHandler extends ChannelInboundHandlerAdapter {
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
log.error("Exception caught on channel", cause);
}
}
Now the magic that allows even outbound exceptions to be handled by ExceptionHandler happens in the OutBoundExceptionRouter:
public class OutboundExceptionRouter extends ChannelOutboundHandlerAdapter {
#Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
promise.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
super.write(ctx, msg, promise);
}
}
This is the first outbound handler invoked in my pipeline, what it does is add a listener to the outbound write promise which will execute future.channel().pipeline().fireExceptionCaught(future.cause()); when the promise fails. The fireExceptionCaught method propagates the exception through the pipeline in the inbound direction, eventually reaching the ExceptionHandler.
In case anyone is interested, as of Netty 4.1, the reason why we need to add a listener to get the exception is because after performing a writeAndFlush to the channel, the invokeWrite0 method is called in AbstractChannelHandlerContext.java which wraps the write operation in a try catch block. The catch block notifies the Promise instead of calling fireExceptionCaught like the invokeChannelRead method does for inbound messages.
Basically what you did is correct... The only thing that is not correct is the order of the handlers. Your ChannelOutboundHandlerAdapter mast be placed "as last outbound handler" in the pipeline. Which means it should be like this:
pipeline.addLast(
new HttpLoggerHandler(),
createOutgoingErrorHandler(),
authHandlerFactory.get());
The reason for this is that outbound events from from the tail to the head of the pipeline while inbound events flow from the head to the tail.
There does not seem to be a generalized concept of a catch-all exception handler for outgoing handlers that will catch errors regardless of where. This means, unless you registered a listener to catch a certain error a runtime error will probably result in the error being "swallowed", leaving you scratching your head for why nothing is being returned.
That said, maybe it doesn't make sense to have a handler/listener that always will execute given an error (as it needs to be very general), but it does make logging errors a bit tricker than need be.
After writing a bunch of learning tests (which I suggest checking out!) I ended up with these insights, which are basically the names of my JUnit tests (after some regex manipulation):
a listener can write to a channel after the parent write has completed
a write listener can remove listeners from the pipeline and write on an erronous write
all listeners are invoked on success if the same promise is passed on
an error handler near the tail cannot catch an error from a handler nearer the head
netty does not invoke the next handlers write on runtime exception
netty invokes a write listener once on a normal write
netty invokes a write listener once on an erronous write
netty invokes the next handlers write with its written message
promises can be used to listen for next handlers success or failure
promises can be used to listen for non immediate handlers outcome if the promise is passed on
promises cannot be used to listen for non immediate handlers outcome if a new promise is passed on
promises cannot be used to listen for non immediate handlers outcome if the promise is not passed on
only the listener added to the final write is invoked on error if the promise is not passed on
only the listener added to the final write is invoked on success if the promise is not passed on
write listeners are invoked from the tail
This insight means, given the example in the question, that if an error should arise near the tail and authHandler does not pass the promise on, then the error handler near the head will never be invoked, as it is being supplied with a new promise, as ctx.write(msg) is essentially ctx.channel.write(msg, newPromise()).
In our situation we ended up solving the situation by injecting the same shareable error handling inbetween all the business logic handlers.
The handler looked like this
#ChannelHandler.Sharable
class OutboundErrorHandler extends ChannelOutboundHandlerAdapter {
private final static Logger logger = LoggerFactory.getLogger(OutboundErrorHandler.class);
private Throwable handledCause = null;
#Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
ctx.write(msg, promise).addListener(writeResult -> handleWriteResult(ctx, writeResult));
}
private void handleWriteResult(ChannelHandlerContext ctx, Future<?> writeResult) {
if (!writeResult.isSuccess()) {
final Throwable cause = writeResult.cause();
if (cause instanceof ClosedChannelException) {
// no reason to close an already closed channel - just ignore
return;
}
// Since this handler is shared and added multiple times
// we need to avoid spamming the logs N number of times for the same error
if (handledCause == cause) return;
handledCause = cause;
logger.error("Uncaught exception on write!", cause);
// By checking on channel writability and closing the channel after writing the error message,
// only the first listener will signal the error to the client
final Channel channel = ctx.channel();
if (channel.isWritable()) {
ctx.writeAndFlush(serverErrorJSON(cause.getMessage()), channel.newPromise());
ctx.close();
}
}
}
}
Then in our pipeline setup we have this
// Prepend the error handler to every entry in the pipeline.
// The intention behind this is to have a catch-all
// outbound error handler and thereby avoiding the need to attach a
// listener to every ctx.write(...).
final OutboundErrorHandler outboundErrorHandler = new OutboundErrorHandler();
for (Map.Entry<String, ChannelHandler> entry : pipeline) {
pipeline.addBefore(entry.getKey(), entry.getKey() + "#OutboundErrorHandler", outboundErrorHandler);
}

Perform route Shutdown Logic with apache camel

I've recently started playing with Apache Camel, and one of the things I've been having issues with is properly performing shutdown logic on selective routes. Since the shutdown logic would vary between routes, Camel's RoutePolicy made the most sense. Here's an example of why I'm trying to do.
public class ProcessingRouteBuilder extends RouteBuilder {
private ProducerTemplate prodTemplate;
public class ProcessingRouteBuilder(ProducerTemplate aProdTemplate) {
prodTemplate = aProdTemplate;
}
#Override
public void configure() {
from("direct://processing")
.routePolicy(new RoutePolicySupport() {
#Override
public void onStop(Route route) {
super.onStop(route);
prodTemplate.sendBody("direct://shutdownRoute", "msg");
}
})
.process(ex -> // Do stuff)
from("direct://shutdownRoute")
.log("Running shutdown A route body - ${body}");
}
}
The shutdown is done like (http://camel.apache.org/how-can-i-stop-a-route-from-a-route.html). The ProducerTemplate comes from the primary CamelContext (read that it is good practice to create one ProducerTemplate per context).
Running this gives me a DirectConsumerNotAvailableException, I've used seda and vm (i don't plan to interact with multiple contexts, but I gave this a shot anyways), both don't exception, but the shutdown routes are never hit. Some questions I have
I might be using the Producer Template wrong? It doesn't look like it's creating an exchange.
Can I even use the ProducerTemplate once the Shutdown hook has been initiated? I'm not sure how Camel performs the shutdown, but it makes sense that it wouldn't allow new messages to be sent, and if the shutdown route is even available at the time of sending.
One thing to note, that I'm not handling here, is ensuring that the shutdown route is performed after the processing route finishes processing all messages in its queue. I'm not entirely sure if the onStop() method is called after there are no more inflight messages and if not, how to enforce it?
I figure another approach is to use when/choice at the beginning of each route and send some sort of shutdown notifier or message, but this seems a little more clunkier.
Thanks guys!
To programmatic shut down a route you can also use the Control Bus EIP.
However the "stop" logic is not clear as you'd want to send a message to the shutdownroute when the processing route stops, but if the stop happen because you are shutting down the camel context it may be possible that the shutdownRoute has already been stopped.

Handling JMSExceptions when using JMS, specifically ActiveMQ?

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.

How to detect broken/recovered JMS connection in Apache Camel?

We are building an integration project using Apache Camel (Camel 2.10.3, Java DSL based).
We have a route that extracts data from a database (lets call it IN_DB), does some logic and inserts into another database (OUT_DB) once a day, and another route that subscribes to a JMS topic for XML data, does some logic and inserts it into the same database (OUT_DB) throughout the day.
The requirement is that when the JMS topic connection goes down for whatever reason, we keep trying to reconnect indefinitely, and once reconnection is successful we need to go back to the database (IN_DB) and do another load to fill in the gap where the topic was down.
My question is how can we do this logic ('I was connected then I got disconnected and now I am connected again') in Camel? What happens to the route that starts with a topic consumer when the topic goes down, will the route just stop? Or will it issue an error message to some error queue? Do I have to write my own handler to monitor the topic connection, or will Camel automatically reconnect when the topic comes back up and set some message header, or set some context variable to indicate that the 'I was connected then I got disconnected and now I am connected again' scenario has happened? I am happy building the route logic around calling the database load I just can't figure out the best way to 'detect' in Camel that this scenario has happened.
Any suggestions much appreciated.
In terms of the reconnecting to your queue, it depends on what JMS broker you are using. If you are using ActiveMQ then you can configure how it reconnects through the URI so it can try and reconnect to another broker, reconnect to the same broker after a timeout etc. The docs for it are here.
To detect when the connection has failed, the easiest from the point of the view of the program is to just use persistent queues instead of topics. However, assuming that isn't feasible then I think you've got two options.
Define a JMS exception listener. This should let you know when the underlying connection has disappeared.
To detect when its back up again I think you're stuck with posting a message to a particular topic and watching for messages from this topic in another route. When you read a message on this topic, you know the broker has come back up so you can reload your data from the DB.
or 2. You could post regular hearbeat messages onto your JMS topic. If you stop receiving them you know the broker has gone down. Once you start to get them again, you know it is back up and you need to reload data from the DB.
Sorry to answer some years later ! I was looking for a solution for this problem since a few days, in order to supervising an AMQP connection in an Apache Camel application.
I finally find a way, I do not know if in the last Camel versions (i am using 2.25.4), it is possible do listen more "natively" the JMS connection changes :
The JMS consumer route must use the option "messageListenerContainerFactory" in the URL : "messageListenerContainerFactory=#customJmsListernerContainerFactory"
The factory would be instancied before of course and added to a registry associated to the camel context :
MessageListenerContainerFactory customJmsListenerContainerFactory = new CustomJmsListenerContainerFactory();
this.registry.put("customJmsListernerContainerFactory", customJmsListenerContainerFactory);
not working on producer, for this kind, you must add an exception clause :
onException(javax.jms.JMSException.class)...
create the class CustomJmsListenerContainerFactory used above, implementing MessageListenerContainerFactory :
public class CustomJmsListenerContainerFactory implements MessageListenerContainerFactory {
#Override
public AbstractMessageListenerContainer createMessageListenerContainer(JmsEndpoint endpoint) {
CustomJmsListenerContainer customJmsListener = new CustomJmsListenerContainer();
return customJmsListener;
}
create a class CustomJmsListenerContainer extending DefaultMessageListenerContainer AND implementing JmsConnectionListener. The trick is to ovveride createConnection, in order to temporary get the created connection in order to manually add a listener (the container itself):
public class CustomJmsListenerContainer extends DefaultMessageListenerContainer implements JmsConnectionListener {
protected Connection createConnection() throws JMSException {
JmsConnection myConnection = (JmsConnection)super.createConnection();
this.myConnection.addConnectionListener(this);
return myConnection;
}
#Override
public void onConnectionEstablished(URI remoteURI) {
LOGGER.info("JMS connection detected");
#Override
public void onConnectionFailure(Throwable error) {
LOGGER.info("JMS connection failure");
}
#Override
public void onConnectionInterrupted(URI remoteURI) {
LOGGER.info("JMS connection interrupted");
#Override
public void onConnectionRestored(URI remoteURI) {
LOGGER.info("JMS connection restored");
#Override
public void onInboundMessage(JmsInboundMessageDispatch envelope) {
// TODO Auto-generated method stub
}
#Override
public void onSessionClosed(Session session, Throwable cause) {
// TODO Auto-generated method stub
}
#Override
public void onConsumerClosed(MessageConsumer consumer, Throwable cause) {
// TODO Auto-generated method stub
}
#Override
public void onProducerClosed(MessageProducer producer, Throwable cause) {
// TODO Auto-generated method stub
}
}

Categories