JMS synchronous Request/Reply using temporary queue timed out reading - jms

I'm currently having a problem with jms synchronous request/reply approach, this is what happens:
1.) ProgramA create a jms message, a temporary queue and set it as a replyTo.
2.) ProgramB has a listener to the message created from ProgramA, process the message and reply to it. But ProgramB needs to communicate to a 3rd party web service that sometimes takes more than 10seconds to reply, and that is the problem I set the consumer to listen for 5000 (5s) and of course it will timeout afterwards. So the message is not received.
My observation:
1.) Even though ProgramA is done reading (no reply yet, at that instant I try to delete the temporary queue). It's not able to and ProgramB was still able to write to the reply queue, but nobody's going to read that message (too late).
When I try to change 5s to 20s listen time the problem was solved, but is it the right approach?
Also is it possible for the ProgramB to not try to write to the queue when ProgramA has stop reading?
Partial codes:
Destination replyQueue = send(jmsUtil, actionDTO);
SalesOrderResponseDTO responseDTO = readReply(jmsUtil, replyQueue, actionDTO);
public Destination send(JmsSessionUtil jmsUtil, SalesOrderActionDTO soDTO) {
try {
utx.begin();
jmsUtil.send(soDTO, null, 0L, 1, Long.parseLong(configBean.getProperty("jms.payrequest.timetolive")), true);
utx.commit();
return jmsUtil.getReplyQueue();
} catch (Exception e) {
try {
utx.rollback();
} catch (Exception e1) {
}
}
return null;
}
public SalesOrderResponseDTO readReply(JmsSessionUtil jmsUtil, Destination replyQueue, SalesOrderActionDTO actionDTO) {
SalesOrderResponseDTO responseDTO = null;
try {
utx.begin();
responseDTO = (SalesOrderResponseDTO) jmsUtil.read(replyQueue);
if (responseDTO != null) {
// fires the response event
SalesOrderResponsePayload eventPayload = new SalesOrderResponsePayload();
eventPayload.setResponseDTO(responseDTO);
responseEvent.fire(eventPayload);
} else { // timeout
((TemporaryQueue) replyQueue).delete();
jmsUtil.dispose();
}
utx.commit();
return responseDTO;
} catch (Exception e) {
try {
utx.rollback();
} catch (Exception e1) {
}
}
return responseDTO;
}
public String send(MessageDTO messageDTO,
JMSQueueEnum resultNotificationQueue, Long parentProcessId,
int JMSPriority, long timeToLive, boolean hasReply)
throws JMSException, InvalidDTOException, NamingException {
try {
// Process optional parameters
messageDTO.setResultNotificationQueue(resultNotificationQueue);
messageDTO.setParentProcessId(parentProcessId);
// Wrap MessageDTO in a JMS ObjectMessage
ObjectMessage msg = MessageDTOHelper.serialize(session, messageDTO);
msg.setJMSType(messageDTO.getClass().getSimpleName());
msg.setStringProperty("DTOType", messageDTO.getClass()
.getSimpleName());
requestProducer = session.createProducer(queue);
if (hasReply) {
replyQueue = session.createTemporaryQueue();
replyConsumer = session.createConsumer(replyQueue);
msg.setJMSReplyTo(replyQueue);
}
if (JMSPriority > -1) {
requestProducer.send(msg, DeliveryMode.PERSISTENT, JMSPriority,
timeToLive);
} else {
// Send the JMS message
requestProducer.send(msg);
}
return msg.getJMSMessageID();
} catch (Exception e) {
}
return null;
}
public MessageDTO read(Destination replyQueue) throws JMSException,
NamingException {
if (replyQueue instanceof Queue) {
Message msg = replyConsumer.receive(20000);
if (msg == null) {
return null;
}
MessageDTO messageDTO = MessageDTOHelper
.deserialize((ObjectMessage) msg);
return messageDTO;
} else {
}
return null;
}

Actual question here is whether you need synchronous or asynchronous communication.
I would always prefer asynchronous, and it seems from your question that there is no need for synchronous communication neither in your case. However, if there is some reason for synchronous then you are stuck with temporary queues - you'll have to specify timeout interval and you'll face problems expressed in your question. If Program A can wait, raise the timeout interval although that's far from optimal. As far as I know, there is no possibility for Program B to check if A still listens.
In case of asynchronous communication, you have (at least) two JMS options:
Using different message queues - Program A sends the message on Queue1 and finishes, but listens (e.g. through Message Driven Bean) on Queue2 where Program B puts its response when it's done. Small drawback is usage of one extra pair of producer and consumer.
Using same message queue - Program A and Program B both send and receive messages on Queue1, but with different message selector (see description here). Basically, message selectors will filter messages for specific listener and thus enable using same queue for bidirectional communication.
See also:
JMS Synchronous Message Consumption

