1 2 Previous Next 16 Replies Latest reply on May 2, 2013 6:01 AM by joe.chuah

    Session and thread safety

    marcin.galazka

      Hi

       

      jBPM 5.2, PostgreSQL 9.1.

       

      Kris wrote (https://community.jboss.org/message/613360#613360) that session is thread safe. Well, getWorkItemManager() present in various implementations (e.g. CommandBasedStatefulKnowledgeSession) does not look particulary safe.

       

      Anyway, my biggest issue is with SingleSessionCommandService. It uses *single* EntityManager instance to perform a database-related operations - see JpaPersistenceContextManaager#getApplicationScopedPersistenceContext, it returns the same (wrapped) appScopedEntityManager. Now, there are two scenarios wrt how SingleSessionCommandService#execute() can be called:

      - there is no active JTA transaction,

      - there is an active JTA transaction (e.g. the method is called from a business layer where someone already started a JTA transaction).

       

      First one is not interesting - execute() will create a new transaction, do its work and commit the transaction. So far so good.

       

      Second one means that in case of many threads calling SingleSessionCommandService#execute() at the same time the same connection will be used to perform many transactions *at the same time*. Now you can pick your favourite exception:

      - org.postgresql.xa.PGXAException: Transaction interleaving not implemented (see http://jdbc.postgresql.org/documentation/faq.html#transaction-interleaving)

      - org.hibernate.AssertionFailure - an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session)

      - org.postgresql.xa.PGXAException: Connection is busy with another transaction

       

      You can say that SingleSessionCommandService#execute() is synchronized but it does not help here. The commit (and the flush from EntityManager) happens elsewhere. Overall I think that SingleSessionCommandService is playing a very dangerous game in JTA managed environment. What is the rationale for *SingleSession*CommandService and PersistenceContextManager#getApplicationScopedPersistenceContext() anyway?

       

      A simple test case attached, run few times or increase totalWorkers if you do not get exception at the first pass. If you want more standalone (e.g. Maven based) example let me know. A Spring-based simple web application can be provided as well.

        • 1. Re: Session and thread safety
          salaboy21

          Marcin, first of all great initiative!

          Please provide a maven project with some tests around this issue and I will personally take a look on that. If we can have a project with the postgre dependencies we can also check that everything works well in different databases. If you add the spring configurations and the different options it will be great.

          If you want to contact me privately to do a follow up of the progress and the analyses feel free to drop me an email, but I suggest you to keep it here in the community forums so everybody can leverage our work and fixes.

          Cheers

          • 2. Re: Session and thread safety
            marcin.galazka

            Test case attached at first post (workflow-test-case.zip). Run mvn clean test and wait some time. Test will pass if you remove transaction from ThreadSaferyTest#startProcessTest. This way SingleSessionCommandService#execute() will start its own transaction instead joining existing.

             

            I will add another (with Spring managed transactions) test case later.

            • 3. Re: Session and thread safety
              salaboy21

              Cool, I will start looking at it tomorrow. I will try it with mysql and then I will install postgre to see if the behavior looks similar.

              • 4. Re: Session and thread safety
                marcin.galazka

                New version (workflow-test-case-v2.tar.gz) attached. Use 'mvn -Dgroups=spring clean test' to run Spring based test or 'mvn -Dgroups=simple clean test' to run non-Spring based test. Do not run both tests at once ('mvn clean test') - it looks that due to the some background thread spawned by Bitronix TM a data source isn't unbinded from JNDI fast enough and you will get 'java.lang.IllegalArgumentException: resource with uniqueName 'jdbc/jbpm-ds' has already been registered'. I did not investigate the matter further, it is irrelevant here.

                 

                I've also fixed parallel tests (mis)configuration.

                 

                Every tests (Spring, non-Spring) contains two methods:

                - testOwnTransaction - should pass.

                - testJoinedTransactions - should fail.

                • 5. Re: Session and thread safety
                  marcin.galazka

                  Any news?

                  • 6. Re: Session and thread safety
                    calbazasebastian

                    I'm having the same issue... JTA managed transactions with a single statefull session that is accesed concurrently...

                    Because  SingleSessionCommandService uses a single instance of JpaPersistenceContextManager that returns the same EntityManager s,  results in inconsistent behaviour when it's accesed concurrently. My workaround was to implement a thread safe ProcessPersistenceContextManager and set it in the drools Environment when creating the session. ( env.set(EnvironmentName.PERSISTENCE_CONTEXT_MANAGER, persistenceContextManager));.

                    • 7. Re: Session and thread safety
                      hhcofcmds

                      The same problem here, still exists in JBpm5, 5.3.0.Final

                      The problem is that SingleSessionCommandService calls beginCommandScopedEntityManager on the PersistenceContextManager, but the endCommandScopedEntityManager in called when the transaction commits. When used in an EE environment, the JTA transaction commits well after the SingleSessionCommandService exits the synchronized block.

                      If another requests comes, and the execution enter the critical section, the CMD_SCOPED_ENTITY_MANAGER is still stored in the environment, so the new request will just use the EntityManager from the previous request, which clearly causes serious problems.

                      I think that this should be reportes as a bug in Jbpm5, because this prevents the correct usage of JBpm in an enterprise container. What do you think?

                      In my workaround, I created a CDI interceptor that synchronizes methods that use KnowledgeSession on a higher level.

                      • 8. Re: Session and thread safety
                        rahulamt

                        Hi Marcin,

                         

                        Any updates on this issue?

                         

                        I think the issue I was facing, as been described here is https://community.jboss.org/thread/204460 , is because of SingleSessionCommandService not being thread safe.

                        • 9. Re: Session and thread safety

                          Oh , it seem it is a critical issue , but i feel so sad no official updates on this issue so far  .Can anyone clarify that this issue still exsit or not , or already been fixed?

                          • 10. Re: Session and thread safety
                            calbazasebastian

                            Here is my thread safe ProcessPersistenceContextManager if someone needs it.

                            It needs to be set on the Environment param (env.set(EnvironmentName.PERSISTENCE_CONTEXT_MANAGER, persistenceContextManager);) before you  pass it to JPAKnowledgeService.newStatefulKnowledgeSession(kbase,null, env)

                             

                            -----------------------------------------

                            public class MyJpaProcessPersistenceContextManager implements

                                    ProcessPersistenceContextManager, PersistenceContextManager {

                             

                                private EntityManagerFactory emf;

                             

                                private ThreadLocal<EntityManager> appScopedEntityManager=new ThreadLocal<EntityManager>();

                                protected ThreadLocal<EntityManager> cmdScopedEntityManager=new ThreadLocal<EntityManager>();

                             

                                public ProcessPersistenceContext getProcessPersistenceContext() {

                                    return new JpaProcessPersistenceContext(cmdScopedEntityManager.get());

                                }

                             

                                public MyJpaProcessPersistenceContextManager(Environment env) {

                                    this.emf = (EntityManagerFactory) env.get(EnvironmentName.ENTITY_MANAGER_FACTORY);

                                }

                             

                                public PersistenceContext getApplicationScopedPersistenceContext() {

                                    if (this.appScopedEntityManager.get() == null) {

                                            this.appScopedEntityManager.set(this.emf.createEntityManager());

                                    }

                                    return new JpaPersistenceContext(appScopedEntityManager.get());

                                }

                             

                                public PersistenceContext getCommandScopedPersistenceContext() {

                                    return new JpaPersistenceContext(this.cmdScopedEntityManager.get());

                                }

                             

                                public void beginCommandScopedEntityManager() {

                                    if (cmdScopedEntityManager.get() == null ||

                                            (this.cmdScopedEntityManager.get() != null && !this.cmdScopedEntityManager.get().isOpen())) {

                                        this.cmdScopedEntityManager.set( this.emf.createEntityManager());

                                    }

                                    cmdScopedEntityManager.get().joinTransaction();

                                    appScopedEntityManager.get().joinTransaction();

                                }

                             

                                public void endCommandScopedEntityManager() {

                                    if (this.cmdScopedEntityManager.get()!=null){

                                        this.cmdScopedEntityManager.get().flush();

                                        this.cmdScopedEntityManager.get().close();

                                    }

                                }

                             

                                public ThreadLocal<EntityManager> getCmdScopedEntityManager() {

                                    return cmdScopedEntityManager;

                                }

                             

                                public ThreadLocal<EntityManager> getAppScopedEntityManager() {

                                    return appScopedEntityManager;

                                }

                             

                                public void dispose() {

                                        if (this.appScopedEntityManager.get() != null && this.appScopedEntityManager.get().isOpen()) {

                                            this.appScopedEntityManager.get().flush();

                                            this.appScopedEntityManager.get().close();

                                        }

                                        this.appScopedEntityManager.set(null);

                                        if (this.cmdScopedEntityManager.get() != null && this.cmdScopedEntityManager.get().isOpen()) {

                                            this.cmdScopedEntityManager.get().flush();

                                            this.cmdScopedEntityManager.get().close();

                                        }

                                        this.cmdScopedEntityManager.set(null);

                                }

                            }

                            • 11. Re: Session and thread safety
                              calbazasebastian

                              As a note.. if you are using JPAWorkingMemoryDbLogger you need to override the  getEntityManager/0 method. Mine is like this:

                              final JPAWorkingMemoryDbLogger logger = new JPAWorkingMemoryDbLogger(

                                              ksession) {

                                          @Override

                                          protected EntityManager getEntityManager() {

                                              return persistenceContextManager.getCmdScopedEntityManager().get();

                                          }

                                      };

                              • 12. Re: Session and thread safety
                                rahulamt

                                One of the solution can be to have as many StatefulKnowledgeSession as process instances. The jbpm document mentions that creating a statefulknowledgesession is lightweight,  it is the knowlegebase creation that is heavy. So we can create knowledgebase once and knowledgesession multiple times.

                                 

                                Chapter 5. Core Engine: API

                                A knowledge base can be shared across sessions and usually is only created once, at the start of the application (as creating a knowledge base can be rather heavy-weight as it involves parsing and compiling the process definitions). Knowledge bases can be dynamically changed (so you can add or remove processes at runtime).

                                Sessions can be created based on a knowledge base and are used to execute processes and interact with the engine. You can create as many independent session as you need and creating a session is considered relatively lightweight.

                                • 13. Re: Session and thread safety
                                  mmanski

                                  Hi,

                                  I use JBPM 5.3.0.Final in my project. My application uses EJB 3.1/JPA 2.0 and runs on JBoss 7.1.1.Final. I have similar problem with thread safety.

                                  When I use one session for whole application, strange exceptions are thrown (e.g. "javax.persistence.PersistenceException: error during managed flush ... caused by: org.hibernate.AssertionFailure: collection [null] was not processed by flush()", "javax.persistence.PersistenceException: error during managed flush ... Caused by: java.util.ConcurrentModificationException" or "org.hibernate.HibernateException: Found two representations of same collection: org.jbpm.persistence.processinstance.ProcessInstanceInfo.eventTypes") if many users use the application simultaneously. I tried to use the ProcessPersistenceContextManager presented by Sebastian, but it didn't resolve the problem - errors still appear.

                                   

                                  When I try to user another solution and create new session for every request, I don't know how to dispose sessions. I read in documentation that this method must always be called after finishing using the session, or the engine will not free the memory used by the session. However, I don't know how to correctly dispose a session.

                                   

                                  If I try to dispose session before end of transaction, an exception is thrown during committing transaction: "javax.persistence.PersistenceException: error during managed flush ... Caused by: java.lang.IllegalStateException: Illegal method call. This session was previously disposed".

                                  On the other hand, if i dispose session after transaction commits, another exception is thrown: "Could not commit session: javax.persistence.TransactionRequiredException: No active JTA transaction on joinTransaction call".

                                   

                                  Does anyone know what is the correct way to dispose a session?

                                  • 14. Re: Session and thread safety
                                    swiderski.maciej

                                    Guys, I created jira issue for this so please keep an eye on it for updates. Please put all your comments/ideas/recommendations in jira as well

                                     

                                    Cheers

                                    1 2 Previous Next