6 Replies Latest reply on Apr 19, 2007 4:21 AM by tom.baeyens

    Concurrent task acquisition (how?)

    johan.parent

      I know the topic has been discussed frequently in this forum but I can't find the answer I'm looking for.

      My question is: How to handle the concurrent access to a task assigned to an actor pool?

      findPoolTaskInstance(actorId) will return all the tasks assigned to any pool the actor is a member of. But as long as you don't use setActorId() the task is not assigned. Concurrent access to the same task instance (taken from the list return by findPoolTaskInstance) will result in an exception when the data gets persisted in the DB for processes which did not set their actorId succesfully.

       protected boolean acquire() {
       // Now acquire the very first task
       createSession();
      
       try {
       jbpmContext.setActorId(actorName);
      
       // Now we search for all the tasks waiting for our actor
       List tasksActor = taskMgmtSession.findPooledTaskInstances(actorName);
      
       if (tasksActor.size() > 0) {
       TaskInstance task = (TaskInstance) tasksActor.get(0);
       task.setActorId(actorName);
      
       jbpmContext.save(task);
       }
       }
       catch(Exception e) {
       e.printStackTrace();
       }
       finally {
       // Tear down the pojo persistence context.
       try {
       closeSession();
       }
       catch(Exception e) {
       // If the Task is already taken you'll end up here
       e.printStackTrace();
      
       return false;
       }
       }
      
       return true;
       }
      


      I'm not considering multi-threaded scenario, using synchronize would be a solution then. Catching the exception, like illustrated above, does not look like an elegant solution to me. Is there a better way?

      Best regards,

      Johan

        • 1. Re: Concurrent task acquisition (how?)
          johan.parent

          Me again...

          Hmmm, is my question so stupid? If so, I gladly settle for a "look at thread nr XXX ..." type of reply :) Just shoot

          How are other people handling this then? With carefully crafted assignment handlers?

          As you can see I'm desperately in need of some jBPM wisdom here ;)

          Best regards,

          Johan

          • 2. Re: Concurrent task acquisition (how?)
            aguizar

            There are two approaches for concurrent task assignment in particular and database access in general: pessimistic locking and optimistic control.

            In the first approach, you lock the task instance (jbpmContext.getSession().lock(taskInstance, LockMode.UPGRADE) before you update it. The first transaction that attempts this will get the lock and proceed. Other transactions will have to wait until the first transaction releases the lock. This approach has the following drawbacks:

            While a transaction waits for the lock to be released, the thread executing it will be stalled and your application will appear unresponsive to its clients
            After acquiring the lock, you have to check whether the taskInstance has already been assigned
            Locking is supported differently between databases. Some do not support specific lock modes and cause your app to behave in unexpected ways
            Optimistic control does not rely on database-provided mechanisms but in checks made by the application. When a conflict is detected, one of those StaleStateExceptions is thrown.

            There is no reason to consider this an unaesthetic programming style, because it is a well-known practice. What you gain here, full application responsiveness and predictable behavior across databases, outweighs the inconvenience of catching the exception.

            By the way, you shouldn't catch all Exceptions, but only JbpmPersistenceExceptions.

            • 3. Re: Concurrent task acquisition (how?)
              johan.parent

              Thank you very very much for this very clear answer! Much appreciated.

              Johan

              • 4. Re: Concurrent task acquisition (how?)
                johan.parent

                In the meanwhile I've implemented this approach. And it solves the problem.

                But I can't catch the exception very early on. So my process produces ugly stack traces upon unsuccesful commits.

                I'm not sure whether replace the DbPersistenceService(Factory) (through the service definition in the jbpm.cfg.xml) would be adviseable. I risk bring a bunch of other things. Is there a better way?

                Best regards,

                Johan

                [oe2] AbstractFlushingEventListener : Could not synchronize database state with session
                org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [org.jbpm.graph.exe.Token#7110]
                 at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:1635)
                 at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2208)
                 at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2118)
                 at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2374)
                 at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:84)
                 at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:243)
                 at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:227)
                 at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:141)
                 at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:296)
                 at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
                 at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:980)
                 at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:353)
                 at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
                 at org.jbpm.persistence.db.DbPersistenceService.close(DbPersistenceService.java:162)
                 at org.jbpm.svc.Services.close(Services.java:211)
                 at org.jbpm.JbpmContext.close(JbpmContext.java:139)
                 at JBpmTest.JBpmOeTest.closeContext(JBpmOeTest.java:331)
                


                • 5. Re: Concurrent task acquisition (how?)
                  johan.parent

                  I found the '"why" of the stack trace mentioned in the previous post. A lost e.printStackTrace in the close() methode in org.jbpm.svc.Service at line 213 (for jbpm-3.1.4).

                  Johan

                  ps: does this need an entry in JIRA?

                  • 6. Re: Concurrent task acquisition (how?)
                    tom.baeyens

                    do a flush before you close the JbpmContext