Document: JMS and QM shutdown issues

Author: Neil Kolban

Date: 06/13/2002

Version: 1.0

 

JMS and QM shutdown issues

IBM’s JMS implementation distributed as part of the MA88 SupportPac utilizes MQSeries as the underlying messaging provider.  When a JMS client application forms a Connection and Session, a connection is formed to the MQSeries queue manager.

 

If the application performs work against the queue manager and then pauses further JMS activities but remains active, if the queue manager is shutdown, it can not restarted until the application terminates its Session.  This is because MQSeries requires that all applications, which were previously connected, to it become dormant before it will allow itself to be restarted.  It is believed that this is because the agent process contains locks on various semaphores and other shared memory components.

 

 


 


At first, it may not seem to be too much of an issue.  Since the queue manager was down, the JMS Client application would have to close its Session any way to re-establish a new connection to the queue manager when it does return.  Unfortunately, this is not sufficient.

 

Imagine the scenario where there are large numbers of JMS Clients connected to a queue manager.  If the queue manager needs to be stopped and restarted, having to ensure that all the applications disconnect is too much of a burden.  If a single client application resides elsewhere and can not be shut down, then it is possible to prevent the queue manager restarting and servicing all of the other activities.

 

If a JMS Client application has not terminated its connection to the queue manager, the process identifier of the agent process holding the remaining connection is displayed when the queue manager start is attempted (strmqm).  From this information, a suitable interrupt signal can be transmitted to the process to cause it to come to an end, after which the queue manager itself may be restarted.  If there are large numbers of JMS Clients, this may prove to be a challenge and an alternate mechanism is required.

 

Ideally, when the queue manager ends, each JMS client that owns a connection to the target queue manager should be informed of the outage and perform its own cleanup by closing all its sessions.  The loss of the queue manager will be identified to the JMS Client application when it performs a subsequent JMS operation involving either a queue or a topic.  Unfortunately, if the application does not perform frequent JMS operations, it may never detect such an outage.

 

This is where the asynchronous exception notification of JMS can come into play.  JMS specifies a Java interface called ExceptionListener.  This interface has one method with the signature:

 

public void onException(JMSException e);

 

A class which implements this interface may be identified to the Connection object with the setExceptionListener() method.  When an exception occurs, the onException() method of the registered class is invoked which can take appropriate action.  This could include disconnecting from the MQSeries queue manager.

 

Unfortunately, this is not yet enough.  The exception listener will only be invoked when it detects an exception (the queue manager going down).  And it will only detect this when an action is attempted against the queue manager.  The easiest way to arrange for this to happen as quickly as possible is to register a MessageListener against an un-used queue.  The MessageListener will always be watching for messages arriving on the queue.  When the queue manager is shut down, the MessageListener can no longer retrieve messages and results in a JMSException being thrown which the ExceptionListener will catch.

 

The obvious next action is to close the Connection from the ExceptionListener class.  At the time of writing (06/13/2002), this was shown to work but other subtle bugs were encountered.

 


Below is the sample application used to test the environment:

 

package bcbs.fl;

import javax.jms.*;

import javax.jms.JMSException;

import javax.jms.Message;

import javax.naming.*;

import javax.naming.directory.*;

public class Shut1 implements ExceptionListener, MessageListener {

 

       QueueConnection conn = null;

 

       public static void main(String[] args) {

              Shut1 shut1 = new Shut1();

              shut1.run();

       }

 

       public void run() {

              final String factory = "com.sun.jndi.fscontext.RefFSContextFactory";

              final String url = "file://C:/MQ/JMS";

              QueueConnectionFactory qcf;

              Queue q;

              java.util.Hashtable environment = new java.util.Hashtable();

              environment.put(Context.INITIAL_CONTEXT_FACTORY, factory);

              environment.put(Context.PROVIDER_URL, url);

              try {

                     Context ctx = new InitialDirContext(environment);

                     qcf = (QueueConnectionFactory) ctx.lookup("QCF");

                     q = (Queue) ctx.lookup("Q");

              } catch (NamingException e) {

                     System.out.println("Exception: " + e.toString());

                     return;

              }

 

              try {

                     conn = qcf.createQueueConnection();

                     conn.setExceptionListener(this);

                     QueueSession session = conn.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);

                     QueueReceiver receiver = session.createReceiver(q);

                     QueueSender sender = session.createSender(q);

 

                     receiver.setMessageListener(this);

 

                     TextMessage message = session.createTextMessage();

                     message.setText("Hello World");

 

                     sender.send(message);

 

                     conn.start();

 

                     System.out.println("Waiting for Session close signal");

                     System.in.read();

                     try {

                           System.out.println("About to close Session");

                           session.close();

                           System.out.println("Session closed");

                     } catch (JMSException e) {

                           System.out.println("JMSException: " + e.toString());

                           System.out.println(e.getLinkedException().toString());

                     }

 

                     System.out.println("Waiting for connection close signal");

                     System.in.read();

                     try {

                           conn.close();

                           System.out.println("Connection terminated");

                     } catch (JMSException e) {

                            System.out.println("JMSException: " + e.toString());

                           System.out.println(e.getLinkedException().toString());

                     }

                     System.out.println("Sleeping ...");

                     Thread.sleep(100000);

 

              } catch (JMSException e) {

                     System.out.println("JMSException: " + e.toString());

                     System.out.println(e.getLinkedException().toString());

              } catch (Exception e) {

                     System.out.println("Exception: " + e.toString());

              }

       }

       /**

        * @see ExceptionListener#onException(JMSException)

        */

       public void onException(JMSException e) {

              System.out.println("onException: " + e.toString());

              System.out.println(e.getLinkedException().toString());

              System.out.println("Closing connection.");

              try {

                     conn.close();

              } catch (JMSException e2) {

                     System.out.println("JMSException: " + e2.toString());

                     System.out.println(e2.getLinkedException().toString());

              }

       }

 

       /**

        * @see MessageListener#onMessage(Message)

        */

       public void onMessage(Message m) {

              System.out.println("Got a message!");

       }

 

}

 

When executed, it produced the following results:

 

Waiting for Session close signal

Got a message!

<Here I shut down the queue manager>

onException: javax.jms.JMSException: MQJMS2002: failed to get message from MQ queue

com.ibm.mq.MQException:  MQJE001: Completion Code 2, Reason 2018

Closing connection.

x   <- typed this at the console

About to close Session

<No further output received>

 

 

I would have expected the Session to close cleanly but instead, the session.close() method call simply hung.