• Post Reply Bookmark Topic Watch Topic
  • New Topic

J2EE spec "bug"? Can not use stateful session bean from other EJBs?

 
Andi Egloff
Greenhorn
Posts: 4
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
With the current spec (1.4) it seems unsafe to use a stateful session bean (SFSB) from a stateless session bean (SLSB) or in certain cases even message driven beans (MDB) when using (propagated) transactions.

But I did not see any warnings or explicit limitations in this regard in the spec. Neither did I see any other users reporting and warning on this issue. Is any one else aware of this issue?

I used the samples that come with the J2EE 1.4 reference implementation (FCS) to reproduce the problem. I chained together the "simple" mdb, stateless and stateful samples to get the following calling chain.
JMS->MDB->SLSB->SFSB

The SLSB instance acts as the SFSB client, i.e. creates a SFSB instance in its ejbCreate and removes it in the ejbRemove, so there is a 1-to-1 relationship between the SLSB instance and the SFSB. It keeps the reference as internal state.

I also modified the deployment descriptors to container managed transactions and ensured that the transaction attributes on the methods called were 'Required' so the same transaction is propagated to the MDB, to the SLSB and then the SFSB. The commit on the transaction in this scenario happens when the JMS resource adapter calls "afterDelivery" and the container completes the transaction.

To get to the point, it will result in "java.lang.IllegalStateException: EJB is already associated with an incomplete transaction" exceptions when running on the RI.

The spec states that:
- a stateful session bean stays associated with a transaction until it completes
- a stateful session bean can only participate in one transaction at a time, and should throw an exception if a client invokes it with a different transaction context whilst it is still associated
- a stateless session bean is returned to the method ready pool as soon as the business method invocation completes, and as soon as it is in the method ready pool, the container can delegate further work to it.

The above means that the SLSB is returned to the pool before the propagated transaction completes, ready for new work. But at this point the SFSB is still associated with the transaction. This transaction might not be completed for quite a while, e.g. the MDB could do more work in the onMessage before returning and the container commiting.

If the SLSB is then assigned work for another client (with a different transaction), it will try to call the potentially "already associated" stateful session bean - and the container will throw an exception as specified in the spec.

It should be noted that indirectly, the SLSB has kept client state across invocations, thanks to its SFSB staying associated with the previous transaction. This is obviously against the spec. But it should also be noted that I don't see any reasonable way to use this SFSB component from the SLSB in this scenario. For example, if the SLSB were to create an SFSB instance as part of the business method invoke (instead of ejbCreate), it would not be allowed to remove the SFSB before returning - the spec disallows ejbRemove to get called on the SFSB whilst it is still associated with a transaction (= again, an exception).

I believe that according to the spec, even the simple JMS->MDB->SFSB has the potential to fail, if the container sets the MDB to method-ready pool state right after the message listener invoke, before processing the transaction completion. In the current reference implementation, this is not a problem though as it only sets the MDB to pooled state in the afterDelivery() handling, and in there after completing the transaction.

However, if a Resource Adapter with transaction inflow contract was used instead, even in the reference implementation this scenario would fail as the completion of the transaction would not happen in the afterDelivery, only the return of the MDB to the method-ready pool.
RA (transaction inflow contract)->MDB->SFSB

Another simple sample scenario which should fail is
WAR (JSP, servlet)->SLSB->SFSB
If the transaction is started at the web component level and propagated to the EJBs.

Below is the full stack trace for the exception; when I feed 1000 messages to my scenario, the server.log easily grows to 20 MB full of these:

