Discussions

EJB programming & troubleshooting: J2EE spec bug? Can not use stateful session bean from other EJBs

  1. 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)
    |#]
  2. I am hard pressed to imagine why you would need to use a SFSB this way. After all, the whole purpose of a SFSB is to hold state information for the client (i.e. end user). If you invoke a SFSB from either a MDB or SLSB, there will be no new state information coming into the call other than the original data passed to the MDB or SLSB.

    You might be wanting to use a SLSB as a caching mechanism, but this is definitely the wrong way to do caching. You can either (a) cache temporary data in local or instance variables of your MDB/SLSB, (b) cache constant data as singletons or data initialized in the ejbCreate() method or (c) really on the caching mechanisms of your persistence layer (Entity or ORM layer).

    Before you abuse the spec as badly as this, you might want to come up with a reason why this will give you some benefit. Personally, I don't see a scenario where this would help you.
  3. I agree with your design suggestions, let me put it this way: this isn’t my design. It is an exception which occurs with an existing implementation.

    In their case the SLSB could be thought of as the Invoker in the command pattern, and it is supplied with multiple user supplied ‘commands’. The way they did it, the commands can’t know about each other or interact, and the Invoker has no knowledge about what it is executing. However, multiple sequential commands may need to operate on the same (potentially remote) state and connections, in this case modeled as state of a SFSB.

    I have to convince them that this is not a problem with the server, but that this is the wrong thing to do, and that they need to spend resources on fixing this.

    The part I do think is lacking (if I am right) is that the spec should explicitly note if components are incompatible. Right now I can’t just point them to the spec and say “see!”. From their point of view, the SLSB is a ‘client’ to the SFSB. If you read the spec that way, the only arguments I have is that the behaviors defined by the spec mean that the life cycles of the two are incompatible, and therefore implicitly can not be used with each other.
  4. Ah! I understand your problem now. You agree that SFSB should not be used in this way. Your complaint is that the spec does not make it 100% clear that calling SFSB from a MDB or SLSB is bad practice.

    Unfortunately, as far as I know, the spec never comes out and says that this usage pattern is bad. In fact, there are a lot of places where the spec is ambiguous about proper usage patterns.

    In the end, though, what the spec says does not really matter. What does matter is what actually works on real servers. Try demonstrating that this usage pattern fails on several different servers (weblogic, websphere, the reference implementation). If you can demonstrate a consistent pattern of failure, that may be good enough.

    If not ... sometimes you can't convince your client not to do a stupid thing. Believe me, I've been in that situation before.