You could have A add a header to its message with the current timestamp + 5 secs. When B receives the response from the 3rd party, if the current time is greater than the header, it should drop the result and not send. You could use the time-to-live jms message property for this, although that is not its express purpose.

Related

why jms Client acknowledge is not working

I am trying to retrieve messages from an ActiveMQ queue. I set the session to Session.CLIENT_ACKNOWLEDGE. When I receive the message from the server it is auto-acknowledged although I have not called acknowledge on the message. Below is sample code of what I am doing:
connection = connectionFactory.createConnection();
Session session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
Destination destination = session.createQueue(queue);
consumer = session.createConsumer(destination);
connection.start();
Message message = consumer.receive(1);
The code snippet above works and I do get the message from the queue. The problem is that messages disappear from the queue although I have not acknowledged the message. Any pointers why this is happening. I have been debugging this issue almost all this afternoon with no luck. This is a Spring boot application.
I got the client_ack to work in jmsTemplate. Below is the code.
public Object retrieve() {
return this.jmsTemplate.execute(session -> {
session.recover();
MessageConsumer consumer = session.createConsumer(
this.jmsTemplate.getDestinationResolver().resolveDestinationName(session, queue, false));
try {
Message received = consumer.receive(1);
if (received != null) {
return this.messageConverter.fromMessage(received);
}
}
catch (Exception e) {
return null;
}
finally {
consumer.close();
}
return null;
}, true);
}
The issue I am now seeing is that my app does not see these messages after it restarts. I tried running session.recover() but it is not making any difference. Thanks for your help.
The working code using jmsTemplate is as below:
public Object retrieve() {
return this.jmsTemplate.execute(session -> {
MessageConsumer consumer = session.createConsumer(
this.jmsTemplate.getDestinationResolver().resolveDestinationName(session, queue, false));
try {
Message received = consumer.receive(1);
session.recover();
if (received != null) {
return this.messageConverter.fromMessage(received);
}
}
catch (Exception e) {
return null;
}
finally {
consumer.close();
}
return null;
}, true);
}
This appears to be a problem with the springframework Constants class. It uses introspection to get the integer value of CLIENT_ACKNOWLEDGE but always return a value of 1, so session is always AUTO_ACKNOWLEDGE
To anybody who might run into this issue, I did the following to resolve the issue:
Set the session to non-transacted. This is the default and only option if you are using jmsTemplate. If you are creating your own consumer, you need to create the session as non-transacted when creating the jms connection
If you are creating your own consumer, you need to set the session to CLIENT_ACKNOWLEDGE.
Check the maximum number of redeliveries to the required value. This will differ depending on the product: ActiveMQ, IBM MQ, etc.
call session.recover() after retrieving the message.
I updated the working code that is using jmsTemplate in the original post.

How to consume message from RabbitMQ dead letter queue one by one

The requirement is like to process the messages from dead letter queue by exposed a REST service API(Spring Boot).
So that once REST service is called, one message will be consumed from the DL queue and will publish in the main queue again for processing.
#RabbitListener(queues = "QUEUE_NAME") consumes the message immediately which is not required as per the scenario. The message only has to be consumed by the REST service API.
Any suggestion or solution?
I do not think RabbitListener will help here.
However you could implement this behaviour manually.
Spring Boot automatically creates RabbitMq connection factory so you could use it. When http call is made just read single message from the queue manually, you could use basic.get to synchronously get just one message:
#Autowire
private ConnectionFactory factory
void readSingleMessage() {
Connection connection = null;
Channel channel = null;
try {
connection = factory.newConnection();
channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
GetResponse response = channel.basicGet(QUEUE_NAME, true);
if (response != null) {
//Do something with the message
}
} finally {
//Check if not null
channel.close();
connection.close();
}
}
If you are using Spring; you can avoid all the boilerplate in the other answer using RabbitTemplate.receive(...).
EDIT
To manually ack/reject the message, use the execute method instead.
template.execute(channel -> {
GetResponse got = channel.basicGet("foo", false);
// ...
channel.basicAck(got.getEnvelope().getDeliveryTag(), false);
return null;
});
It's a bit lower level, but again, most of the boilerplate is taken care of for you.

ActiveMQ how to resend/retry DLQ messages programmatically

