users@jms-spec.java.net

[jms-spec users] [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: Fri, 25 May 2012 16:58:54 +0100

Following Clebert's clarifications I'm happy to stick with a CompletionListener as the way to notify the calling thread
that the send has completed. I withdraw my suggestion that a Future be used instead.

I'd like to return to the other issues I raised (and raise one more about its use in Java EE).

Please take a look and let me know:

1. If you have any comments on the proposed general comments, threading restrictions, and restrictions on usage in Java EE

2. Think we should change the CompletionListener's callback methods to be given a JMSMessageID rather than a Message, to
avoid the need for the application to worry about avoiding concurrent access to the Message object.

*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
suggest we add a statement something like 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 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 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 the
        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.

The reason this is written rather generically is that I want the definition to cover the cases where the send is not
acknowledged (e.g. some non-persistent messaging implementations) or where there is no remote JMS server (e.g. a JMS
provider embedded in the same JVM as the application). I will try to make this text a bit more concise before adding it
to the javadocs and spec, if we're agreed on the general content.

*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.

I suggest we clarify the API as follows:

 1. The javadoc for these methods and the spec should remind developers that code in a CompletionListener must confirm
    to the threading restrictions defined in section 4.4.6. "Conventions for using a session".

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

*Restrictions on usage in Java EE*

I think that an asynchronous send should not be permitted be allowed in a Java EE EJB or web container because it
registers a callback method which is executed in a separate thread. The existing methods setMessageListener and
setExceptionListener are already prohibited for the same reason: this restriction is in both the EJB and Java EE
specifications. I therefore propose we update the spec and javadocs to say this, and invite the EJB and Java EE spec
leads to say the same.

Note that even if the Java EE EJB or web container did not have these restrictions, we would probably still want to
restrict its use in XA transactions for several reasons. It might be difficult for the commit() to guarantee that the
preceding async sends had succeeded. This is because the calls to send() and the subsequent commit might take place in
different threads.

In addition, there is no way to define the transactional state when the callback method was active, so we would probably
want to prevent the callback method from sending or receiving messages.

*Threading restrictions on the message that is sent*

The Early Draft proposed the following CompletionListener interface:

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

In the onCompletion() method, the Message being sent is passed as a parameter. This is provided to allow applications to
identify which message a given callback relates to.

This means we should also pass the Message object in the onException method.

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

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. In addition a MessageObject is not thread-safe (JMS 1.1 section
2.8).

These issues 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. I think we therefore need to add an additional warning in the javadoc:

    Applications should bear in mind that the Message passed into the CompletionListener's callback methods will be the
    same as the Message passed to the asynchronous send method. This means that if the application changes the state of
    a Message after sending it, such as by clearing its properties or sending it a second time, then this will also
    change the object passed to the CompletionListener's callback methods. In addition, applications should bear in mind
    that a Message object does not support concurrent use (see section 2.8). It is therefore recommended that after an
    application sends a message using an asynchronous send, that message is never changed or sent again. In addition the
    application must ensure that the callback method and the calling thread do not attempt to use the Message at the
    same time, such as to obtain a property value.

I think this is quite an onerous set of restrictions, and wonder whether it would be better to simply pass in the
JMSMessageID instead of the Message.

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

Please let me know what you think.

Nigel