jsr343-experts@jms-spec.java.net

[jsr343-experts] Re: (JMS_SPEC-43) New API to send a message with async acknowledgement from server

From: Nigel Deakin <nigel.deakin_at_oracle.com>
Date: Thu, 17 May 2012 16:02:20 +0100

I refer to this JIRA issue:
http://java.net/jira/browse/JMS_SPEC-43

This relates to the following new methods on MessageProducer and JMSContext:

   void send(Message message, CompletionListener completionListener) throws JMSException;

void
   send(Message message, int deliveryMode, int priority,
         long timeToLive, CompletionListener completionListener) throws JMSException;

http://jms-spec.java.net/2.0-SNAPSHOT/apidocs/javax/jms/MessageProducer.html#send%28javax.jms.Message,%20javax.jms.CompletionListener%29

These are is in the Early Draft and javadocs and I think are pretty much settled. However I've received a few useful
comments about this from the implementation team, which I'd like to address.

Executive summary for the busy
------------------------------

In this email, I propose:

* To change the API for an asynchronous send to return a Future object which can be used to block until the send is
complete.

* To clarify the required behaviour and expected implementation of an asynchronous send by incorporating the "Proposed
general comments" listed below.

Please read on...

Proposed general comments
-------------------------

I think we need to clarify the purpose of these methods and how the JMS provider is expected to implement them. I would
like to propose some wording similar to the following:

This method is provided to allow the JMS provider to perform part of the work involved in sending a message in a
separate thread, allowing the application to do something else whilst this is happening. JMS refers to this as an
"asynchronous send". When the message has been successfully sent the JMS provider invokes a callback method on an
application-specified CompletionListener object. Only when that callback has been invoked can the application be sure
that the message has been successfully sent with the same degree of confidence as if a normal synchronous send had been
performed. An application which requires this degree of confidence must therefore wait for the callback to be invoked
before continuing.

The following information is intended for information purposes only to give an indication of how an asynchronous send
would typically be implemented.

In some JMS providers, a normal synchronous send involves sending the message to a remote JMS server and then waiting
for an acknowledgement to be received before returning. It is expected that such a provider would implement an
asynchronous send by sending the message to the remote JMS server and then returning without waiting for an
acknowledgement. When the acknowledgement is received, the JMS provider would notify the application by invoking the
onCompletion method on the an application-specified CompletionListener object. This allows the application to do
something else before waiting for the completion listener to be invoked.

In those cases where the JMS specification permits a lower level of reliability, a normal synchronous send might not
wait for an acknowledgement. In that case it is expected that an asynchronous send would be the similar to a synchronous
send: the JMS provider would send the message to the remote JMS server and then return without waiting for an
acknowledgement. The JMS provider would then notify the application by invoking the onCompletion method on the
application-specified CompletionListener object.

It is up to the JMS provider to decide exactly what is performed in the calling thread and what, if anything, is
performed asynchronously, so long as it satisfies the following:

* After the send operation is complete, which means that the message has been successfully sent with the same degree of
confidence as if a normal synchronous send had been performed, the JMS provider must invoke completion listener. The
completion listener must not be invoked earlier than this.

* The JMS specification defines a number of message header fields and JMS-defined message properties of the specified
Message which must be set by the JMS provider send method (see section 3.4.11 "How message header values are set" and
3.5.9 "JMS defined properties"). The asynchronous send method must set these before returning.