I would like to create a simple code snippet that fetches all messages from the DLQ and re-sends them to the original destination (AKA resend/retry)
It can be done easily by the ActiveMQ UI (but for a single message at a time).
There is no direct JMS API for re-sending a message from a DLQ to its original queue. In fact, the JMS API doesn't even discuss dead-letter queues. It's merely a convention used by most brokers to deal with messages that can't be consumed.
You'd need to create an actual JMS consumer to receive the message from the DLQ and then create a JMS producer to send the message back to its original queue.
It's important that you use Session.TRANSACTED mode to avoid potential message loss or duplication.
If you use Session.AUTO_ACKNOWLEDGE and there is a problem between the time the message is consumed and sent (e.g the application crashes, hardware failure, etc.) then the message could be lost due to the fact that it was already acknowledged before it was sent successfully.
If you use Session.CLIENT_ACKNOWLEDGE and there is a problem between the time the message is sent and acknowledged then the message could ultimately be duplicated due to the fact that it was already sent before it was acknowledged successfully.
Both operations should be part of the JMS transaction so that the work is atomic.
Lastly, I recommend you either invoke commit() on the transacted session for each message sent or after a small batch of messages (e.g. 10). Given that you have no idea how many messages are in the DLQ it would be unwise to process every message in a single transaction. Generally you want the transaction to be as small as possible in order to minimize the window during which an error might occur and the transaction's work will need to be performed again. Also, the larger the transaction is the more heap memory will be required on the broker to keep track of the work in the transaction. Keep in mind that you can invoke commit() on the same session as many times as you want. You don't need to create a new session for each transaction.
Retrying all messages on the DLQ is already implemented in activemq as an mbean.
You can trigger the retry method with jmxterm/jolokia
e.g
Replaying all messages on queue ActiveMQ.DLQ with jolokia
curl -XGET --user admin:admin --header "Origin: http://localhost" http://localhost:8161/api/jolokia/exec/org.apache.activemq:brokerName=localhost,destinationName=ActiveMQ.DLQ,destinationType=Queue,type=Broker/retryMessages
NOTE: You can only use this method on a queue that is marked as a DLQ. It will not work for regular queues.
Also the DLQ queue can have its 'DLQ' flag set to false if the server is restarted. It is automatically set to true when a new message is sent to the DLQ
After Justin's reply I've manually implemented the retry mechanism like so:
public void retryAllDlqMessages() throws JMSException {
logger.warn("retryAllDlqMessages starting");
logger.warn("Creating a connection to {}", activemqUrl);
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("test", "test", activemqUrl);
HashMap<String, MessageProducer> messageProducersMap = new HashMap<>();
MessageConsumer consumer = null;
try (ActiveMQConnection connection = (ActiveMQConnection) connectionFactory.createConnection();
ActiveMQSession session = (ActiveMQSession) connection.createSession(true, Session.SESSION_TRANSACTED)) {
String dlqName = getDlqName();
logger.warn("Creating a session to {}", dlqName);
ActiveMQQueue queue = (ActiveMQQueue) session.createQueue(dlqName);
logger.warn("Starting JMS Connection");
connection.start();
logger.warn("Creating a DLQ consumer");
consumer = session.createConsumer(queue);
logger.warn("Consumer start receiving");
Message message = consumer.receive(CONSUMER_RECEIVE_TIME_IN_MS);
int retriedMessages = 0;
while (message != null) {
try {
retryMessage(messageProducersMap, session, message);
retriedMessages++;
} catch (Exception e) {
logger.error("Error calling retryMessage for message = {}", message);
logger.error("Rolling back the JMS transaction...");
session.rollback();
return;
}
message = consumer.receive(CONSUMER_RECEIVE_TIME_IN_MS);
}
logger.warn("Consumer finished retrying {} messages", retriedMessages);
logger.warn("Commiting JMS Transactions of retry");
session.commit();
} finally {
if (!messageProducersMap.isEmpty()) {
logger.warn("Closing {} messageProducers in messageProducersMap", messageProducersMap.size());
for (MessageProducer producer : messageProducersMap.values()) {
producer.close();
}
}
if (consumer != null) {
logger.warn("Closing DLQ Consumer");
consumer.close();
}
}
}
private void retryMessage(HashMap<String, MessageProducer> messageProducersMap, ActiveMQSession session, Message message) {
ActiveMQObjectMessage qm = (ActiveMQObjectMessage) message;
String originalDestinationName = qm.getOriginalDestination().getQualifiedName();
logger.warn("Retry message with JmsID={} to original destination {}", qm.getJMSMessageID(), originalDestinationName);
try {
if (!messageProducersMap.containsKey(originalDestinationName)) {
logger.warn("Creating a new producer for original destination: {}", originalDestinationName);
messageProducersMap.put(originalDestinationName, session.createProducer(qm.getOriginalDestination()));
}
logger.info("Producing message to original destination");
messageProducersMap.get(originalDestinationName).send(qm);
logger.info("Message sent");
} catch (Exception e) {
logger.error("Message retry failed with exception", e);
}
}

