I have configured JMS topic in JBOSS_EAP_7.0 and write a simple java code to create a message producer. I have the following stateless bean
#Stateless
public class ExchangeSenderFacadeWrapperBean {
private static final OMSLogHandlerI logger = new Log4j2Handler("ClientSenderFacadeBean");
#Resource(lookup = "java:/JmsXA") // inject ConnectionFactory (more)
protected ConnectionFactory factory;
#Resource(lookup = "java:/jms/topic/ORD_CLINT_PUSH")
protected Topic target;
private Connection connection = null;
private Session session = null;
public void sendMessage(String message) {
MessageProducer producer= null;
try {
if(connection==null){ //todo verify
connection = factory.createConnection();
}
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
producer = session.createProducer(target);
producer.setDisableMessageID(true);
TextMessage outmsg = session.createTextMessage(message);
producer.send(outmsg);
logger.info("Message was sent to Topic");
producer.setTimeToLive(900000);//15min //todo
} catch (Exception e) {
logger.error(" Error when sending order to jboss:", e);
throw new OMSCoreRuntimeException(e.getMessage(), e);
} finally {
try {
if (producer != null)
producer.close();
} catch (JMSException e) {
logger.warn("\n jms producer close error:",e);
}
try {
if (session != null)
session.close();
} catch (JMSException e) {
logger.warn("\n jms session close error:",e);
}
}
}
This works fine until i made simple change to move sendMessage(String message) method to pojo class as follow.
#Stateless(name = "ExchangeSenderFacadeBean")
#Local({ExchangeSenderFacadeLocalI.class})
public class ExchangeSenderFacadeWrapperBean implements ExchangeSenderFacadeLocalI {
#Resource(lookup = "java:/JmsXA") // inject ConnectionFactory (more)
protected ConnectionFactory factory;
#EJB(beanName = "BeanRegistryLoader")
protected BeanRegistryLoader omsRegistryBean;
protected BeanRegistryCore beanRegistryCore;
#Resource(lookup = "java:/jms/queue/ToExchange")
protected Queue target;
private ExchangeSenderFacadeCoreI exchangeSenderFacadeCore;
#Override
public void sendToExchange(ExchangeMessage exchangeMessage) {
exchangeSenderFacadeCore.sendToExchange(exchangeMessage);
}
#PostConstruct
public void init() {
beanRegistryCore = omsRegistryBean.registry();
if (exchangeSenderFacadeCore == null) {
exchangeSenderFacadeCore = ((BeanRegistryCore) omsRegistryBean.registry()).getExchangeSenderFacadeCoreI();
exchangeSenderFacadeCore.setBeanRegistryCore(omsRegistryBean.registry());
exchangeSenderFacadeCore.setFactory(factory);
exchangeSenderFacadeCore.setTargetQueue(target);
}
}
}
ConnectionFactory and target Queue variables set inside EJB PostConstruct method and pojo class looks like follow, which now contains logic to create and publish method to EJB queue
public class ExchangeSenderFacadeCore implements ExchangeSenderFacadeCoreI {
private static final OMSLogHandlerI logger = new Log4j2HndlAdaptor("ExchangeSenderFacadeCore");
private BeanRegistryCore beanRegistryCore;
private ConnectionFactory factory;
private Connection connection = null;
private Session session = null;
private long ttl = 900000;
protected Queue targetQueue;
public ExchangeSenderFacadeCore() {
if (System.getProperty(OMSConst.SYS_PROPERTY_JMS_TTL) != null && System.getProperty(OMSConst.SYS_PROPERTY_JMS_TTL).length() > 0) {
ttl = Long.parseLong(System.getProperty(OMSConst.SYS_PROPERTY_JMS_TTL));
}
logger.info("LN:103", "==JMS Topic TTL:" + ttl);
}
#Override
public void processSendToExchange(ExchangeMessage exchangeMessage) {
sendToExchange(exchangeMessage);
}
public boolean isParallelRunEnabled() {
Object isParallelRun = beanRegistryCore.getCacheAdaptorI().cacheGet(OMSConst.DEFAULT_TENANCY_CODE, OMSConst.APP_PARAM_IS_PARALLEL_RUN, CACHE_NAMES.SYS_PARAMS_CACHE_CORE);
if (isParallelRun != null && String.valueOf(isParallelRun).equals(OMSConst.STRING_1)) {
return true;
}
return false;
}
#Override
public void sendToExchange(ExchangeMessage exchangeMessage) {
MessageProducer producer = null;
try {
if (isParallelRunEnabled()) {
logger.info("LN:66", "== Message send to exchange skipped,due to parallel run enabled");
return;
}
if (connection == null) {
connection = factory.createConnection();
}
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
producer = session.createProducer(targetQueue);
producer.setDisableMessageID(true);
Message message = beanRegistryCore.getJmsExchangeMsgTransformerI().transformToJMSMessage(session, exchangeMessage);
producer.send(message);
producer.setTimeToLive(ttl);//default 15min
logger.elkLog("78", "-1", LogEventsEnum.SENT_TO_EXCHANGE, exchangeMessage.toString());
} catch (Exception e) {
logger.error("LN:80", " Error when sending order to exchange:", e);
throw new OMSCoreRuntimeException(e.getMessage(), e);
} finally {
try {
if (producer != null)
producer.close();
} catch (JMSException e) {
logger.error("LN:87", "JMS producer close error:", e);
}
try {
if (session != null)
session.close();
} catch (JMSException e) {
logger.error("LN:93", "JMS session close error:", e);
}
}
}
#Override
public void processSendToExchangeSync(ExchangeMessage exchangeMessage) {
}
#Override
public BeanRegistryCore getBeanRegistryCore() {
return beanRegistryCore;
}
#Override
public void setBeanRegistryCore(BeanRegistryCore beanRegistryCore) {
this.beanRegistryCore = beanRegistryCore;
}
#Override
public ConnectionFactory getFactory() {
return factory;
}
#Override
public void setFactory(ConnectionFactory factory) {
this.factory = factory;
}
#Override
public Queue getTargetQueue() {
return targetQueue;
}
#Override
public void setTargetQueue(Queue targetQueue) {
this.targetQueue = targetQueue;
}
}
But when i execute moderated code it gives me following error
javax.ejb.EJBTransactionRolledbackException: Producer is closed
Any possible fixes?
After a deep search deep into the problem, I found this https://developer.jboss.org/wiki/ShouldICacheJMSConnectionsAndJMSSessions article posted on one of JBOSS developer thread. This explains clearly the reason for caching connection and other JMS related resources being an anti-pattern for JMS code is running in a JEE application server.
In nutshell JCA layer pools JMS connections and JMS sessions. So when you call createConnection() or createSession(), then, in most cases it's not really calling the actual JMS implementation to actually create a new JMS connection or JMS session, it's just returning one from its own internal cache.
Additionally JBOSS server too manages stateless session bean pool. A stateless session bean is available on the connection pool only after you are done with the purpose of it, but not prior. Meantime Connection (Either JMS newly created or Cached) used to create JMS Session (session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)) inside stateless session bean, also done with its purpose and too available on JCA layer connection pool. Therefore calling cached connection inside stateless EJB class as follow will not give you an exception even though it is not recommended by Oracle.
public void sendToExchange(ExchangeMessage exchangeMessage) {
MessageProducer producer = null;
try {
if (isParallelRunEnabled()) {
logger.info("LN:66", "== Message send to exchange skipped,due to parallel run enabled");
return;
}
if (connection == null) {
connection = factory.createConnection();
}
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
producer = session.createProducer(targetQueue);
producer.setDisableMessageID(true);
Message message = beanRegistryCore.getJmsExchangeMsgTransformerI().transformToJMSMessage(session, exchangeMessage);
producer.send(message);
producer.setTimeToLive(ttl);//default 15min
logger.elkLog("78", "-1", LogEventsEnum.SENT_TO_EXCHANGE, exchangeMessage.toString());
} catch (Exception e) {
logger.error("LN:80", " Error when sending order to exchange:", e);
throw new OMSCoreRuntimeException(e.getMessage(), e);
} finally {
try {
if (producer != null)
producer.close();
} catch (JMSException e) {
logger.error("LN:87", "JMS producer close error:", e);
}
try {
if (session != null)
session.close();
} catch (JMSException e) {
logger.error("LN:93", "JMS session close error:", e);
}
}
}
But in this case, since the same POJO class instance can be used on multiple occasions as bellow. It does not guarantee that the connection is freed and available in the JCA layer connection pool and gives exceptions.
#PostConstruct
public void init() {
beanRegistryCore = omsRegistryBean.registry();
if (exchangeSenderFacadeCore == null) {
exchangeSenderFacadeCore = ((BeanRegistryCore) omsRegistryBean.registry()).getExchangeSenderFacadeCoreI();
exchangeSenderFacadeCore.setBeanRegistryCore(omsRegistryBean.registry());
exchangeSenderFacadeCore.setFactory(factory);
exchangeSenderFacadeCore.setTargetQueue(target);
}
}
Related
Using Wildfly and JMS via ActiveMQ I got following exception.
javax.ejb.EJBTransactionRolledbackException: Producer is closed
I have the following stateless bean
#Stateless(name = "ExchangeSenderFacadeBean")
#Local({ExchangeSenderFacadeLocalI.class})
public class ExchangeSenderFacadeWrapperBean implements ExchangeSenderFacadeLocalI {
#Resource(lookup = "java:/JmsXA") // inject ConnectionFactory (more)
protected ConnectionFactory factory;
#EJB(beanName = "BeanRegistryLoader")
protected BeanRegistryLoader omsRegistryBean;
protected BeanRegistryCore beanRegistryCore;
#Resource(lookup = "java:/jms/queue/ToExchange")
protected Queue target;
private ExchangeSenderFacadeCoreI exchangeSenderFacadeCore;
#Override
public void sendToExchange(ExchangeMessage exchangeMessage) {
exchangeSenderFacadeCore.sendToExchange(exchangeMessage);
}
#PostConstruct
public void init() {
beanRegistryCore = omsRegistryBean.registry();
if (exchangeSenderFacadeCore == null) {
exchangeSenderFacadeCore = ((BeanRegistryCore) omsRegistryBean.registry()).getExchangeSenderFacadeCoreI();
exchangeSenderFacadeCore.setBeanRegistryCore(omsRegistryBean.registry());
exchangeSenderFacadeCore.setFactory(factory);
exchangeSenderFacadeCore.setTargetQueue(target);
}
}
}
And I use simple java class to create a method which produces a message and send it to the destination as follow
public class ExchangeSenderFacadeCore implements ExchangeSenderFacadeCoreI {
private static final OMSLogHandlerI logger = new Log4j2HndlAdaptor("ExchangeSenderFacadeCore");
private BeanRegistryCore beanRegistryCore;
private ConnectionFactory factory;
private Connection connection = null;
private Session session = null;
private long ttl = 900000;
protected Queue targetQueue;
public ExchangeSenderFacadeCore() {
if (System.getProperty(OMSConst.SYS_PROPERTY_JMS_TTL) != null && System.getProperty(OMSConst.SYS_PROPERTY_JMS_TTL).length() > 0) {
ttl = Long.parseLong(System.getProperty(OMSConst.SYS_PROPERTY_JMS_TTL));
}
logger.info("LN:103", "==JMS Topic TTL:" + ttl);
}
#Override
public void processSendToExchange(ExchangeMessage exchangeMessage) {
sendToExchange(exchangeMessage);
}
public boolean isParallelRunEnabled() {
Object isParallelRun = beanRegistryCore.getCacheAdaptorI().cacheGet(OMSConst.DEFAULT_TENANCY_CODE, OMSConst.APP_PARAM_IS_PARALLEL_RUN, CACHE_NAMES.SYS_PARAMS_CACHE_CORE);
if (isParallelRun != null && String.valueOf(isParallelRun).equals(OMSConst.STRING_1)) {
return true;
}
return false;
}
#Override
public void sendToExchange(ExchangeMessage exchangeMessage) {
MessageProducer producer = null;
try {
if (isParallelRunEnabled()) {
logger.info("LN:66", "== Message send to exchange skipped,due to parallel run enabled");
return;
}
if (connection == null) {
connection = factory.createConnection();
}
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
producer = session.createProducer(targetQueue);
producer.setDisableMessageID(true);
Message message = beanRegistryCore.getJmsExchangeMsgTransformerI().transformToJMSMessage(session, exchangeMessage);
producer.send(message);
producer.setTimeToLive(ttl);//default 15min
logger.elkLog("78", "-1", LogEventsEnum.SENT_TO_EXCHANGE, exchangeMessage.toString());
} catch (Exception e) {
logger.error("LN:80", " Error when sending order to exchange:", e);
throw new OMSCoreRuntimeException(e.getMessage(), e);
} finally {
try {
if (producer != null)
producer.close();
} catch (JMSException e) {
logger.error("LN:87", "JMS producer close error:", e);
}
try {
if (session != null)
session.close();
} catch (JMSException e) {
logger.error("LN:93", "JMS session close error:", e);
}
}
}
#Override
public void processSendToExchangeSync(ExchangeMessage exchangeMessage) {
}
#Override
public BeanRegistryCore getBeanRegistryCore() {
return beanRegistryCore;
}
#Override
public void setBeanRegistryCore(BeanRegistryCore beanRegistryCore) {
this.beanRegistryCore = beanRegistryCore;
}
#Override
public ConnectionFactory getFactory() {
return factory;
}
#Override
public void setFactory(ConnectionFactory factory) {
this.factory = factory;
}
#Override
public Queue getTargetQueue() {
return targetQueue;
}
#Override
public void setTargetQueue(Queue targetQueue) {
this.targetQueue = targetQueue;
}
}
ExchangeSenderFacadeCoreI is interface class but when I execute this code I get above exception but if I move sendToExchange() method in ExchangeSenderFacadeCore to ExchangeSenderFacadeWrapperBean class then the error will disappear. Can anyone tell me the exact reason for this scenario
After a deep search deep into the problem, I found this
https://developer.jboss.org/wiki/ShouldICacheJMSConnectionsAndJMSSessions article posted on one of JBOSS developer thread. This explains clearly the reason for caching connection and other JMS related resources being an anti-pattern for JMS code is running in a JEE application server.
In nutshell JCA layer pools JMS connections and JMS sessions. So when you call createConnection() or createSession(), then, in most cases it's not really calling the actual JMS implementation to actually create a new JMS connection or JMS session, it's just returning one from its own internal cache.
Additionally JBOSS server too manages stateless session bean pool. A stateless session bean is available on the connection pool only after you are done with the purpose of it, but not prior. Meantime Connection (Either JMS newly created or Cached) used to create JMS Session (session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)) inside stateless session bean, also done with its purpose and too available on JCA layer connection pool. Therefore calling cached connection inside stateless EJB class as follow will not give you an exception even though it is not recommended by Oracle.
public void sendToExchange(ExchangeMessage exchangeMessage) {
MessageProducer producer = null;
try {
if (isParallelRunEnabled()) {
logger.info("LN:66", "== Message send to exchange skipped,due to parallel run enabled");
return;
}
if (connection == null) {
connection = factory.createConnection();
}
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
producer = session.createProducer(targetQueue);
producer.setDisableMessageID(true);
Message message = beanRegistryCore.getJmsExchangeMsgTransformerI().transformToJMSMessage(session, exchangeMessage);
producer.send(message);
producer.setTimeToLive(ttl);//default 15min
logger.elkLog("78", "-1", LogEventsEnum.SENT_TO_EXCHANGE, exchangeMessage.toString());
} catch (Exception e) {
logger.error("LN:80", " Error when sending order to exchange:", e);
throw new OMSCoreRuntimeException(e.getMessage(), e);
} finally {
try {
if (producer != null)
producer.close();
} catch (JMSException e) {
logger.error("LN:87", "JMS producer close error:", e);
}
try {
if (session != null)
session.close();
} catch (JMSException e) {
logger.error("LN:93", "JMS session close error:", e);
}
}
}
But in this case, since the same POJO class instance can be used on multiple occasions as bellow. It does not guarantee that connection is freed and available in the JCA layer connection pool and gives exceptions.
#PostConstruct
public void init() {
beanRegistryCore = omsRegistryBean.registry();
if (exchangeSenderFacadeCore == null) {
exchangeSenderFacadeCore = ((BeanRegistryCore) omsRegistryBean.registry()).getExchangeSenderFacadeCoreI();
exchangeSenderFacadeCore.setBeanRegistryCore(omsRegistryBean.registry());
exchangeSenderFacadeCore.setFactory(factory);
exchangeSenderFacadeCore.setTargetQueue(target);
}
}
I want to receive chunk of messages from Queue within some timeLimit(Ex : 300 millisec after receiving the 1st message) using DefaultMessageListenerConatiner Of Spring (By overriding doReceiveAndExecute) as mentioned in the link.
I can group the messages of my batch size i.e 20 when the queue is having too many messages and I can receive less than 20 messages when there are very less messages in Queue.
Issue :
I see it takes too much time(sometimes 1 sec and sometime 2 secs and more) for sending the messages to Listener even when the queue is full.
When I try with DefaultMessageListenerConatiner as such to receive single messages concurrently, I see the messages are received in a delay of few milliseconds(like 1 millisec or max 30 to 60 millisec)
I didn't specify transactionTimeout or receiveTimeout and I didn't link any transactionManager as well.
Can Springers please help me to find where the timeOut can be specified or How can I redeuce the time delay?
BatchMessageListenerContainer :
package com.mypackage;
import javax.jms.Session;
import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import java.util.ArrayList;
import java.util.List;
import org.springframework.jms.listener.DefaultMessageListenerContainer;
import org.springframework.jms.connection.ConnectionFactoryUtils;
import org.springframework.jms.support.JmsUtils;
import org.springframework.transaction.TransactionStatus;
/**
* Listener Container that allows batch consumption of messages. Works only with transacted sessions
*/
public class BatchMessageListenerContainer extends DefaultMessageListenerContainer {
public static final int DEFAULT_BATCH_SIZE = 20;
private int batchSize = DEFAULT_BATCH_SIZE;
public BatchMessageListenerContainer() {
super();
setSessionTransacted(true);
}
/**
* #return The batch size on this container
*/
public int getBatchSize() {
return batchSize;
}
/**
* #param batchSize The batchSize of this container
*/
public void setBatchSize(int batchSize) {
this.batchSize = batchSize;
}
/**
* The doReceiveAndExecute() method has to be overriden to support multiple-message receives.
*/
#Override
protected boolean doReceiveAndExecute(Object invoker, Session session, MessageConsumer consumer,
TransactionStatus status) throws JMSException {
Connection conToClose = null;
MessageConsumer consumerToClose = null;
Session sessionToClose = null;
try {
Session sessionToUse = session;
MessageConsumer consumerToUse = consumer;
if (sessionToUse == null) {
Connection conToUse = null;
if (sharedConnectionEnabled()) {
conToUse = getSharedConnection();
}
else {
conToUse = createConnection();
conToClose = conToUse;
conToUse.start();
}
sessionToUse = createSession(conToUse);
sessionToClose = sessionToUse;
}
if (consumerToUse == null) {
consumerToUse = createListenerConsumer(sessionToUse);
consumerToClose = consumerToUse;
}
List<Message> messages = new ArrayList<Message>();
int count = 0;
Message message = null;
// Attempt to receive messages with the consumer
do {
message = receiveMessage(consumerToUse);
if (message != null) {
messages.add(message);
}
}
// Exit loop if no message was received in the time out specified, or
// if the max batch size was met
while ((message != null) && (++count < batchSize));
if (messages.size() > 0) {
// Only if messages were collected, notify the listener to consume the same.
try {
doExecuteListener(sessionToUse, messages);
sessionToUse.commit();
}
catch (Throwable ex) {
handleListenerException(ex);
if (ex instanceof JMSException) {
throw (JMSException) ex;
}
}
return true;
}
// No message was received for the period of the timeout, return false.
noMessageReceived(invoker, sessionToUse);
return false;
}
finally {
JmsUtils.closeMessageConsumer(consumerToClose);
JmsUtils.closeSession(sessionToClose);
ConnectionFactoryUtils.releaseConnection(conToClose, getConnectionFactory(), true);
}
}
protected void doExecuteListener(Session session, List<Message> messages) throws JMSException {
if (!isAcceptMessagesWhileStopping() && !isRunning()) {
if (logger.isWarnEnabled()) {
logger.warn("Rejecting received messages because of the listener container "
+ "having been stopped in the meantime: " + messages);
}
rollbackIfNecessary(session);
throw new JMSException("Rejecting received messages as listener container is stopping");
}
#SuppressWarnings("unchecked")
SessionAwareBatchMessageListener<Message> lsnr = (SessionAwareBatchMessageListener<Message>) getMessageListener();
try {
lsnr.onMessages(session, messages);
}
catch (JMSException ex) {
rollbackOnExceptionIfNecessary(session, ex);
throw ex;
}
catch (RuntimeException ex) {
rollbackOnExceptionIfNecessary(session, ex);
throw ex;
}
catch (Error err) {
rollbackOnExceptionIfNecessary(session, err);
throw err;
}
}
#Override
protected void checkMessageListener(Object messageListener) {
if (!(messageListener instanceof SessionAwareBatchMessageListener<?>)) {
throw new IllegalArgumentException("Message listener needs to be of type ["
+ SessionAwareBatchMessageListener.class.getName() + "]");
}
}
#Override
protected void validateConfiguration() {
if (batchSize <= 0) {
throw new IllegalArgumentException("Property batchSize must be a value greater than 0");
}
}
public void setSessionTransacted(boolean transacted) {
if (!transacted) {
throw new IllegalArgumentException("Batch Listener requires a transacted Session");
}
super.setSessionTransacted(transacted);
}
}
SessionAwareBatchMessageListener:
package com.mypackage;
import java.util.List;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
public interface SessionAwareBatchMessageListener<M extends Message> {
/**
* Perform a batch action with the provided list of {#code messages}.
*
* #param session JMS {#code Session} that received the messages
* #param messages List of messages
* #throws JMSException JMSException thrown if there is an error performing the operation.
*/
public void onMessages(Session session, List<M> messages) throws JMSException;
}
Bean in applicationContext.xml:
<bean id="myMessageListener" class="org.mypackage.MyMessageListener">
<bean id="jmsContainer" class="com.mypackage.BatchMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destinationName" ref="queue"/>
<property name="messageListener" ref="myMessageListener"/>
<property name ="concurrentConsumers" value ="10"/>
<property name ="maxConcurrentConsumers" value ="50"/>
</bean>
MyMessageListner :
package org.mypackage;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import org.mypackage.service.MyService;
public class MyMessageListener implements SessionAwareBatchMessageListener<TextMessage> {
#Autowired
private MyService myService;
#Override
public void onMessage(Session session, List<TextMessage> messages) {
try {
for(TextMessage tm :messages) {
TextMessage textMessage = (TextMessage) message;
// parse the message and add to list
}
//process list of Objects to DB
} catch (JMSException e1) {
e1.printStackTrace();
}
}
}
i think that the time spent before sending the message to the consumer was caused by your while loop, because you're awaiting each time the list to be full but this one is only filled by the current thread since it is created inside the doReceiveAndExecute method!
// Exit loop if no message was received in the time out specified, or
// if the max batch size was met
while ((message != null) && (++count < batchSize));
maybe this can do it well :
...
List<Message> messages = Collections.synchronizedList(new ArrayList<Message>());
#Override
protected boolean doReceiveAndExecute(Object invoker, Session session, MessageConsumer consumer,
TransactionStatus status) throws JMSException {
Connection conToClose = null;
MessageConsumer consumerToClose = null;
Session sessionToClose = null;
try {
Session sessionToUse = session;
MessageConsumer consumerToUse = consumer;
if (sessionToUse == null) {
Connection conToUse = null;
if (sharedConnectionEnabled()) {
conToUse = getSharedConnection();
}
else {
conToUse = createConnection();
conToClose = conToUse;
conToUse.start();
}
sessionToUse = createSession(conToUse);
sessionToClose = sessionToUse;
}
if (consumerToUse == null) {
consumerToUse = createListenerConsumer(sessionToUse);
consumerToClose = consumerToUse;
}
Message message = null;
// Attempt to receive messages with the consumer
do {
message = receiveMessage(consumerToUse);
if (message != null) {
messages.add(message);
}
}
if (messages.size() >= batchSize)) {
synchronized (messages) {
// Only if messages were collected, notify the listener to consume the same.
try {
doExecuteListener(sessionToUse, messages);
sessionToUse.commit();
// clear the list!!
messages.clear();
}
catch (Throwable ex) {
handleListenerException(ex);
if (ex instanceof JMSException) {
throw (JMSException) ex;
}
}
}
return true;
}
// No message was received for the period of the timeout, return false.
noMessageReceived(invoker, sessionToUse);
return false;
}
finally {
JmsUtils.closeMessageConsumer(consumerToClose);
JmsUtils.closeSession(sessionToClose);
ConnectionFactoryUtils.releaseConnection(conToClose, getConnectionFactory(), true);
}
}
We have a java web application that sends (JobsController.java) and receives messages (JMSMessageListener.java) via JMS. After running the application under constant load for 24 hours and taking heap dumps, I observe a constant increase in memory usage that the application does not let go when in idle state. I know that this is going to cause a java heap out of memory issue.
JobsController is an ejb stateless bean and its resources are destroyed correctly after each call.
JMSMessageListener gets handled by ejb global bean pool and its instance is reused.
The suspects i can see from the java heap dump are
EJB bean injection is causing a memory leak
https://blog.akquinet.de/2017/01/04/dont-get-trapped-into-a-memory-leak-using-cdi-instance-injection/
ActiveMQConnection.finalize(). If it is the culprit than it must
happen to all those wildfly activemq deployments. Any hint is
appreciated.
ActiveMQConnection.java
#Override
protected final void finalize() throws Throwable {
if (!closed) {
if (this.factoryReference.isFinalizeChecks()) {
ActiveMQJMSClientLogger.LOGGER.connectionLeftOpen(creationStack);
}
close();
}
JobsController
#Stateless
public class JobsController {
#Inject
private JMSContext jmsContext;
private Connection connection;
private Session session;
private MessageProducer jmsProducer;
#Resource(lookup = "java:/ConnectionFactory")
private ConnectionFactory connectionFactory;
#Resource(lookup = JAVA_JMS_JOB_QUEUE)
private Queue jobQueue;
#Resource(lookup = JAVA_JMS_QUEUE)
private Queue progressQueue;
#PreDestroy
void release() {
try {
if (jmsProducer != null) {
jmsProducer.close();
}
if (session != null) {
session.close();
}
if (jmsContext != null) {
jmsContext.close();
}
if (connection !=null) {
connection.close();
}
} catch (JMSException e) {
LOG.warn("failed to close JMS resources: {}", e.getMessage());
}
}
public synchronized MessageProducer getJmsProducer() {
if (jmsProducer == null) {
try {
connection = connectionFactory.createConnection();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
jmsProducer = session.createProducer(jobQueue);
connection.start();
} catch (JMSException e) {
LOG.error("failed to setup JMS message producer: {}", e.getMessage());
}
}
return jmsProducer;
}
public void addMessageToProgressQueue(ProgressMessage progressMessage) {
ObjectMessage objectMessage = jmsContext.createObjectMessage(progressMessage);
try {
getJmsProducer().send(progressQueue, objectMessage);
} catch (JMSException e) {
LOG.error("failed to send progress message {}: {}", objectMessage, e.getMessage());
}
}
}
JMSMessageListener.java
#MessageDriven(name = "JMSMessageListener", mappedName = JAVA_JMS_QUEUE, activationConfig = {
#ActivationConfigProperty(
propertyName = "acknowledgeMode",
propertyValue = "Auto-acknowledge"),
#ActivationConfigProperty(
propertyName = "destinationType",
propertyValue = "javax.jms.Queue"),
#ActivationConfigProperty(
propertyName = "destination",
propertyValue = JAVA_JMS_QUEUE)
})
public class JMSMessageListener implements MessageListener {
private static Logger LOG = LoggerFactory.getLogger(JMSMessageListener.class);
#EJB
private JobsController jobsController;
private final ObjectMapper progressMessageMapper;
public JMSMessageListener() {
progressMessageMapper = new ObjectMapper();
progressMessageMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
}
#Override
public void onMessage(Message message) {
ProgressMessage progressMessage = null;
try {
if (message instanceof BytesMessage) {
BytesMessage bytesMessage = (BytesMessage) message;
int TEXT_LENGTH = new Long(bytesMessage.getBodyLength()).intValue();
byte[] textBytes = new byte[TEXT_LENGTH];
bytesMessage.readBytes(textBytes, TEXT_LENGTH);
String progressText = new String(textBytes, "UTF-8");
progressText = progressText.replaceAll("'totalSteps': None", "'totalSteps': 0");
progressMessage = progressMessageMapper.readValue(progressText, ProgressMessage.class);
} else if (message instanceof ObjectMessage) {
progressMessage = message.getBody(ProgressMessage.class);
}
if (progressMessage != null) {
jobsController.sendProgressMessage(progressMessage);
} else {
LOG.error("An empty progress message was received");
}
} catch (JMSException | IOException e) {
LOG.error("failed to process progress message: {}", e.getMessage(), e);
}
}
}
Couple of things:
You're injecting a JMSContext but never using it (at least in the code that you pasted). This seems like an error.
If you aren't going to use the injected JMSContext but rather use the injected ConnectionFactory then you should be injecting "java:/JmsXA" rather than "java:/ConnectionFactory" since it is a &tl;pooled-connection-factory>. Creating a connection for every message sent with "java:/ConnectionFactory" is an anti-pattern since it is not pooled. Also, I assume you'd want to use an XA transaction so that message consumption and sending in your MDB is atomic and that won't work with "java:/ConnectionFactory".
I am newbie in JMS and OpenMQ. I am using both in my code with multi threading but I am getting this weird output when executing the code. Can anyone please explain how this statements in multi threading executes?
public void receiveData(){
try {
Hashtable<String, String> contextParams = new Hashtable<>();
contextParams.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.fscontext.RefFSContextFactory");
contextParams.put(Context.PROVIDER_URL, "file:///C:/jndiStorage");
Context ctx = new InitialContext(contextParams);
QueueReceiver server = new QueueReceiver(ctx, "firstQueueConnectionFactory", "firstQueue");
server.start();
System.out.println("staement 1");
}catch (NamingException e) {
System.out.println("JNDI Problem. Check used hostname and/or Glassfish configuration.");
e.printStackTrace();
}
}
And my other method is something like this which return list of objects received
public List<Ticket> getAllTickets() throws TicketException {
receiveData();
System.out.println("statement 2");
return JMSLocalTicketStore.entrySet().stream().map(entry -> (Ticket) entry.getValue().clone()).collect(Collectors.toList());
}
QueueReceiver class which is extending Thread class is something like this :
public QueueReceiver(Context ctx, String connFactoryName, String queueName)
throws NamingException {
// Look up the connection factory object in the JNDI context provided
connFactory = (ConnectionFactory) ctx.lookup(connFactoryName);
// Look up the Destination in the JNDI context
destination = (Destination) ctx.lookup(queueName);
}
private void startServer() {
System.out.println("\t [RECEIVER]: Start waiting for messages");
active = true;
// Create JMS Context
try (JMSContext jmsContext = connFactory.createContext()) {
// Create a JMSConsumer to receive message
JMSConsumer consumer = jmsContext.createConsumer(destination);
while (active) {
Ticket ticket = (Ticket) consumer.receiveBody(Ticket.class, 5000);
// if no message is received with 5 secs messsage == null
if (ticket != null) {
System.out.println("Reveicer statement 1");
stopServer();
}else{
empty();
}
}
}
System.out.println("\t [RECEIVER]: Stopped.");
}
public void empty(){
active = false;
System.out.println("No message in the OpenMQ");
}
public void stopServer() {
active = false;
System.out.println("\t [RECEIVER]: Stopping to listen for messages.");
}
#Override
public void run() {
startServer();
}
}
And my output is :
staement 1
statement 2
[RECEIVER]: Start waiting for messages
Reveicer statement 1
[RECEIVER]: Stopping to listen for messages.
[RECEIVER]: Stopped.
Why it is not printing all the receiver statements before statement 2 of getAllTickets()
Below ActiveMQ implementation is present in code. Sometimes, system stops working and become very slow. When I checked thread dump using JavaMelody - I have seen too many threads are on Runnable state for long time and is not being terminated.
ActiveMQ version - activemq-all-5.3.0.jar
Please refer below code :
Broker :
public class ActiveMQ extends HttpServlet {
private static final long serialVersionUID = -1234568008764323456;
private static final Logger logger = Logger.getLogger(ActiveMQ.class.getName());
public Listener listener;
private String msgBrokerUrl = "tcp://localhost:61602";
public BrokerService broker = null;
public TransportConnector connector = null;
#Override
public void init() throws ServletException {
try {
broker = new BrokerService();
broker.setPersistent(false);
broker.setUseJmx(false);
connector = broker.addConnector(msgBrokerUrl);
broker.setUseShutdownHook(true);
System.out.println("BROKER LOADED");
broker.start();
broker.deleteAllMessages();
listener = new Listener();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Listener:
public class Listener implements MessageListener {
private String msgQueueName = "jms/queue/MessageQueue";
public Session session;
public Destination adminQueue;
public static String id;
public ActiveMQConnection connection;
public MessageConsumer consumer = null;
public Listener() {
try {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
new URI("failover://(" + "tcp://localhost:61602" + "?wireFormat.cacheEnabled=false"
+ "&wireFormat.maxInactivityDuration=0&wireFormat.tightEncodingEnabled=true)?maxReconnectDelay=1000"));
connection = (ActiveMQConnection) connectionFactory.createConnection();
connection.start();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
adminQueue = session.createQueue(msgQueueName);
id = new Timestamp(new Date().getTime()).toString();
consumer = this.session.createConsumer(this.adminQueue, "ID='" + id + "'");
consumer.setMessageListener(this);
} catch (JMSException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
#SuppressWarnings("unchecked")
#Override
public void onMessage(Message message) {
TextMessage msg = (TextMessage) message;
try {
String xmlMsg = msg.getText();
// business logic
} catch (JMSException ex) {
ex.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
Producer :
public class Producer {
private static String url = "tcp://localhost:61602";
private static String msgQueueName = "jms/queue/MessageQueue";
public ConnectionFactory connectionFactory = null;
public Connection connection = null;
public Session session = null;
public Destination destination = null;
public Producer() {
connectionFactory = new ActiveMQConnectionFactory(url);
}
public void sendResponse(String xml, DataBean objDataBean) {
sendToQueue(xml, msgQueueName, objDataBean);
}
private void sendToQueue(String xml, String msgQueueName, DataBean obj) {
try {
Connection connection = connectionFactory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue(msgQueueName);
MessageProducer producer = session.createProducer(destination);
TextMessage message = session.createTextMessage(xml);
message.setJMSExpiration(1000);
message.setStringProperty(obj.getMsgKey(), obj.getMsgValue());
producer.send(message);
xml = null;
session.close();
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
for (int msg = 0; msg < 20; msg++) {
DataBean obj = getData();
new Producer().sendResponse(xml, obj);
;
}
}
}
Hanging Threads Exception details :
Type 1 :
ActiveMQ Transport: tcp:///127.0.0.1:41818
java.net.SocketInputStream.socketRead0(Native Method)
java.net.SocketInputStream.read(SocketInputStream.java:152)
java.net.SocketInputStream.read(SocketInputStream.java:122)
org.apache.activemq.transport.tcp.TcpBufferedInputStream.fill(TcpBufferedInputStream.java:50)
org.apache.activemq.transport.tcp.TcpBufferedInputStream.read(TcpBufferedInputStream.java:58)
java.io.DataInputStream.readInt(DataInputStream.java:387)
org.apache.activemq.openwire.OpenWireFormat.unmarshal(OpenWireFormat.java:272)
org.apache.activemq.transport.tcp.TcpTransport.readCommand(TcpTransport.java:210)
org.apache.activemq.transport.tcp.TcpTransport.doRun(TcpTransport.java:202)
org.apache.activemq.transport.tcp.TcpTransport.run(TcpTransport.java:185)
java.lang.Thread.run(Thread.java:745)
Type 2 :
ActiveMQ Transport: tcp://localhost/127.0.0.1:61602
java.net.SocketInputStream.socketRead0(Native Method)
java.net.SocketInputStream.read(SocketInputStream.java:152)
java.net.SocketInputStream.read(SocketInputStream.java:122)
org.apache.activemq.transport.tcp.TcpBufferedInputStream.fill(TcpBufferedInputStream.java:50)
org.apache.activemq.transport.tcp.TcpBufferedInputStream.read(TcpBufferedInputStream.java:58)
java.io.DataInputStream.readInt(DataInputStream.java:387)
org.apache.activemq.openwire.OpenWireFormat.unmarshal(OpenWireFormat.java:272)
org.apache.activemq.transport.tcp.TcpTransport.readCommand(TcpTransport.java:210)
org.apache.activemq.transport.tcp.TcpTransport.doRun(TcpTransport.java:202)
org.apache.activemq.transport.tcp.TcpTransport.run(TcpTransport.java:185)
java.lang.Thread.run(Thread.java:745)
Please could you give some hints on this issue for further investigation.
Edit:
I read few posts on internet and concluded that I must update activemq jar file and implement timeout but when I started reading about timeout setting then I got confused whether I should set timeout in producer and consumer or failover or on message or broker service. Timeout at each component has different purpose then where I should implement timeout considering above code and exception.
Creating a connection is very expensive and when you close it, the port is retained for up to 3 minutes to ensure it is shutdown cleanly.
You want to create connections only when you really have to avoid performance problems. I suggest you create the connection once, and keep that connection open unless you get an error. This can improve performance by 2 to 3 orders of magnitude.
This is a good performance tuning pattern which applies in many cases;
only create and destroy expensive resources when you really have to.
operations you perform many times should be kept to a minimum. i.e do repeatedly as little as possible.