spring-projects / spring-framework

Spring Framework
https://spring.io/projects/spring-framework
Apache License 2.0
56.27k stars 37.99k forks source link

MessageListenerContainers should invalidate the Session properly in case of JMSException [SPR-8616] #13259

Closed spring-projects-issues closed 1 year ago

spring-projects-issues commented 13 years ago

Sam Brannen opened SPR-8616 and commented

Status Quo

When an exception is caught in AbstractPollingMessageListenerContainer's doReceiveAndExecute() method, AbstractPollingMessageListenerContainer delegates to AbstractMessageListenerContainer's handleListenerException() method. If the supplied Throwable is a JMSException, it gets passed to AbstractMessageListenerContainer's invokeExceptionListener() method for further processing. In turn, invokeExceptionListener() calls onException() on the JMS ExceptionListener that was registered with the AbstractMessageListenerContainer.

AbstractMessageListenerContainer's invokeExceptionListener() method does not call onException() on the JMS ExceptionListener of the underlying JMS Connection.

Consequently, connection errors that occur while processing inbound messages in a Spring MessageListenerContainer do not get propagated to the underlying connection. If the underlying connection is a shared connection that was created by Spring's SingleConnectionFactory or CachingConnectionFactory, that now broken connection will be reused by subsequent clients even though it should have been reset due to the connection failure. This leads to an endless loop of retries with invalid connections that will never work, and in the end the application will likely have to be restarted in order to obtain new connections.

Goal

AbstractMessageListenerContainer's invokeExceptionListener() method should call onException():

Note that DefaultMessageListenerContainer's refreshDestination() method already provides similar support for refreshing a possibly invalid destination name for a CachingDestinationResolver.

Work-around

The current work-around is to set the exceptionListener property of the DefaultMessageListenerContainer to the CachingConnectionFactory. This works since CachingConnectionFactory implements the JMS ExceptionListener interface, and CachingConnectionFactory's onException() method will internally reset the connection (i.e., clear the cache). Note, however, that the exceptionListener property is unfortunately not exposed as an attribute via the <jms:listener-container> XML namespace. The following XML-based configuration has been tested as a suitable work-around configuration:

<bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
    <property name="connectionFactory" ref="cachingConnectionFactory" />
    <property name="exceptionListener" ref="cachingConnectionFactory" />
    <property name="destinationName" value="my.queue.name" />
    <property name="messageListener" ref="myMessageListener" />
</bean>

Affects: 3.0.5

Reference URL: http://forum.springsource.org/showthread.php?96821-DefaultMessageListenerContainer-CachingConnectionFactory-tomcat-and-WebSphere-MQ

spring-projects-issues commented 10 years ago

Stéphane Nicoll commented

does not call onException() on the JMS ExceptionListener of the underlying JMS Connection.

Sam, just to be sure, you meant ConnectionFactory there, right? And we're talking about "our" ConnectionFactory decorators there, right? It still unclear to me what the difference it would be with a caching connection factory provided by the JMS broker: that one would not be notified anyway...

spring-projects-issues commented 10 years ago

Sam Brannen commented

Hi Stéphane,

It's been almost three years since I opened this issue. So my recollection of the exact details is a bit rusty. ;)

Sam, just to be sure, you meant ConnectionFactory there, right?

Good question. I think I actually meant Connection. Though based on the Javadoc for javax.jms.Connection.setExceptionListener(ExceptionListener), perhaps we shouldn't be doing that.

However, my main goal was to solve the problem described in the forum (see link from this issue). I ran into the exact same issue and solved it as I described in the forum and this issue's description.

In the very least, I think that Spring's CachingConnectionFactory should be automatically given a chance to recover from such exceptions, which is not currently the case.

Does that help clarify things?

Cheers,

Sam

spring-projects-issues commented 10 years ago

Stéphane Nicoll commented

It does.

That being said, I am not sure that's how we should handle it. When such an issue happens, calling onException will clear any session that was managed by the CachingConnectionFactory whereas what we're looking after is a way to invalidate the Session.

One way to deal with this issue would be to introduce a specific interface that ConnectionFactory could implement. DMLC could detect if such interface is available and invoke a callback so that only that particular session (and the related connection) is handled.

Juergen Hoeller, what do you think?

jhoeller commented 1 year ago

Reading the description above, this sounds like misbehavior on the side of the JMS broker. The broker itself needs to notify the Connection-registered ExceptionListener, not the consumer. Once that works, a connection reset should work automatically, even with CachingConnectionFactory. As for resetting a particular session only, since they typically all come from the same connection, it seems sensible to always clear all sessions and start with a fresh setup there.