users@jmsjca.java.net

Re: Sessions are already closed

From: Andreas Loew <Andreas.Loew_at_Sun.COM>
Date: Tue, 06 Jan 2009 23:13:07 +0100

Hi Brian,

Brian Repko schrieb:
> Thanks for the replies. I was able to rework the cache flush
> broadcaster to not hold onto the session and that seems to work. At
> this point it only holds onto the TCF and the Topic and does everything
> needed to send a message. I'll try to answer Andreas' questions below
> and will also try to send some code as well.

so it's basically good news that everything works fine when only holding
on to the TCF and the Topic (i.e. the two resources available via JNDI
lookup). I would have assumed so :-)

I would also argue that this setup is exactly as it should be, as it is
the responsibility of the app server connection pool and resource
adapter implementation to do things like connection and session pooling
(note that behind the scenes, JMSJCA manages a 1:1 relationship between
its Connection and Session objects (so it is managing JMS connection
pools in the exact same way as is much wider known with JDBC connection
pools).

So IMHO this usage pattern is completely fine and should work pretty
well also performance-wise (have you tried logging the JMS Connection
and Session instances to verify that they are indeed pooled and reused?).

> We are not using EJBs at all. The web app framework (Struts2) objects
> (Actions) have services injected into them from Spring. Services are
> just Spring beans (singletons) with a transaction interceptor. So the
> thread will start a transaction, do some work in the Service/DAO - the
> DAO will use Hibernate and the way that Spring works, it will flush the
> Hibernate session as part of a transaction synchronization (preCommit).
> When Hibernate flushes, it will update its second level cache and that
> fires off a cache flush notification. We get notifications for all
> entities changed within the transaction so we end up sending many
> messages all within the scope of the transaction synchronization object.
> With the existing code, the session was transactional so the same one is
> used and if there was a second entity updated in the transaction, then I
> would get the state exception below.

If all this work indeed has been initiated from a beforeCompletion()
method in a javax.transaction.Synchronization instance, is executed
synchronously within one single thread (namely the current thread that
is about to complete its JTA Transaction), and is using the exact same
JMS Commection and JMS Session objects, then I would indeed expect this
code to work fine rather than throwing a "JMSJCA-E153: This Session is
closed" Exception...

Are you possibly calling session.close() or connection.close() anywhere
from your code between two subsequent send attempts? This would explain
why JMSJCA seems to have closed down the cached instance...!?


> You can see the base code for the listener in OSCache. EHCache has a
> similar beast. I've extended the OSCache code significantly in order
> to use it with Spring/Hibernate (but not in its usage of JMS objects).
> It would be interesting to try this with EHCache.
>
> I'm using the 6.0.1-SNAPSHOT RAR (the download from the main website).
> It looks like the code has changed a bit since then but I've not tried
> to get the code and build it.
>
> The following is the Ant build snippet that deploys the RAR and
> sets up the JMS resoruces:
>
> <target name="sun-jms-adapter-deploy" description="deploy the RAR">
> <sun-appserv-admin command="create-resource-adapter-config sun-jms-adapter" user="admin" passwordfile="${user.home}/ant/glassfish.properties" asinstalldir="${env.APPSERVER_HOME}" />
> <sun-appserv-deploy file="${env.APPSERVER_HOME}/lib/addons/resourceadapters/sun-jms-adapter/sun-jms-adapter.rar" name="sun-jms-adapter" upload="true" user="admin" passwordfile="${user.home}/ant/glassfish.properties" asinstalldir="${env.APPSERVER_HOME}" />
> </target>
>
> <target name="resources-jms-create" description="create JMS resources">
> <sun-appserv-admin command="create-connector-connection-pool --raname sun-jms-adapter --connectiondefinition javax.jms.TopicConnectionFactory --steadypoolsize 0 --poolresize 4 --idletimeout 25000 --transactionsupport XATransaction --isconnectvalidatereq=true --property UserName=admin:Password=admin:ConnectionURL=mq\\://localhost\\:7676/ jms/CmmoTopicConnectionFactory" user="admin" passwordfile="${user.home}/ant/glassfish.properties" asinstalldir="${env.APPSERVER_HOME}" />
> <sun-appserv-admin command="create-connector-resource --poolname jms/CmmoTopicConnectionFactory jms/CmmoTopicConnectionFactory" user="admin" passwordfile="${user.home}/ant/glassfish.properties" asinstalldir="${env.APPSERVER_HOME}" />
> <sun-appserv-admin command="create-admin-object --restype javax.jms.Topic --raname sun-jms-adapter --property Name=CmmoCacheFlushTopicDestination jms/CmmoCacheFlushTopic" user="admin" passwordfile="${user.home}/ant/glassfish.properties" asinstalldir="${env.APPSERVER_HOME}" />
> <sun-appserv-admin command="create-admin-object --restype javax.jms.Topic --raname sun-jms-adapter --property Name=CmmoImportBundlesTopicDestination jms/CmmoImportBundlesTopic" user="admin" passwordfile="${user.home}/ant/glassfish.properties" asinstalldir="${env.APPSERVER_HOME}" />
> <sun-appserv-admin command="create-admin-object --restype javax.jms.Topic --raname sun-jms-adapter --property Name=CmmoImportAssetsTopicDestination jms/CmmoImportAssetsTopic" user="admin" passwordfile="${user.home}/ant/glassfish.properties" asinstalldir="${env.APPSERVER_HOME}" />
> </target>

IMHO, this setup is completely fine.

> You'll notice that we aren't using ProducerPooling since this seems
> to be deprecated with the version 6 adapter.

I think this is a misunderstanding: If I read the docs correctly, it
only says for the connection factory property named "ProducerPooling":
"deprecated; use JMSJCA.producerpooling instead", which means that on
the contrary you are *encouraged* to still use it, but pass it as an RA
option.

As the docs say, "RA Options can be specified in:

    1. the Options field in the general section of the ra.xml; the
options field is a serialized Java properties set. In short: this field
consists of key-value pairs. One pair per line. A key is separated from
the value using a = sign. Comments are indicated using ! or #.
    2. the Options field of the connection factory in the ra.xml, or in
the activation spec of the ejb-jar.xml. Format: see 1. Takes precedence
over 1.
    3. the connection URL in the form of query parameters. Takes
precedence over 2."

So my understanding is that this was intended to provide a more flexible
configuration, not to remove this feature.


> As for Spring, we have 2 use cases - one that is pure Spring and does
> seem to work with the Containers - and another where we use Spring
> classes directly. That last one fails but it failed without Spring
> classes as well (straight JMS).
>
> For the one that works we are doing the following:
>
> <bean id="cmmo_jmsImportBundlesContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
> <property name="connectionFactory" ref="cmmo_topicConnectionFactory"/>
> <property name="destination" ref="cmmo_bundleDestination"/>
> <property name="messageListener" ref="cmmo_importBundlesListener" />
> </bean>
>
> <bean id="cmmo_importBundlesJmsTemplate" class="org.springframework.jms.core.JmsTemplate">
> <property name="connectionFactory" ref="cmmo_topicConnectionFactory"/>
> <property name="defaultDestination" ref="cmmo_bundleDestination" />
> </bean>
>
> <bean id="cmmo_bundleDestination" class="org.springframework.jndi.JndiObjectFactoryBean">
> <property name="jndiName" value="${jms.topic.bundle_import}"/>
> </bean>
>
> <bean id="cmmo_importBundlesSender" class="com.biperf.cmmo.service.message.async.CmmoImportBundlesSender">
> <property name="jmsTemplate" ref="cmmo_importBundlesJmsTemplate"/>
> </bean>
>
> <bean id="cmmo_importBundlesListener" class="com.biperf.cmmo.service.message.async.CmmoImportBundlesListener">
> <property name="messageService" ref="cmmo_messageService" />
> </bean>
>
> <bean id="cmmo_topicConnectionFactory" class="org.springframework.jms.connection.TransactionAwareConnectionFactoryProxy">
> <property name="targetConnectionFactory" ref="internalJmsTopicConnectionFactory"/>
> </bean>
>
> <bean id="internalJmsTopicConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
> <property name="jndiName" value="${jms.topic.factory}"/>
> </bean>

Again, I think this setup is completely fine with one possible exception
for inbound configuration:

> Andreas - you mentioned that this wouldn't work but it does for us
> with Spring 2.5.3 and JMSJCA 6.0.1.

I did so because when using a Spring DefaultMessageListenerContainer
with the "cmmo_topicConnectionFactory" as defined below, you are
effectively using the JMSJCA "outbound" ConnectionFactory resource to
create an inbound JMS connection/message consumer.

(For the exact distinction between outbound and inbound resources -
inbound resources have only been added with JCA 1.5/J2EE 1.4 -, please
see the JCA spec.)

In this case, using the outbound CF/connection pool to create an inbound
connection happens to work fine for plain JMS, but you'll effectively
bypass JMSJCA and therefore lose any more sophisticated JMSJCA features
such as redelivery handling, message wrapping, batching etc.: All of
these *require* the use of the proper "inbound" JCA resource, a.k.a. the
RA Activation object in order to deliver its messages.

But it's relatively painless to switch from a MessageListenerContainer
to a JmsMessageEndpointManager. The following snippet shows the Spring
configuration needed to make this work with Glassfish:

     <bean id="workManager" class="org.
springframework.jca.work.glassfish.GlassFishWorkManagerTaskExecutor" />

     <bean id="resourceAdapter"
class="org.springframework.jca.support.ResourceAdapterFactoryBean">
         <property name="resourceAdapter">
             <bean
class="com.stc.jmsjca.unifiedjms.RAUnifiedResourceAdapter">
                 <property name="connectionURL" value="jndi://"/>
             </bean>
         </property>
         <property name="workManager" ref="workManager" />
     </bean>

     <bean id="subscriptionChangeEventMessageEndpointManager"
class="org.springframework.jms.listener.endpoint.JmsMessageEndpointManager">
         <property name="resourceAdapter" ref="resourceAdapter"/>
         <property name="activationSpec">
             <bean
class="com.stc.jmsjca.unifiedjms.RAUnifiedActivationSpec">
                 <property name="destination"
value="lookup://jms/SubscriptionChangeEvents" />
                 <property name="destinationType" value="javax.jms.Topic" />
                 <property name="subscriptionDurability"
value="NonDurable" />
                 <!-- <property name="endpointPoolMaxSize" value="1" /> -->
                 <property name="concurrencyMode" value="serial" />
                 <property name="redeliveryHandling"
value="2:1000;5:delete" />
                 <property name="contextName"
value="ArbitraryContextNameDescribingYourUseCase" />
                 <property name="options"
value="JMSJCA.sep=,JMSJCA.TopicCF=jms/XAInTopicCF,JMSJCA.QueueCF=jms/XAInQueueCF,JMSJCA.UnifiedCF=jms/XAInUnifiedCF"
/>
             </bean>
         </property>
         <property name="messageListener"
ref="subscriptionChangeEventListener" />
     </bean>


> For the scenario that doesn't work, we have code that is based on
> the JMSBroadcastListener within OSCache as per http://www.opensymphony.com/oscache/wiki/Clustering.html
> This code holds onto (as instance variables) a Connection, Session, MessageProducer
> and MessageConsumer.

> (...)

> and again, if I did this code 2 times in a transaction, then I would
> get the state issue.

> Again, I've worked around this by changing what objects I was holding
> on to but I'm still curious about what is the best way to setup something
> like this (again, EHCache does something similar).

As stated above, as long as you are running the listener as well as the
sender code from the same thread within the same XA transaction, then it
should indeed work.

Have you checked here as well that you are not explicitly closing either
the JMS Connection or the JMS Session object by accident?

Also, have you taken care that, when processing multiple requests, a)
implementation is thread-safe, i.e. the same JMS Connection and JMS
Session instances are not shared by several threads and b) even from the
same thread, the exact same Connection and Session instance are not
being used from different XA transactions (i.e. a JMS Connection is
retrieved "freshly" from the pool and a Session freshly created at the
beginning of each XA transaction and only cached/reused from that
transaction)?

> I'll try to put together a small sample application and double-check
> the OSCache codebase to see if they have changed their code.

As I tend to agree with you that - if you didn't run into any of the
pitfalls mentioned above - caching the JMS objects in the way you
described should indeed work, I would be interested to see a
stripped-down test case that produces the "JMSJCA-E153" exception...

Looking forward to your comments, best regards

Andreas

-- 
Andreas Loew
Senior Java Architect
Sun Microsystems (Germany)