jsr343-experts@jms-spec.java.net

[jsr343-experts] Re: [jms-spec users] Re: Re: Late issue: Calling MessageConsumer#close from onMessage

From: Nigel Deakin <nigel.deakin_at_oracle.com>
Date: Mon, 04 Feb 2013 10:47:18 +0000

(resent to EG)

On 01/02/2013 16:42, Chris Barrow wrote:
> If an application needs to stop the message flow it can always just set the message listener to null within the message listener itself. AFAIK that is perfectly legal according to the spec. So that's a possible work around for A.

Yes, that's legal. The spec doesn't say exactly what setMessageListener(null) does (the API docs says "setting the the
|MessageListener| to null is the equivalent of unsetting the |MessageListener| for the |MessageConsumer|", which seems
circular to me). It doesn't mention any need for that method to block until the existing listener has returned, so we
don't have a deadlock problem.

However this means that there isn't a clearly-defined point at which the message listener thread ceases to be the thread
of control (presumably it occurs after onMessage has returned and the message listener thread has had time to perform
any post-callback housekeeping on the session such as performing auto-ack. Only after this undefined point is it legal
to call close() from another thread, or set another listener.

I suspect the JMS authors simply didn't consider this, and if they had they would have applied similar rules to close()
- that setMessageListener(null) should block until the current listener has returned.

All this makes me wary of considering setMessageListener(null) as a workaround given that it is poorly defined.

> Having said that I would be in favor of both A and B. For A I would recommend allowing consumer.close to be called from any message listener, for simplicity. Since all listeners for a session run on the same thread by spec there's no need to restrict it to the listener on the consumer that's being closed.

I note your support for both A and B.

If we agree on both A and B (and more views are needed, please), then what would the actual wording be?

*Wording for B
-------------------
*
For B we would change 4.4.6. "Conventions for using a session". This currently says

"It is erroneous for client code to use such a session from another thread of control. The only exception to this is the
use of the session or connection close method."

We would change it to say:

"It is erroneous for client code to use such a session from another thread of control. The only exception to this is the
use of the consumer, session or connection close method."

*Wording for A*
*-------------------*

For A we would change the API doc for consumer.close().

In JMS 1.1 this simply says (in the API doc)

/This call blocks until a |receive| or message listener in progress has completed.
/
The reference to "receive" is nonsense, as far as I can see, and we should simply drop it. Calling consumer.close() in
one thread whilst calling consumer.receive() in another would break existing threading restrictions on a session and is
never valid.

If we adopted *A+B* this could be changed to say:

/If there is a message listener configured on this consumer, and this method is called from a thread other than the
thread of control which delivers messages to it, then this method must block until that message listener has completed.
/
This allows close() to be called from any of the sessions's message listeners.

If we adopted *A only* then we could simply drop the whole sentence. This would then allow close() to be called from a
listener on its own consumer, of from any other listener using the same thread of control (which is what Chris suggests).

More views please?

Nigel


> Chris
>
> Sent from my iPhone
>
> On Feb 1, 2013, at 8:07 AM, Nigel Deakin<nigel.deakin_at_oracle.com> wrote:
>
>> Thanks for the comments on this issue.
>>
>> The only EG member who replied was RĂ¼diger, who voted for A+B
>> I vote for A+B, with a slight preference for deferring B until 2.1.
>>
>> A community member "earmitage" emailed me directly to support A. He/she wrote "I think closing a message consumer can be classified as application behavior with many business scenarios describable with words like receive until condition x is met so allowing application code (onMessage) to close the consumer seems prudent enough"
>>
>> I think we need more views, especially from vendors.
>>
>> I've pasted my earlier message below. (If it isn't clear please say so)
>>
>> Nigel
>>
>> -------- Original Message --------
>> Subject: [jsr343-experts] Late issue: Calling MessageConsumer#close from onMessage
>> Date: Fri, 18 Jan 2013 18:52:11 +0000
>> From: Nigel Deakin<nigel.deakin_at_oracle.com>
>> Reply-To:jsr343-experts_at_jms-spec.java.net
>> Organization: Oracle Corporation
>> To:jsr343-experts_at_jms-spec.java.net
>>
>> I think an issue has arisen with JMS_SPEC-48, which clarified the issue of what should happen if MessageConsumer#close,
>> Session#close and Connecton#close was called from within a message listener's onMessage method.
>>
>> Background
>> ----------
>>
>> Here's a reminder of this issue (which was originally raised by Graham Wallis of IBM):
>>
>> In JMS 1.1:
>>
>> * the API docs for Connection#close stated that it "should not return until...all message listeners that may have been
>> running have returned"
>>
>> * the API docs for Session#close stated that "This call will block until a... message listener in progress has completed"
>>
>> * the API docs for MessageConsumer#close stated that "This call blocks until a... message listener in progress has
>> completed.
>>
>> In all cases, this means that if one of these close methods is called within a message listener's onMessage method then
>> it should never return, causing deadlock.
>>
>> In JMS 2.0 we agreed that this didn't make sense, so we defined that:
>>
>> * A message listener must not attempt to close its own connection as this would lead to deadlock. The JMS provider must
>> detect this and throw a IllegalStateException.
>>
>> * A MessageListener must not attempt to close its own Session as this would lead to deadlock. The JMS provider must
>> detect this and throw a IllegalStateException.
>>
>> * A MessageListener must not attempt to close its own MessageConsumer as this would lead to deadlock. The JMS provider
>> must detect this and throw a IllegalStateException.
>>
>> Problem
>> -------
>>
>> I think we may have "painted ourself into a corner" here, at least in the case of MessageConsumer#close
>>
>> Consider an application that creates a MessageConsumer and calls setMessageListener to configure a message listener.
>> Once this method has been called (and the conneciton started), then it is prohibited to call MessageConsumer#close from
>> any thread other than the one which delivers messages to the message listener.
>>
>> (JMS 1.1 section 4.4.6 states: "...once the first message listener for a session has been registered, the session is now
>> controlled by the thread of control that delivers messages to it. At this point a client thread of control
>> cannot be used to further configure the session". This section explicitly states that calling Session#close or
>> Connection#close is exempt from this restriction, but does not mention MessageConsumer#close.)
>>
>> However in JMS 2.0 we have said that it is prohibited to call MessageConsumer#close from the message listener as well.
>>
>> This means that there is no way of ever calling MessageConsumer#close when a message listener is being used. This is
>> clearly a nonsensical state of affairs which we need to resolve.
>>
>> Possible solutions
>> ------------------
>>
>> I think there are two fairly obvious solutions:
>>
>> Solution A. Change the specification for MessageConsumer#close to allow it to be called from a message listener's
>> onMessage on its own consumer.
>>
>> Solution B. Add MessageConsumer#close to the list of methods exempted from the "single thread of control" restriction on
>> a session, just like Session#close and Connection#close.
>>
>> Solution C. Both (A) and (B) together.
>>
>> Discussion: Solution A
>> ----------------------
>>
>> Allowing MessageConsumer#close to be called from a message listener on its own consumer would make it possible to create
>> simple applications which need to consume a specific number of messages. After a sufficient number of messages have been
>> received, onMessage calls MessageConsumer#close and no further messages are delivered.
>>
>> Discussion: Solution B
>> ----------------------
>>
>> Allowing MessageConsumer#close to be called from a thread other than the one which is calling onMessage would be
>> consistent with Session#close and Connection#close, which are already allowed to be called from a thread other than the
>> one which is calling onMessage.
>>
>> However allowing this on its own does not allow the fine-grained control over the number of messages delivered that is
>> offered by (A) .
>>
>>
>>
>> Affect of consumer close on message acknowledgement
>> ---------------------------------------------------
>>
>> If we chose (A) then we'd need to decide what happens to the message which was being delivered at the time.
>>
>> I suggest we define that consumer.close() has no effect on message acknowledgement. After all, acknowledgement is really
>> a function of the session rather than the consumer, and the session is still open.
>>
>> We could clarify the behaviour for each acknowledgement mode as follows:
>>
>> * Auto-acknowledge: calling consumer.close() has no effect on message acknowledgement. The message will be automatically
>> acknowledged when onMessage returns.
>>
>> * Client-acknowledge: calling consumer.close() has no effect on message
>> acknowledgement. If the message listener calls Message#acknowledge (or
>> the new JMSContext#acknowledge) then the message will be acknowledged.
>> If the message listener does not call acknowledge then the message will
>> be redelivered after the session is closed or recovered. It makes no
>> difference whether the message is acknowledged before or after the
>> consumer is closed.
>>
>> * Local transaction: calling consumer.close() has no effect on
>> transaction commit or rollback. If the message listener calls
>> Session#commit (or the new JMSContext#commit) then the transaction will
>> be committed. If the message listener calls rollback then the transaction
>> will be rolled back and the message will be redelivered. It makes no
>> difference whether the transaction is committed or rolled back before or
>> after the consumer is closed.
>>
>> Questions
>> ---------
>>
>> So, which do you prefer? A, B or A+B?
>>
>> Nigel
>>