Code...
#Transactional
#JmsListener(destination = "QueueA")
public void process(String input) {
doSomethingWhichMayThrowException(input);
}
Consider following situation where...
Transaction is started (using Spring #Transactional annotation)
Persistent JMS message is read from QueueA (Queue use disk as message storage)
Disk is full and do not accept any write operations
Exception happens and transaction is rolled back
Is message lost?
If it's not then how message is read from queue under transaction (step 2)?
Is some kind of a queue browser used so message is read from queue but not consumed?
Is message lost?
No, the message is NOT lost as the transaction is rolledback.
If it's not then how message is read from queue under transaction
(step 2) ?
Once after the message listener's process()/onMessage() method completes and returns with a success or exception, then internally message acknowledgment (default is AUTO_ACKNOWLEDGE) happens (which is the last thing implicitly happens) to the JMS provider(IBMMQ, ActiveMQ, SonicMQ, etc..) which tells that the transaction is successful or not.
If the Transaction is successful, JMS provider deletes the message from the queue/topic.
If the Transaction is NOT successful, JMS provider preserve the message as is (until message TimetoLive expires).
Is some kind of a queue browser used so message is read from queue but
not consumed ?
You can think that it is like queue browser concept, but it is upto the implementation of the JMS provider how do they implement this internally. In order to achieve this, the message broker just reads the message content, but do not delete the actual message from the queue/topic until the acknowledgement is received from the message listener's process()/onMessage() method for the current transaction.
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).
My configuration:
#Bean
public ActiveMQConnectionFactory connectionFactory(){
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
connectionFactory.setBrokerURL(DEFAULT_BROKER_URL);
return connectionFactory;
}
#Bean
public DefaultMessageListenerContainer listenerContainers() {
DefaultMessageListenerContainer container = new DefaultMessageListenerContainer();
container.setConnectionFactory(connectionFactory());
//container.setConnectionFactory(connectionFactory1());
container.setClientId("consumer1");
container.setDestinationName(COMMENT_QUEUE);
container.setPubSubDomain(true);
container.setSessionTransacted(true);
container.setSubscriptionDurable(true);
container.setMessageListener(datafileSubscriber);
container.start();
return container;
}
#Bean
public DefaultMessageListenerContainer listenerContainers1() {
DefaultMessageListenerContainer container = new DefaultMessageListenerContainer();
container.setConnectionFactory(connectionFactory());
container.setClientId("consumer2");
container.setDestinationName(COMMENT_QUEUE);
container.setPubSubDomain(true);
container.setSessionTransacted(true);
container.setSubscriptionDurable(true);
container.setMessageListener(datafileSubscriber);
container.start();
return container;
}
I need the messages to be published to multiple listeners. All the listeners execute the same code. I want them to be durable. I have put setsessiontransacted true. It is a pub/sub model.
My idea is that if one listener will execute the code. Other listeners can send simply send an acknowledgement. This way they can get another message.
My assumption here:
The broker sends a message to both the listeners. One of them acknowledges immediately, while the other processes it.
Now the broker got another message. Since the 1st listener didnt send an acknowledgement, it will send the message to the 2nd listener
and it puts the message in a queue for 1st listener so that it can send whenever the 1st listener acknowledges the previous message.
My important doubt here:
Does the activemq broker send another message without all the listeners acknowledging ?
I think the concept is that each listener will have a queue maintained in the broker. As the broker get messages, it will push messages into the
queue of each individual listener. If the listener is idle, it will take the message. If it is busy processing, until the acknowledgement is sent, the message
will stay in the queue. After the acknowledgement, next message will be delievered to the listener.
I am only saying this in context of properties I have, durable subscribers, setsession transacted true.
What I have tried and failed.
I tried to set the concurrent consumer property to 2 and also set it to durable subscriber. Looks like if it is a durable subscriber, it needs a
unique client ID. So I switched to using multiple containers with concurrent consumer property 1.
EDIT :
Everything I said here is in the context of my configuration which is using durable subscribers, setsessiontransacted true and same message listener
My assumption here: The broker sends a message to both the listeners. One of them acknowledges immediately, while the other processes it.
Now the broker got another message. Since the 1st listener didnt send an acknowledgement, it will send the message to the 2nd listener and it puts the message in a queue for 1st listener so that it can send whenever the 1st listener acknowledges the previous message.
It doesn't work that way at all, the consumers/subscriptions are independent of each other. There is no "queue" for each user; just the topic; with durable subscriptions, the broker keeps track of the last message sent a consumer; when all durable subscriptions have received the message, it is deleted.
The actual process of sending the message to the consumer depends on other factors, for example, ActiveMQ supports prefetch (default 1000) which means it will send up to that number without waiting for acks.
You MUST use sessionTransacted with the DMLC so the ack is not committed until your listener completes.
concurrent consumer property to 2
As I said in the answer to your other question, increasing the concurrency makes no sense when consuming from a topic.
I have a message-driven bean which receives messages from a queue, processes them, and sends messages to another queue, with
onMessage(Message inputMessage) {
... Message processing stuff...
Connection connection = connectionFactory.createConnection();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Message outputMessage = session.createObjectMessage();
outputMessage.setJMSCorrelationID(uniqueId);
MessageProducer messageProducer = session.createProducer(outputQueue);
messageProducer.send(outputMessage);
... Some more processing...
QueueBrowser browser;
browser = session.createBrowser(outputQueue,
String.format("JMSCorrelationID='%s'", uniqueId);
}
Then, I check the queue for the uniqueId, but the message does not yet appear in the queue. After experimenting a little, I found out that the message appear in the output queue only after the onMessage method has returned.
Is this a bug? Is there a way to send the outputMessage immediately, so that I can be sure that after messageProducer.send(outputMessage) the message does appear in the outputQueue?
Seems like the flip-side to the situation here - JMS rollback
You're wanting to avoid the transactional behavior -- send immediately unrelated to the MDB transaction.
Reading the JavaEE 7 Connection.createSession() docs it sounds like there's not a good way to create a session detached from the MDB's JTA transaction. The docs go so far as to say that #schtever's answer of using session.commit() won't work.
If all this is true, maybe create some additional method that does the JMS send call. Set this additional method as transaction NOT_SUPPORTED or maybe REQUIRES_NEW.
Issue a session.commit(); after the messageProducer.send(outputMessage);
When running in an application server the JMS operations enlist in any global transaction. The solution to this is to do the send in another transactional context. The simplest thing to do is move the send into an EJB with a transaction attribute of requires new.
I have an amqp-backed channel <int-amqp:channel> to temporarily persist messages coming from a JDBC inbound adapter. when no exception is thrown, the message is ack'd and removed from the rabbit queue. when an exception occurs, the message is returned to the queue and is reprocessed continuosly. There are some circumstances where I'd like the request to go back to the queue, but in most cases I just want to log the error and acknowledge the request (remove from the rabbit queue).
I've implemented an errorHandler to deal with thrown exceptions and allow for logging and "successful" completion, however even after handling, the original request is redelivered to the rabbit queue (unacknowledged)
in the inbound-rabbit-adapter, there is a property for error-channel and handling the message on the errorChannel allows me to achieve the desired behavior described above. the only error property on the amqp channel is errorhandler.
any suggestions on a configuration that would allow me to meet my requirements?
thanks
Throw an AmqpRejectAndDontRequeueException. See 3.9 Exception Handling.
The default error handler does that for message conversion exceptions (which are likely irrecoverable).
In fact you can use that error handler by injecting a custom FatalExceptionStrategy.
I have a java class which consumes messages from a queue, sending HTTP calls to some urls. I have made some search on google and also on stackoverflow (and really sorry if i have missed any sources mentioning about the problem) but couldnt find anything in details about setRollbackOnly call.
My question is... in case I rollback, the message which is consumed from the queue will be blocking the rest of the queue and will be looping until it is processed successfully or it will be requeued at the end of the current queue?
My code which I use for consuming from the queue and sending HTTP calls is below and the whole application is running on Glassfish server:
public class RequestSenderBean implements MessageListener
{
#Resource
private MessageDrivenContext mdbContext;
public RequestSenderBean(){}
public void onMessage(final Message message)
{
try
{
if(message instanceof ObjectMessage)
{
String responseOfCall=sendHttpPost(URL, PARAMS_FROM_MESSAGE);
if(responseOfCall.startsWith("Success"))
{
//Everything is OK, do some stuff
}
else if(responseOfCall.startsWith("Failure"))
{
//Failure, do some other stuff
}
}
catch(final Exception e)
{
e.printStackTrace();
mdbContext.setRollbackOnly();
}
}
}
This is fundamental JMS/messaging knowledge.
Queues implement "load balancing" scenarios, whereby a message hits a queue and is dequed to be processed by one consumer. Increasing the number of consumers increases potential throughput of that queue's processing. Each message on a queue will be processed by one and only one consumer.
Topics provide publish-subscribe semantics: all consumers of a topic will receive the message that is pushed to the topic.
With that in mind, once a message is dequed and handed (transactionally) to a consumer, it is by no means blocking the rest of the queue if it is asynchronous (as is the case with MDBs).
As the Java EE Tutorial states:
Message Consumption
Messaging products are inherently asynchronous: There is no fundamental timing dependency between the production and the consumption of a message. However, the JMS specification uses this term in a more precise sense. Messages can be consumed in either of two ways:
Synchronously: A subscriber or a receiver explicitly fetches the message from the destination by calling the receive method. The receive method can block until a message arrives or can time out if a message does not arrive within a specified time limit.
Asynchronously: A client can register a message listener with a consumer. A message listener is similar to an event listener. Whenever a message arrives at the destination, the JMS provider delivers the message by calling the listener’s onMessage method, which acts on the contents of the message.
Because you use a MessageListener which is by definition asynchronous, you are not blocking the queue or its subsequent processing.
Also from the tutorial is the following:
Using Session Beans to Produce and to Synchronously Receive Messages
An application that produces messages or synchronously receives them can use a session bean to perform these operations. The example in An Application That Uses the JMS API with a Session Bean uses a stateless session bean to publish messages to a topic.
Because a blocking synchronous receive ties up server resources, it is not a good programming practice to use such a receive call in an enterprise bean. Instead, use a timed synchronous receive, or use a message-driven bean to receive messages asynchronously. For details about blocking and timed synchronous receives, see Writing the Clients for the Synchronous Receive Example.
As for message failure, it depends on how your queue is configured. You can set error-queues (in the case of containers like Glassfish or Weblogic) that failed messages are pushed to for later inspection. In your case, you're using setRollbackOnly which is handled thus:
7.1.2 Coding the Message-Driven Bean: MessageBean.java
The message-driven bean class, MessageBean.java, implements the
methods setMessageDrivenContext, ejbCreate, onMessage, and ejbRemove.
The onMessage method, almost identical to that of TextListener.java,
casts the incoming message to a TextMessage and displays the text. The
only significant difference is that it calls the
MessageDrivenContext.setRollbackOnly method in case of an exception.
This method rolls back the transaction so that the message will be
redelivered.
I recommend you read the Java EE Tutorial as well as the Enterprise Integration Patterns book which covers messaging concepts in good detail that's also product/technology-agnostic.