IBM-MQ (ibm.mq.allclient:jar:9.2.2.0) Spring Boot dynamic listener fails with too many MQ connections with compcode '2' ('MQCC_FAILED') reason 2537 - spring-boot

Error : MQ call failed with compcode '2' ('MQCC_FAILED') reason '2537' ('MQRC_CHANNEL_NOT_AVAILABLE')
Start-up of the application we fetch the queue name dynamically and create a JMS listener programmatically. Messages are processed within the transaction and if the transaction fails it rolls back to the IBM-MQ queue
public DefaultJmsListenerContainerFactory mqJmsListenerContainerFactory() throws JMSException {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(ibmConnectionFactory);
factory.setDestinationResolver(new DynamicDestinationResolver());
factory.setSessionTransacted(true);
factory.setConcurrency(requestConcurrency);
return factory;
}
for (IBMmqQueue aTradeQueue : requestMQQueueNames) {//we have dynamic queue names
if(StringUtils.isEmpty(aTradeQueue.getTradeRequestName())){
continue;
}
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
endpoint.setId(aTradeQueue.getTradeRequestName());
endpoint.setDestination(aTradeQueue.getTradeRequestName());
endpoint.setMessageListener(message -> {
TransactionStatus status = jmsTransactionManager.getTransaction(null);
try {
// This starts a new transaction scope. "null" can be used to get a default transaction model
ibmmqConsumer.tradeListener(message, aTradeQueue.getTradeRequestName());
jmsTransactionManager.commit(status);
} catch (Exception e) {
jmsTransactionManager.rollback(status);
try {
LOG.error("Failed to publish to Active-MQ & rolled back to IBM-MQ : " + message.getJMSMessageID());
} catch (JMSException ex) {
ex.printStackTrace();
}
}
});
try {
registrar.setContainerFactory(this.mqJmsListenerContainerFactory());
} catch (JMSException e) {
e.printStackTrace();
}
registrar.registerEndpoint(endpoint);
}

Related

Spring Boot - JMS Connection to initialize later

I am connecting to JMS queue using t3 protocol using DefaultMessageListenerContainer and it is working fine if the t3 protocol is UP.
But If the t3 URL is down, then the Listener cannot re-register the Bean once the t3 URL is UP. I have override the Listener to make a call but not able to re-register the Bean after application started.
#Bean
public QueueConnectionFactory queueConnectionFactory() {
Context m_context = getInitialContext();
QueueConnectionFactory queueConnectionFactory = new JMSConnectionFactory();
try {
System.out.println("Connection Factory");
queueConnectionFactory = (QueueConnectionFactory) m_context
.lookup("myconnectionfactory");
} catch (Exception e) {
System.err.println("Exception Connection Factory goes down");
}
return queueConnectionFactory;
}
#Bean
public Context getInitialContext() {
try {
Properties h = new Properties();
System.out.println("getInitialContext ");
h.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
h.put(Context.PROVIDER_URL, "t3://100.21.101.12:7001");
return new InitialContext(h);
} catch (Exception e) {
System.error.println("Error at getInitialContext");
}
return null;
}
#Bean
public Queue jmsQueue() {
Context m_context = getInitialContext();
Queue jmsQueue = new MQQueue();
try {
System.out.println("jmsQueue ");
jmsQueue = (Queue) m_context.lookup("myqueue");
} catch (Exception e) {
System.error.println("Error at JMS");
}
return jmsQueue;
}
#Bean
public DefaultMessageListenerContainer messageListener() {
DefaultMessageListenerContainer listener = new DefaultMessageListenerContainer();
try {
System.out.println("messageListener ");
listener.setConcurrentConsumers("4");
listener.setConnectionFactory((ConnectionFactory) queueConnectionFactory());
listener.setDestination((Destination) jmsQueue());
listener.setMessageListener(queueListener());
} catch (Exception e) {
System.out.println("Exception >>"+e);
}
return listener;
}
2022-08-05 15:16:24.615 ERROR 56480 --- [ssageListener-5] .b.e.s.DefaultMessageListenerContainer : Could not refresh JMS Connection for destination from DefaultMessageListener'queue:///' - retrying using FixedBackOff{interval=5000, currentAttempts=52, maxAttempts=unlimited}. Cause: null
Is there any way to do this bean to get register using the Listener.
That is an error from the listener container, not the registration of the connection factory bean.
You should be able to set the containers to not start automatically, using
spring.jms.listener.auto-startup=false
https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties.integration.spring.jms.listener.auto-startup
Then you can start them manually (in a try block) via the JmsListenerEndpointRegistry bean.

Does DefaultMessageListenerContainer stop , shutdown close database connections?