java.lang.IllegalStateException: EJB is already associated with an incomplete transaction
at com.sun.ejb.containers.BaseContainer.useClientTx(BaseContainer.java:2305)
at com.sun.ejb.containers.BaseContainer.preInvokeTx(BaseContainer.java:2141)
at com.sun.ejb.containers.BaseContainer.preInvoke(BaseContainer.java:726)
at com.sun.ejb.containers.EJBObjectInvocationHandler.invoke(EJBObjectInvocationHandler.java:126)
at $Proxy9.getContents(Unknown Source)
at samples.ejb.stateful.simple.ejb._Cart_Stub.getContents(Unknown Source)
at samples.ejb.stateless.simple.ejb.GreeterEJB.getGreeting(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:324)
at com.sun.enterprise.security.SecurityUtil$2.run(SecurityUtil.java:146)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.enterprise.security.application.EJBSecurityManager.doAsPrivileged(EJBSecurityManager.java:930)
at com.sun.enterprise.security.SecurityUtil.invoke(SecurityUtil.java:151)
at com.sun.ejb.containers.EJBObjectInvocationHandler.invoke(EJBObjectInvocationHandler.java:128)
at $Proxy7.getGreeting(Unknown Source)
at samples.ejb.stateless.simple.ejb._Greeter_Stub.getGreeting(Unknown Source)
at samples.ejb.mdb.simple.ejb.SimpleMessageBean.onMessage(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:324)
at com.sun.enterprise.security.SecurityUtil$2.run(SecurityUtil.java:146)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.enterprise.security.application.EJBSecurityManager.doAsPrivileged(EJBSecurityManager.java:930)
at com.sun.enterprise.security.SecurityUtil.invoke(SecurityUtil.java:151)
at com.sun.ejb.containers.MessageBeanContainer.deliverMessage(MessageBeanContainer.java:944)
at com.sun.ejb.containers.MessageBeanListenerImpl.deliverMessage(MessageBeanListenerImpl.java:42)
at com.sun.enterprise.connectors.inflow.MessageEndpointInvocationHandler.invoke(MessageEndpointInvocationHandler.java:130)
at $Proxy10.onMessage(Unknown Source)
at com.sun.messaging.jms.ra.OnMessageRunner.run(OnMessageRunner.java:165)
at com.sun.enterprise.connectors.work.OneWork.doWork(OneWork.java:45)
at com.sun.corba.ee.impl.orbutil.threadpool.ThreadPoolImpl$WorkerThread.run(ThreadPoolImpl.java:382)
javax.ejb.EJBException: nested exception is: java.lang.IllegalStateException: EJB is already associated with an incomplete transaction
at com.sun.ejb.containers.BaseContainer.preInvoke(BaseContainer.java:742)
at com.sun.ejb.containers.EJBObjectInvocationHandler.invoke(EJBObjectInvocationHandler.java:126)
at $Proxy9.getContents(Unknown Source)
at samples.ejb.stateful.simple.ejb._Cart_Stub.getContents(Unknown Source)
at samples.ejb.stateless.simple.ejb.GreeterEJB.getGreeting(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:324)
at com.sun.enterprise.security.SecurityUtil$2.run(SecurityUtil.java:146)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.enterprise.security.application.EJBSecurityManager.doAsPrivileged(EJBSecurityManager.java:930)
at com.sun.enterprise.security.SecurityUtil.invoke(SecurityUtil.java:151)
at com.sun.ejb.containers.EJBObjectInvocationHandler.invoke(EJBObjectInvocationHandler.java:128)
at $Proxy7.getGreeting(Unknown Source)
at samples.ejb.stateless.simple.ejb._Greeter_Stub.getGreeting(Unknown Source)
at samples.ejb.mdb.simple.ejb.SimpleMessageBean.onMessage(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:324)
at com.sun.enterprise.security.SecurityUtil$2.run(SecurityUtil.java:146)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.enterprise.security.application.EJBSecurityManager.doAsPrivileged(EJBSecurityManager.java:930)
at com.sun.enterprise.security.SecurityUtil.invoke(SecurityUtil.java:151)
at com.sun.ejb.containers.MessageBeanContainer.deliverMessage(MessageBeanContainer.java:944)
at com.sun.ejb.containers.MessageBeanListenerImpl.deliverMessage(MessageBeanListenerImpl.java:42)
at com.sun.enterprise.connectors.inflow.MessageEndpointInvocationHandler.invoke(MessageEndpointInvocationHandler.java:130)
at $Proxy10.onMessage(Unknown Source)
at com.sun.messaging.jms.ra.OnMessageRunner.run(OnMessageRunner.java:165)
at com.sun.enterprise.connectors.work.OneWork.doWork(OneWork.java:45)
at com.sun.corba.ee.impl.orbutil.threadpool.ThreadPoolImpl$WorkerThread.run(ThreadPoolImpl.java:382)
|#]
 
Roger Chung-Wee
Ranch Hand
Posts: 1683
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I also modified the deployment descriptors to container managed transactions and ensured that the transaction attributes on the methods called were 'Required' so the same transaction is propagated to the MDB, to the SLSB and then the SFSB.

You cannot propogate a transaction into an MDB as it has no client. Remember, it is the Container which calls the onMessage() method. The only two transaction attributes allowed for onMessage() are Required (start a new transaction) or NotSupported (no transaction required).
 
Andi Egloff
Greenhorn
Posts: 4
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
You can propagate a transaction to the MDB via the JCA 1.5 transaction inflow contract (Chapter 14 of the JCA 1.5 spec). Typically this resource adapter would not be an JMS "inbound", but an "inbound" RA for an EIS which has its own transaction manager.

In this case, the RA itself is responsible to control transaction boundaries via the XATerminator interface, to satisfy the transaction commands received from the EIS.

This means that the container will not complete the transaction after it called "onMessage" (or whatever listener interface the MDB implements), the EIS at a later point will instruct the RA to commit/rollback etc.
 
Andi Egloff
Greenhorn
Posts: 4
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
BTW the Required transaction attribute does not force the creation of a new transaction, Required will use an existing transaction context if present.
 
Andi Egloff
Greenhorn
Posts: 4
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Originally posted by Roger Chung-Wee:

You cannot propogate a transaction into an MDB as it has no client.


Sorry, I just noticed the specific line you quoted and refered to.

In the JMS->MDB->SLSB->SFSB scenario by 'propagated' to the MDB I meant that the same transaction that the container creates when the JMS RA calls "beforeDelivery" is used for the "onMessage" call. As you noted though, besides not using a transaction there isn't really another choice this MDB can make.
 
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
Boost this thread!