* If the same MessageProducer or JMSContext is used to send multiple messages then JMS message ordering requirements
(see section 4.4.10.1 "Order of message receipt) must be satisfied. This applies even if a combination of synchronous
and asynchronous sends have been performed.

In addition,

* If the session is transacted (uses a local transaction) then when commit() is called the JMS provider must block until
any incomplete send operations have been completed before performing the commit and returning. If rollback() is called
the JMS provider must ensure that any incomplete send operations are rolled back before returning.

* If close() is called (on the MessageProducer, Session, Connection or JMSContext object) then there is no requirement
for the JMS provider to block until any incomplete send operations have completed. It is undefined whether a completion
listener will be invoked after its producer, session, connection or context has been closed.

Threading restrictions in the callback method
---------------------------------------------

We may also want to state what JMS operations the completion listener is permitted to perform. For example is it allowed
to use the session to send a message?

On reflection I don't think we need to impose any restrictions, except perhaps to remind developers that they need to
confirm to the threading restrictions defined in section 4.4.6. "Conventions for using a session".

This means that the callback thread may only use the session if no other thread is using the session at the same time.
Typically this could only be guaranteed if the main thread were to block on some mutex and wait for the completion
listener to be called. However this is a very unlikely scenario, given that the whole point of using this method is to
avoid the main thread having to block. So in practice a callback method would not be able to use the session since it
could not know what the main thread was doing. I'm unsure exactly how much of this needs spelling out.

However I think we probably do need to state explicitly that setting a completion listener does NOT cause the session to
be dedicated to the thread of control which calls the completion listener. This contrasts with setting a message
listener, which does. We need to state this because otherwise it would be illegal for the main thread to continue to use
the session after async send.

We could clarify the API as follows:

1. the javadoc and spec reminds developers using these methods that they need to confirm to the threading restrictions
defined in section 4.4.6. "Conventions for using a session".

2. state explicitly, both in the javadoc for these methods and in 4.4.6, that setting a completion listener does NOT
cause the session to be dedicated to the thread of control which calls the completion listener.

However we might want to review whether we really need a callback method at all. Please read on...

Arguments passed in the callback
--------------------------------

The Early Draft proposed the following CompletionListener interface:

public interface CompletionListener {
    void onCompletion(Message message);
    void onException(Exception exception);
}

In both callback methods the Message being sent was passed as a parameter. This was provided to allow applications to
identify which message a given callback relates to. However if the Message parameter was the same as the Message that
was originally sent then the main thread would need to be careful not to change the state of the Message after sending
it, such as clearing its properties or sending it a second time, both of which are otherwise allowed in JMS. This
restriction could be avoided by requiring the Message parameter to be a deep copy of the message that was sent. However
this would be potentially expensive. To avoid the need to do this, and to avoid any other problems caused by concurrent
use of the same Message, it would be better to simply pass in the JMSMessageID rather than the whole message.

public interface CompletionListener {
    void onCompletion(String messageID);
    void onException(String messageID, Exception exception);
}

However I think we might be able to do without a CompletionListener at all. Please read on...

Do we really need a CompletionListener?
---------------------------------------

I thought I'd write a simple example that demonstrated how this new method might be used in practice. I think the key
use case is:

1. Calling the async send
2. Performing some other work whilst waiting for the send to be completed
3. Blocking until confirmation is received that the send has been completed

The most complicated part of this is the mechanism to make the main thread block until the CompletionListener has been
invoked. I've implemented it using wait/notify though there are other ways to implement this.

        MyCompletionListener cl = new MyCompletionListener();
        messageProducer.send(message,cl);
                        
        // This is where we can do something else whilst waiting for the send to complete
                        
        // We have finished doing that other thing, now we need to wait for the send to complete before carrying on
        synchronized (cl) {
           if (!cl.isComplete()) {
              cl.wait();
           }
        }
        
        // the send has completed
        // did it work or did we get an exception?
        if (cl.getException() == null) {
           System.out.println("Send completed successfully");
        } else {
           System.out.println("Send failed with exception " + cl.exception);
        }

This would require a CompletionListener implementation something like this:

        class MyCompletionListener implements CompletionListener {
           private boolean complete = false;
           private Exception exception = null;
        
           boolean isComplete() {return complete;}
           Exception getException() {return exception;}
        
           @Override
           public void onCompletion(String messageID) {
              synchronized (this) {
                 this.notifyAll();
              }
              complete = true;
           }
        
           @Override
           public void onException(String messageID, Exception exception) {
              this.exception = exception;
              synchronized (this) {
                 this.notifyAll();
              }
              complete = true;
           }
        }

I've assumed we change onCompletion() to pass the JMSMessageID rather than the Message, and added this as an argument to
onException(). However in this example I didn't need to use this anyway.

I also found it necessary for my CompletionListener to provide two additional methods isComplete() and getException().
The result is simple enough, but it does require some basic understanding of java notification, including the need to
handle the case where the notifyAll() occurs before the wait().

Whilst this is not difficult it does clutter the code with boilerplate and requires the application developer to
understanding threading concepts that JMS does not otherwise require. This made me consider whether a CompletionListener
was the most appropriate way to inform the application that the send was complete.

An alternative would be for the send method to return a java.util.concurrent.Future.

So the new methods on MessageProducer and JMSContext would be:

       Future<Boolean> sendAsync(Message message) throws JMSException;

       Future<Boolean> sendAsync(Message message, int deliveryMode, int priority) throws JMSException;

These methods would need to be called something other than name to avoid a clash with the existing methods.

The Future object provides a method get() which will block until the send is completed. It would also throw an exception
if the asynchronous part of the send throws an exception.

Here's what the same use case would look like:

        Future<Boolean> sendFuture = messageProducer.sendAsync(message);
        
        // This is where we can do something else whilst waiting for the send to complete
                        
        // We have finished doing that other thing, now we need to wait for the send to complete before carrying on
        try {
           boolean ok = sendFuture.get();
           System.out.println("Send completed successfully with ok="+ok);
        } catch (ExecutionException e) {
           System.out.println("Send failed with exception " + e);
        }

That's all. This gives the same behaviour as the previous use case, with no need to define a CompletionListener class
and no need to use wait/notify.

The Future class requires the asynchronous task to return an Object, even though we don't actually need this. I've made
it return a Boolean, which will always be set to true.

One advantage of using a Future is that because it doesn't require the application to supply a callback method there is
no need to define any rules about what can or cannot be done in the callback method.

Conclusions and recommendations
-------------------------------

In summary, I propose we

* Change the API for an asynchronous send to return a Future object which can be used to block until the send is complete.

* Incorporate the "Proposed general comments" listed above

Any comments?

Nigel