Friday, January 30, 2009

JMS Synchronous Message Consumption

A JMS message consumer can consume messages synchronously by calling one of the MessageConsumer's receive() methods:
  • receive()
  • receive (long timeout)
  • receiveNoWait();
Calling receive() or receive (0) will block until there is a message available. This is the easiest form to receive a message synchronously. The only disadvantage is that the application thread is blocked indefinitely.

Receive with timeout and receiveNoWait are the two alternative API to consume messages synchronously. But applications should be aware of the intended contract and behavior of the APIs. Applications may not function reliably or consistently if they are not used properly.

1. receive (long timeout)

The JMS API JavaDoc for the API is quoted as follows.

"Receives the next message that arrives within the specified timeout interval.

This call blocks until a message arrives, the timeout expires, or this message consumer is closed. A timeout of zero never expires, and the call blocks indefinitely."

One important thing to note is the timeout value. This is the maximum wait time that the application should be expecting to wait. The call returns immediately if there is a message available. The call may block up to the specified timeout value if no message is available.

Some books and articles provide misleading examples that would cause the application behaves inconsistently. For example, the following code used a timeout value of one milli second that is likely to cause the application's behavior in-deterministic.

//create a queue instance
dest = session.createQueue(destName);

//create a message consumer to receive messages on dest.
MessageConsumer consumer = session.createConsumer(dest);

//tell the connection I am ready to receive the reply
conn.start();

//receive a message with the max timeout of 1 milli second.
Message rm = consumer.receive(1);

if (rm == null) {
System.out.println ("\n No message is received:\n ");
} else {
System.out.println ("\n Message received: " + rm);
}

The above code is likely to result in a null value be returned for the receive(1) call even if there is a message on the destination. The reason is that the timer may start from the moment receive method is called, and the timeout value is also reached immediately. If there is no message available within one milli second to the application, the API returns a null value. (The message may be still in network transition from the server to the message consumer.)

Even though different messaging vendors may internally implement in a different way, it is always a good practice to code defensively.

Application should always assume the timer starts when receive(timeout) is called, and a null value is returned if timeout expired before a message is available, if any.

A good practice to use timed synchronous receive is to consider the following suggestions.
  • specify a "reasonable" max timeout value whenever possible.
  • the receive (timeout) call should always be revisited (call again) if a small timeout value is specified.
  • a small timeout value in a loop could possibly cause tight cpu loop if no other wait/sleep in the loop.
  • avoid (re)creating a new synchronous message consumer for each receive (timeout) call.
The following is an example to use the timed receive API.

//create a queue instance
dest = session.createQueue(destName);

//create a message consumer to receive messages on dest.
MessageConsumer consumer = session.createConsumer(dest);

//tell the connection I am ready to receive the reply
conn.start();

while ( !done ) {
//receive a message with the max timeout of 3 seconds.
Message rm = consumer.receive(3000);
//process business login
...
}


2. receiveNoWait().

The behavior of this API is similar to the timed receive API with a small timeout value. The programming practice should follow the above suggestions to ensure reliable messaging behavior.

1 comment: