When to use ASF and non-ASF modes to process messages in WebSphere Application Server
One of the custom properties that users can set on the message listener service in WebSphere Application Server Versions 5.1.x, 6.0.x, and 6.1.x is NON.ASF.RECEIVE.TIMEOUT. Setting this property disables the Application Server Facilities (ASF) mode, and means that the message-driven beans (MDBs) that use listener ports will use a "synchronous receive" mechanism to get messages. But what does this mean exactly? This article will explain what ASF mode is, and then discuss how non-ASF mode works. This article assumes that you have a good working knowledge of Java Message Service (JMS).
By default, MDBs that are bound to listener ports will use ASF mode to monitor JMS destinations and to process messages. In ASF mode, when a listener port starts, this is what happens:
A queue agent is created that polls the destination looking for messages.
When a message is detected, the queue agent checks if the message is suitable for the MDB using the listener port. If it is, the application server will get a server session from the application's server session pool, and then run this server session on a thread taken from the message listener service thread pool.
The queue agent passes the ID of the message to the server session, and then resumes its browse of the destination.
Once the server session has the ID of the message to process, it will get the message from the destination, and start processing it by calling the MDB's onMessage() method.
When the message has been processed, the server session exits and is returned to the server session pool. The thread that the server session was running on is also returned to the thread pool.
Figure 1 is a simple diagram that shows the steps taken by the application server to process messages in ASF mode.
Figure 1. How a listener port processes messages in ASF mode
ASF mode and maximum sessions
By default, the listener port's maximum sessions property has the value 1; hence, an MDB can only process one message at a time. To illustrate what this means, suppose we have an MDB listener browsing a destination looking for messages, and there are two messages on the destination suitable for the MDB. In this scenario:
The queue agent finds the first message, gets the server session from the server session pool and a thread from the message listener service thread pool, and then uses them to process the message.
Once the server session has been kicked off, the queue agent returns to the task of browsing the destination.
The queue agent then finds the second message. However, since the listener port has maximum sessions set to 1 -- and there is currently one server session already running for this listener port -- the queue agent will block the second message until the current message has finished being processed and the server session has become available.
Of course, this behavior isn't ideal. It would be much better if a listener port could process multiple messages concurrently. Well, the good news is that it can. All you need to do is increase the value of maximum sessions for the listener port.
Let's assume we have the same situation as described above, but this time maximum sessions is set to 2:
The queue agent finds the first message, and gets a thread and server session to process the message. While the message is being processed, the queue agent returns to its job of browsing the destination.
The queue agent then finds the second message. This time, since maximum sessions is set to 2, the listener port will get a new server session from the pool and a new thread from the thread pool, and use these to process this message.
Now that we have know what ASF mode is and how listener ports process messages when using this mechanism, let's look at non-ASF mode.
When the message listener service custom property NON.ASF.RECEIVE.TIMEOUT is set, non-ASF mode is activated, which means that listener ports will use a synchronous model to process messages. When the listener port starts up, this is what occurs:
The listener port gets a thread from the message listener service thread pool, and on that thread, creates a JMS message consumer for the destination the listener port is configured to listen to.
The consumer uses the receive() method to detect incoming messages. This method will take the message off the destination, and the thread then invokes the MDB's onMessage().
When the message has been processed, the thread calls receive() again, and the cycle starts over.
If the receive() method does not detect a message in the time specified by the NON.ASF.RECEIVE.TIMEOUT property, the thread performs a small bit of processing before calling receive() again.
Figure 2 shows how a listener port processes messages when non-ASF mode is being used.
Figure 2. How a listener port processes messages in non-ASF mode
The benefit of using non-ASF mode is that, once a message is detected on the queue, the time taken to invoke the MDB is much quicker than if the listener port was using ASF mode. When ASF mode is in use, the listener port needs to get a thread and a server session, run the server session on the thread, and get the message to the server session, which then invokes onMessage(). With Non-ASF mode, as soon as the message is detected, it is taken off the destination and passed to onMessage() for processing.
Non-ASF mode and maximum sessions
Earlier, we discussed how the listener port's maximum sessions property affected the way messages were processed. How does this property work when the listener port has been configured to use non-ASF mode?
When the listener port starts up, it creates a number of threads up to the value of maximum sessions. Each of these threads creates a JMS message consumer, and calls receive(). So, for example, let's suppose that maximum sessions is set to 2. When the listener port starts up:
Two threads are created, which listen for messages on the same destination.
When a message arrives at the destination, both threads compete for the message, but only one will get it. The other thread will remain in the receive() method. Figure 3 shows this in more detail
Figure 3. A listener port using non-ASF mode with maximum sessions set to 2
Non-ASF mode and transactions
Just prior to calling receive(), the message listener service will create a new transaction for the current thread. This transaction will complete (either committed or rolled back) when one of the following events occurs:
- A message has been detected and processed by the MDB's onMessage() method.
- The call to receive() has timed out.
WebSphere Application Server enables you to specify how long to wait for a transaction to complete once it has started. If the transaction takes longer than the value specified, the application server will time it out. The default value for the transaction timeout property is 120 seconds (2 minutes), but this can be changed by modifying the transaction service's total transaction lifetime timeout property.
The transaction timeout can have implications when using non-ASF mode. The WebSphere Application Server Information Center says the following when describing the NON.ASF.RECEIVE.TIMEOUT property:
If a transaction timeout occurs, the message must recycle causing extra work. If you want to use the non-ASF mode, set this property to lower than the transaction timeout, but leave spare at least the maximum duration of your message-driven bean's onMessage() method. For example, if your message-driven bean's onMessage() method typically takes a maximum of 10 seconds, and the transaction timeout is set to 120 seconds, you might set the NON.ASF.RECEIVE.TIMEOUT property to no more than 110000 (110000 milliseconds, that is 110 seconds).
So, what does this mean? Well, suppose we have set NON.ASF.RECEIVE.TIMEOUT to 110000 (110 seconds), and left the transaction timeout at 120 seconds. Our MDB takes 15 seconds to process a message:
When the listener port starts up, it creates a message consumer, starts a transaction, and calls receive(110000).
If there is a message on the destination when the listener starts, the message is consumed straight away, and onMessage() is called.
The total time for this transaction is 15 seconds.
Now, let's suppose that, initially, there are no messages on the destination:
The message consumer starts up, a transaction is created, and receive(110000) is called.
After 110 seconds, a message arrives.
The thread removes the message from the queue, and calls onMessage().
10 seconds later, the transaction timeout is reached. This causes the application server to mark the transaction for rollback.
After a further five seconds, the MDB finishes processing the message, and tries to commit the transaction.
The total amount of time that has elapsed since the transaction was started is 125 seconds (110 seconds waiting for a message, 15 seconds to process the message). As this is longer than the transaction timeout, the application server prevents the transaction from being committed, and rolls it back instead.
It is for this reason that you should ensure that the value of the NON.ASF.RECEIVE.TIMEOUT property is set to less than the transaction timeout, and that the difference between the two properties should be greater than the amount of time it takes for your MDB to process a message. Here is a formula that can be used to determine what NON.ASF.RECEIVE.TIMEOUT should be set to:
NON.ASF.RECEIVE.TIMEOUT <= Total transaction lifetime timeout - Time taken for MDB to process a message
Which mode should you use?
In general, you should use ASF mode. This is the correct way for MDBs to detect and process messages, as defined in Sun®'s JMS specifications. However, if you need to increase the throughput of messages on your system, then you should consider using non-ASF mode, since the time taken to invoke an MDB once a message has been detected is much shorter with non-ASF mode than it is with ASF mode.
The downside to using non-ASF is that if maximum sessions is set to a very large value, you will have a lot of threads running when your listener port starts. For example, if maximum sessions is set to 100, there will be 100 threads active (each with an associated message consumer) when your listener port is running. These threads will be active (calling receive()) even if there are no messages on the destination to process.
Consider the same situation with ASF mode: here, there will just be a single thread running (the queue agent), and other threads will be created and used to process messages as required.
We have looked at how WebSphere Application Server uses the Application Server Facilities to process JMS messages, how the ASF can be disabled, and what the impact of doing so has on the behaviour of the application server. We have also seen the effect the listener port's maximum sessions property has when processing messages in both ASF and non-ASF mode, as well as the impact non-ASF mode has on transactions. Finally, we provided recommendations on when ASF and non-ASF modes should be used.
- Configure WebSphere Application Server to make message-driven beans process messages in a strict order
- How MDB listeners work with a listener port in WebSphere Application Server
- How WebSphere Application Server handles poison messages
- How the maximum sessions property on the listener port affects WebSphere Application Server performance