I have a DefaultMessageListenerContainer which is processing a message from the queue.
While the message is being processed -- stop , shutdown methods are called on DefaultMessageListenerContainer. Does this close database connections?
Looks like it is closing the database connections and hence the message being processed is getting interrupted from completely processing.
I see these errors :
o.s.jdbc.support.SQLErrorCodesFactory : Error while extracting database name
Closed Connection; nested exception is java.sql.SQLRecoverableException: Closed Connection
could these be because the DefaultMessageListenerContainer was stopped and shutdown ?
My code is as follows . startStopContainer is where I am trying to stop and shutdown container. I want to shutdown container only if listener completed processing the current message. I added logic to figure out if listener completed processing .
Is the below logic the only way or is there a better way to figure out if listener completed processing. Please suggest. Thank you.
public class MyMessageConsumerFacade {
private ConnectionFactory connectionFactory() {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
connectionFactory.setBrokerURL(url);
connectionFactory.setUserName(userName);
connectionFactory.setPassword(password);
return connectionFactory;
}
#Bean
public MessageListenerContainer listenerContainer() {
DefaultMessageListenerContainer container = new DefaultMessageListenerContainer();
container.setConnectionFactory(connectionFactory());
container.setDestinationName(queueName);
container.setMessageListener(new MyJmsListener());
return container;
}
}
public class MyJmsListener implements MessageListener {
public boolean onMessageCompleted;
public void onMessage(Message message) {
onMessageCompleted = false;
processMessage(message);
onMessageCompleted = true;
}
}
private String startStopContainer(ExecutionContext etk) {
String response = "success";
AnnotationConfigApplicationContext context = null;
DefaultMessageListenerContainer myNewContainer = null;
if (context == null) {
context = new AnnotationConfigApplicationContext(MyMessageConsumerFacade.class);
}
if (myNewContainer == null) {
myNewContainer = context.getBean(DefaultMessageListenerContainer.class);
}
MyCaseMessageJmsListener messageJmsListener = (MyCaseMessageJmsListener) myNewContainer.getMessageListener();
if (!myNewContainer.isRunning()) {// container not running
myNewContainer.start();
}
//due to some business logic we need to stop listener every 5 minutes, so sleep for 5 minutes and then stop
try {
Thread.sleep(300000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (myNewContainer.isRunning()) {
myNewContainer.stop();
}
//Before shutting down container , make sure listener processed all msgs completely
if(!messageJmsListener.isOnMessageCompleted) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(messageJmsListener.isOnMessageCompleted) {
myNewContainer.shutdown();
}
if (context != null) {
context.destroy();
}
return response;
}
Is there a better way than this?
No; the container knows nothing about JDBC or any connections thereto.
Stopping the container only stops the JMS consumer(s) the consumers from receiving messages; shutDown() on the container closes the consumer(s).
Something else is closing your JDBC connection.

javax.jms.JMSException: Duplicate durable subscription detected

I have a JMS application deployed as a Docker image in AWS Fargate. Two services are running for the task. However the problem is I am getting this:
2021-03-24 05:15:43.022 ERROR 1 --- [ main] com.hp.ext.cpq.pubsub.SnsTopicPublisher : Exception happened in readJmsTopicPublishToSnsTopic --->javax.jms.JMSException: Duplicate durable subscription detected
This is the code I am using to create the durable subscriber:
SnsTopicPublisher asyncSubscriber = this.ctx.getBean(SnsTopicPublisher.class);
if (prop.getProperty("tibco.msgSourceType").equalsIgnoreCase("TOPIC")) {
dest_t = session.createTopic(prop.getProperty("tibco.msgSource"));
**TopicSubscriber topicSubscriber = session.createDurableSubscriber(dest_t, "pfpDurable");**
topicSubscriber.setMessageListener(asyncSubscriber);
logger.debug("Set Jms Topic Listener ---> asyncSubscriber");
}
if (prop.getProperty("tibco.msgSourceType").equalsIgnoreCase("QUEUE")) {
dest_q = session.createQueue(prop.getProperty("tibco.msgSource"));
MessageConsumer msgConsumer_p = session.createConsumer(dest_q);
msgConsumer_p.setMessageListener(asyncSubscriber);
logger.debug("Set Jms Queue Listener ---> asyncSubscriber");
}
I am getting the error for the marked line from AWS cloud watch logs.
Most likely you have a connection (and generally other JMS objects) leak. When the exception is thrown, you need to close resources in a finally {} block, similar to the JDBC pattern.
Also, you might want to look at using a Pooled Connection. This allows for open+close pattern on JMS connections without really closing connections to the server. Check out the activemq-jms-pool which is a JMS-standard pool (not ActiveMQ specific) that works with most JMS brokers, including Tibco and IBM MQ.
Connection connection = null;
Session session = null;
MessageConsumer messageConsumer = null;
try {
connection = connectionFactory.createConnection();
connection.start();
.. do some JMS
} catch (JMSException e) {
// handle errors
} finally {
if (messageConsumer != null) {
try { messageConsumer.close(); } catch (JMSException e) { logger.error("Error closing MessagingConsumer", e);
}
if (session != null) {
try { session.close(); } catch (JMSException e) { logger.error("Error closing Session", e);
}
if (connection != null) {
try { connection.close(); } catch (JMSException e) { logger.error("Error closing Connection", e);
}
}
Note: The JMS specification only allows 1 durable subscription per topic using the API you have in the code. The JMS v2.0 allows for a Shared Durable Subscription to support multiple consumers.

Spring transaction: unexpected rollback behavior

I am doing a simple experiment for debugging purpose.
First I insert serveral records to database, and then I do a invalid data conversion which will throw DataIntegrityViolationException, but I will catch the exception.
I expected the records being successfully inserted into the db, since I catch the checked exception. But the whole thing is rolled back.
I do the experiment again using TransactionTemplate instead of using annotation, same result.
My questions are:
is this the expected behavior?
If anwser to No.1 is yes, then I catch the exception, how is it possible that spring knows an exception is thrown?
Here is my code:
public void insertValue() {
jdbcTemplate.execute("insert into people (person_id, name) values (4, 'asjkdhadsjkqhweqkewhkashdkahd')");
jdbcTemplate.execute("insert into people (person_id, name) values (5, 'tttqqq')");
}
// this should throw exception
public void truncateValue() {
jdbcTemplate.execute("alter table people alter column name varchar(7)");
}
public void jdbc_calls() {
insertValue();
try {
truncateValue();
} catch (Exception e) {
System.out.println(e.getMessage());
}
System.out.println("Finish");
}
public void run() {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
transactionTemplate.execute(transactionStatus -> {
try {
jdbc_calls();
} catch (RuntimeException e) {
throw e;
} catch (Throwable e) {
throw new RuntimeException(e);
}
return null;
});
}
More about question No.2.
Here is the source code of TransactionTemplate.execute()
From my understanding, if I don't throw an exception, rollbackOnException won'r be triggered.
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
}
else {
TransactionStatus status = this.transactionManager.getTransaction(this);
T result;
try {
result = action.doInTransaction(status);
}
catch (RuntimeException | Error ex) {
// Transactional code threw application exception -> rollback
rollbackOnException(status, ex);
throw ex;
}
catch (Throwable ex) {
// Transactional code threw unexpected exception -> rollback
rollbackOnException(status, ex);
throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
}
this.transactionManager.commit(status);
return result;
}
}
is this the expected behavior?
Yes, it is.
If anwser to No.1 is yes, then I catch the exception, how is it possible that spring knows an exception is thrown?
When an exception occurs, spring will mark your transaction as rollbackOnly.
So even when you catch your exception, at the end of your method, your transaction still rolled back.
In your case, I don't get why you use #Transaction since you want to commit regardless if exception occurs.
Edit
When you're using transaction with DB, the transaction invocation is delegated to EntityManager.
Look at AbstractEntityManagerImpl#handlePersistenceException:
#Override
public void handlePersistenceException(PersistenceException e) {
if ( e instanceof NoResultException ) {
return;
}
if ( e instanceof NonUniqueResultException ) {
return;
}
if ( e instanceof LockTimeoutException ) {
return;
}
if ( e instanceof QueryTimeoutException ) {
return;
}
try {
markForRollbackOnly();
}
catch ( Exception ne ) {
//we do not want the subsequent exception to swallow the original one
LOG.unableToMarkForRollbackOnPersistenceException(ne);
}
}
When exception occurs, the EntityManager mark your transaction as rollbackOnly before throws out the exception for you to catch.
After the exception is catched in your service, the AbstractPlatformTransactionManager will try to commit (because, as you know, no exception is detected there), but the EntityManager refuses to commit because its detect that the transaction marked as rollback-only.
If you read the exception, you will see something like:
javax.persistence.RollbackException: Transaction marked as rollbackOnly

JMS Template,How can i receive a message from one queue and send to another using JMS Template

public void sendSimpleMessage(String receiver, String sender) {
try {
Message message = jmsTemplate.receive(receiver);
System.out.println(message.getIntProperty("OlQuestionId"));
jmsTemplate.send(sender, new MessageCreator() {
#Override
public Message createMessage(Session session) throws JMSException {
throw new JMSException("Exception"+message.getIntProperty("OlQuestionId"));
}
});
} catch (JmsException jmsException) {
System.out.println(jmsException);
} catch (JMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
If an exception occurs while sending the received message there would be a loss of message as it is already recieved.
For Jms Template configuration i have :
#Bean
public JmsTemplate jmsTemplate() throws JMSException {
JmsTemplate template = new JmsTemplate();
template.setConnectionFactory(connectionFactory());
//template.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);
template.setSessionTransacted(true);
template.setDeliveryMode(2);
return template;
Can you please tell me the way so that i can do recieving and sending in a single session.
Note: i have also tried Session.ClientAcknowledge while removing sessionTransacted, if exception is there i am not acknowledging the message but still there's a message loss.
Thanks
You can use client acknowledge mode. The message will stay until you decide to make it disappear.
message.acknowledge();
See How to Give manual Acknowledge using JmsTemplate and delete message from Rabbitmq queue

Resources