How comes my channel.basicConsume does not wait for messages

Whenever I start the following code:
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
String exchangeName = "direct_logs";
channel.exchangeDeclare(exchangeName, "direct");
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, exchangeName, "red");
channel.basicQos(1);
final Consumer consumer = new DefaultConsumer(channel){
#Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException{
String message = new String(body, "UTF-8");
System.out.println(message);
System.out.println("message received");
}
};
channel.basicConsume(queueName, true, consumer);
It does not start an endless loop, as is implied in the documentation. Instead, it stops right away.
The only way I can have it consume for some time is to replace channel.basicConsume with a loop, as follows:
DateTime startedAt = new DateTime();
DateTime stopAt = startedAt.plusSeconds(60);
long i=0;
try {
while (stopAt.compareTo(new DateTime()) > 0) {
channel.basicConsume(queueName, true, consumer);
i++;
}
}finally {
System.out.println(new DateTime());
System.out.println(startedAt);
System.out.println(stopAt);
System.out.println(i);
}
There must be a better way to listen to messages for a while, correct? What am I missing?
It stops listening right away.
Are you sure it's stopping? What basicConsume does is register a consumer to listen to a specific queue so there is no need to execute it in a loop. You only execute it once, and the handleDelivery method of the instance of Consumer you pass will be called whenever a message arrives.
The Threads that the rabbitmq library creates should keep the JVM from exiting. In order to exit the program you should actually call connection.close()
Here is a complete receiver example from rabbitmq: https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/java/Recv.java
It's actually pretty much the same as yours.
i had the same issue. the reason was that i was calling connection.close at the end. however, the basicConsume() method does not block on the current thread, rather on other threads, so the code after it, i.e. the connection.close() is called immediately.

JMS QueueReceiver - Need to keep receiver wait for sometime even messages are available on Queue

I have to update my existing JMS Receiver program to as follows.
Existing Functionality:
My receiver class will read a message and calls a web service to process the job in one of the server once the message is received as xml.
New Functionality:
The receiver should wait for sometime until the job server is free to process a job. I tried using MessageSelectors but which is only applicable for message headers.I tried this option "message = (JMSTextMessage) mqQueueReceiver.receive(100000000000000);" but whenever i posted a message those message is read after posted into queue. But i want to keep receiver to wait for some interval which i am getting from Job server through web service call.
My Code is below:
connectionFactory = new MQQueueConnectionFactory();
connectionFactory.setHostName(config.getValue("host"));
connectionFactory.setPort(Integer.parseInt(config.getValue("port")));
connectionFactory.setTransportType(JMSC.MQJMS_TP_CLIENT_MQ_TCPIP);
connectionFactory.setQueueManager(config.getValue("manager"));
connectionFactory.setChannel(config.getValue("channel"));
queueConnection = (MQQueueConnection) connectionFactory.createQueueConnection();
queueSession = (MQQueueSession) queueConnection.createQueueSession(true, Session.CLIENT_ACKNOWLEDGE);
queue = (MQQueue) queueSession.createQueue(config.getValue("queue"));
mqQueueReceiver = (MQQueueReceiver) queueSession.createReceiver(queue);
while(true) {
if(this.stopListener) {
System.out.println("stopListener variable is changed ");
break;
}
try {
message = (JMSTextMessage) mqQueueReceiver.receive(1000);
String response = "";
if(this.nullCheckJMSTextObject(message)) {
response= soapClient.invokeWebService(message.getText(),message.getJMSCorrelationID());
if(this.nullCheckSoapResponse(response)) {
queueSession.commit();
} else {
queueSession.rollback();
queueSession.commit();
Thread.sleep(receiverWaitTime);
}
}
} catch (JMSException e) {
System.err.println("Linked Exception");
e.getLinkedException();
System.err.println("Error Code");
e.getErrorCode();
System.err.println("Cause ");
e.getCause();
System.err.println("fillTrackTrace ");
e.fillInStackTrace();
e.printStackTrace();
break;
}catch(IllegalStateException e) {
e.printStackTrace();
break;
}catch(InterruptedException e) {
e.printStackTrace();
break;
}catch(Exception e) {
e.printStackTrace();
break;
}
}
The receive(timeout) method will wait for the specified timeout period for a message to arrive on a queue. If a message arrives on a queue before the timeout, the method will return immediately with a message otherwise the method will wait till the timeout period and then return with no message. You will see a 2033 exception.
The timeout specified for the receive() call indicates how long the receive method must wait for messages before it can return. The timeout specified is not to delay the message delivery. If there is a message, the method will return immediately.
I think your logic can be modified to alter the order of execution. Change the code to receive messages only when your web service is ready to process messages.

Resources