Search code examples
spring-integrationibm-mq

How to keep alive an IBM MQ connection that expires instead of disconnecting


We have a basic adapter => channel => adapter pattern with int-jms:message-driven-channel-adapter and int-jms:outbound-channel-adapter. The connectionFactory is a com.ibm.mq.jms.MQConnectionFactory (code below). It gets message from a MQSeries Broker (9+) toward another MQSeries Broker (6+).

This bridge works well with most our recipients, but one of them encounters a problem. When the bridge isn't active for a while the connection becomes "invalid" and once a new message arrives the bridge fails sending it. According to me, the connectionFactory's default behavior is to reconnect as soon at it detects the outbound has disconnected. Here, instead of detecting a "disconnection", it tries to use the previous connection and fails:

Caused by: javax.jms.JMSException: MQJMS2007: failed to send message to MQ queue....  
Caused by: com.ibm.mq.MQException: MQJE001: Completion Code '2', Reason '2009'.

I am not skilled with networking / sockets (and so on), but it sounds like something expired on the recipient side (with no disconnection notification). It sounds like that a "keepalive" option on the connection would help here, but I was not able to find such a mecanism in Spring Integration JMS and/or IBM MQ classes.

Does anyone have an idea on how I can perform such a keepalive from Spring Integration side? Or otherwise an idea about why this connection expires without notification?

<beans>
    <int:channel id="channelMQ_MQ" ></int:channel>

    <!-- Source : MQseries -->
    <!-- ... --> 
                                    

    <!-- Destination MQ_SERIES      -->
        <!- ... -->
    <bean id="jmsQueueOut" class="com.ibm.mq.jms.MQQueue" depends-on="jmsConnectionFactory">
        ...
    </bean>
    
    <bean id="jmsConnectionFactory2" class="com.ibm.mq.jms.MQConnectionFactory">
        <property name="queueManager" value="..." />
        <property name="connectionNameList" value="..." />
        <property name="channel" value="..." />
        <property name="transportType" value="1" />
    </bean>
    
    <bean id="jmsConnectionFactory_cred2"
        class="org.springframework.jms.connection.UserCredentialsConnectionFactoryAdapter">
        <property name="targetConnectionFactory" ref="jmsConnectionFactory2" />
        <property name="username" value="..."/> 
        <property name="password" value="..."/> 
    </bean> 

    <bean id="connectionFactoryCaching2"
        class="org.springframework.jms.connection.CachingConnectionFactory">
        <property name="targetConnectionFactory" ref="jmsConnectionFactory_cred2" />
        <property name="sessionCacheSize" value="..." />
    </bean>
    
    <int-jms:outbound-channel-adapter channel="channelMQ_MQ" 
                                      id="jmsOut" 
                                      destination="jmsQueueOut" 
                                      connection-factory="connectionFactoryCaching2" 
                                      delivery-persistent="true" 
                                      explicit-qos-enabled="true" 
                                      session-transacted="true" >
    </int-jms:outbound-channel-adapter>
</beans>

Edit 1 :

  • MQSeries Version 6
  • IBM Mq classes : 9.1.5
  • Spring Integration : 5.5.13
  • MQSeries Setting :
    • HBINT : 60
    • KAINT : 120
    • SHARECNV => does not exist on version 6

Solution

  • It took us a little while to find the trick, but we ended up finding the suited solution....

    You need to set it up on both system and application sides:

    (within docker-compose, you need to use the "systctls clause)

    System :

      - net.ipv4.tcp_keepalive_intvl=30
      - net.ipv4.tcp_keepalive_probes=2
      - net.ipv4.tcp_keepalive_time=60
    

    Application

    System.setProperty("com.ibm.mq.cfg.TCP.KeepAlive","YES");
    

    (there are various ways to set up this system variable. This is just one of them, as a matter of exemple)