7 Replies Latest reply on Aug 9, 2012 2:07 PM by jbize

    Drools Spring Integration, errors in DroolsSpringTransactionManager

    jbize

      I have an application that integrates jBPM, Spring, Hibernate/JPA, JPA2, and Richfaces4.  It's mostly working ok, except sometimes I am/was getting NullPointer exceptions from the DroolsSpringTransactionManager.  I'm using drools-spring 5.3.1.Final.  This class is apparently a (broken?) wrapper class around (in my case), the org.springframework.orm.jpa.JpaTransactionManager.

       

      Looking at the source code, I observed several things. There is an obvious bug in the begin() method in that if the call to getStatus() returns STATUS_NO_TRANSACTION, which it will if the wrapped tm (ptm) is null, it immediately dereferences the null ptm. (this.ptm.getTransaction(td)).  I'm not too woried about this one as it seems rather unlikely.

       

      What's happening for me (in the getStatus() method) is that it's making the call "transaction = this.ptm.getTransaction(td);" which is occasionally throwing an "IllegalStateException: Transaction already active," wrapped in an outer RuntimeException and dropping down to the finally block.  There, the still null transaction is committed, throwing an NPE that masks the original exception.  To mitigate this, I added a catch RuntimeException and if the cause is an IllegalStateException, I return STATUS_UNKNOWN.  In the finally block, I also ensure that I don't try to commit a null transaction.

       

      Clearly this code is fragile, and https://issues.jboss.org/browse/JBRULES-2791 is probably correct, but can anyone suggest why I'm sometimes getting the "IllegalStateException: Transaction already active" exceptions in the first place.  I am using 4 separate persistence units (and transaction managers), two for basic components of my application, one for the jBPM process tables, and one for the LocalTaskManager Task tables.

       

      Copying the code from: https://github.com/droolsjbpm/droolsjbpm-integration/blob/master/drools-container/drools-spring/src/main/java/org/drools/container/spring/beans/persistence/DroolsSpringTransactionManager.java, my mitigating changes start with "||" (typed, not copied):

       

       

      {code}

      public class DroolsSpringTransactionManager

          implements

          TransactionManager {

       

          Logger                                     logger             = LoggerFactory.getLogger( getClass() );

          private AbstractPlatformTransactionManager ptm;

       

          TransactionDefinition                      td                 = new DefaultTransactionDefinition();

          TransactionStatus                          currentTransaction = null;

       

          public DroolsSpringTransactionManager(AbstractPlatformTransactionManager ptm) {

              this.ptm = ptm;

          }

       

          public boolean begin() {

              try {

                  if ( getStatus() == TransactionManager.STATUS_NO_TRANSACTION ) {

                      // If there is no transaction then start one, we will commit within the same Command

                      // it seems in spring calling getTransaction is enough to begin a new transaction

                      currentTransaction = this.ptm.getTransaction( td );

                      return true;

                  } else {

                      return false;

                  }

              } catch ( Exception e ) {

                  logger.warn( "Unable to begin transaction",

                               e );

                  throw new RuntimeException( "Unable to begin transaction",

                                              e );

              }

          }

       

          public void commit(boolean transactionOwner) {

              if ( transactionOwner ) {

                  try {

                      // if we didn't begin this transaction, then do nothing

                      this.ptm.commit( currentTransaction );

                      currentTransaction = null;

                  } catch ( Exception e ) {

                      logger.warn( "Unable to commit transaction",

                                   e );

                      throw new RuntimeException( "Unable to commit transaction",

                                                  e );

                  }

              }

          }

       

          public void rollback(boolean transactionOwner) {

              try {

                  if ( transactionOwner ) {

                      this.ptm.rollback( currentTransaction );

                      currentTransaction = null;

                  }

              } catch ( Exception e ) {

                  logger.warn( "Unable to rollback transaction",

                               e );

                  throw new RuntimeException( "Unable to rollback transaction",

                                              e );

              }

          }

       

          /**

           * Borrowed from Seam

           */

          public int getStatus() {

              if ( ptm == null ) {

                  return TransactionManager.STATUS_NO_TRANSACTION;

              }

       

              logger.debug( "Current TX name (According to TransactionSynchronizationManager) : " + TransactionSynchronizationManager.getCurrentTransactionName() );

              if ( TransactionSynchronizationManager.isActualTransactionActive() ) {

                  TransactionStatus transaction = null;

                  try {

                      if ( currentTransaction == null ) {

                          transaction = ptm.getTransaction( td );

                          if ( transaction.isNewTransaction() ) {

                              return TransactionManager.STATUS_COMMITTED;

                          }

                      } else {

                          transaction = currentTransaction;

                      }

                      logger.debug( "Current TX: " + transaction );

                      // If SynchronizationManager thinks it has an active transaction but

                      // our transaction is a new one

                      // then we must be in the middle of committing

                      if ( transaction.isCompleted() ) {

                          if ( transaction.isRollbackOnly() ) {

                              return TransactionManager.STATUS_ROLLEDBACK;

                          }

                          return TransactionManager.STATUS_COMMITTED;

                      } else {

                          // Using the commented-out code in means that if rollback with this manager,

                          //  I always have to catch and check the exception

                          //  because ROLLEDBACK can mean both "rolled back" and "rollback only".

                          // if ( transaction.isRollbackOnly() ) {

                          //     return TransactionManager.STATUS_ROLLEDBACK;

                          // }

       

                          return TransactionManager.STATUS_ACTIVE;

                      }

      ||            } catch (RuntimeException e) {

      ||                if ( e.getCause() instanceof IllegalStateException ) {

      ||                    logger.debug( "IllegalStateException in getStatus()", e );

      ||                    return TransactionManager.STATUS_UNKNOWN;

      ||                } else {

      ||                    logger.debug( "Unexpected Exception in getStatus()", e );

      ||                    throw e;

      ||                }

                  } finally {

                      if ( currentTransaction == null ) {

      ||                    if (transaction != null) {

                            ptm.commit( transaction );

      ||                    }

                      }

                  }

              }

              return TransactionManager.STATUS_NO_TRANSACTION;

          }

       

          public void registerTransactionSynchronization(TransactionSynchronization ts) {

              TransactionSynchronizationManager.registerSynchronization( new SpringTransactionSynchronizationAdapter( ts ) );

          }

      }

      {code}

        • 1. Re: Drools Spring Integration, errors in DroolsSpringTransactionManager
          swiderski.maciej

          would be worth to add this comment into mentioned jira so drools dev team could be aware of it.

          • 2. Re: Drools Spring Integration, errors in DroolsSpringTransactionManager
            jbize

            Maciej, I assumed that the dev team read this.  I added the comment as you sugested, thanks. 

             

            Does anyone have any idea of why I seem to be the only one bitten by this issue?

            • 3. Re: Drools Spring Integration, errors in DroolsSpringTransactionManager
              swiderski.maciej

              Not sure that drools core dev team follows threads on jbpm forum

               

              Do you get those errors even after your modification to the mentioned class? Moreover can you easily reproduce it? with unit test?

              • 4. Re: Drools Spring Integration, errors in DroolsSpringTransactionManager
                jbize

                I guess it's too late to add more tags.  Is there a policy here on cross-posting?  Hopefully, following your suggestion the devs will see this.

                 

                After I added the changes, I still get the IllegalStateExceptions wrapped in RuntimeExceptions, I didn't do anything to change that, but I'm returning the STATUS_UNKNOWN instead of propogating it.  The problem is intermittent, and I can only reproduce it by excercising my process repeatedly.  If there's any good news, it's that with the above changes, my application seems to be working, sucessfulling updating processes, tasks, and application data.  Without the changes, some processes just end prematurely.  I use Spring @Transactional classes in the service and UI layers; perhaps there's a race condition, or perhaps I'm not configuring things correctly. Whatever the cause, I'm not reliably able to reproduce the problem.  It most often happens calling the complete() method of LocalTaskService, but not always; I noticed it in a EmailWorkItemHandler once.

                 

                Since I don't know how it all ties together, and the getStatus() method is public, I really don't know if returning TransactionManager.STATUS_UNKNOWN is correct behavior.  Should it be returning TransactionManager.STATUS_ACTIVE instead?  What about the RuntimeException that isn't wrapping the IllegalStateException, should it be returning some status too?

                 

                Right now I have somethig that seems to work, but that's not a satisfactory way to leave things; my customer wants a robust system.

                 

                Thanks.

                • 5. Re: Drools Spring Integration, errors in DroolsSpringTransactionManager
                  jbize

                  Is there a better forum for this?

                  • 6. Re: Drools Spring Integration, errors in DroolsSpringTransactionManager
                    jbize
                    • 7. Re: Drools Spring Integration, errors in DroolsSpringTransactionManager
                      jbize

                      Well now that I know I'm getting the "IllegalStateException: Transaction already active" and no one seems to know anything about it, I looked further. 

                       

                      I am using 4 different persistence units:

                      • One for reading data from a source unrelated to the BPM component of my application (just data from a different application)
                      • One for the application data components associated with the BPM component of my application (my application data)
                      • One for the BPM Process entities "org.jbpm.persistence.jpa"
                      • One for the BPM LocalTaskServer "org.jbpm.task.???" Task entities "org.jbpm.task.persistence.jpa" (I'm not using MinaTaskServer)

                       

                      I was using the four spring JpaTransactionManager instances, one for each of the corresponding EntityManagerFactories.

                       

                      This was mostly working, but I was running into the above mentioned issues.

                       

                      Well I saw the following comment in the JpaTransactionManager API documentation:

                      This transaction manager is appropriate for applications that use a single JPA EntityManagerFactory for transactional data access. JTA (usually through JtaTransactionManager) is necessary for accessing multiple transactional resources within the same transaction. Note that you need to configure your JPA provider accordingly in order to make it participate in JTA transactions.

                      So, I reconfigured the persistence units and application context to use the JtaTransactionManager, and now when I do something like claimTask(), I get: an "IllegalStateException: A JTA EntityManager cannot use getTransaction()"

                       

                      Can anyone help me now? 

                       

                      Why is org.jbpm.task.service.TaskServiceSession.taskOperation calling getTransaction directly?

                      How can I use Spring transaction management with jBPM?