[jboss-user] [jBPM Users] - Re: nested forks

saraswati.santanu do-not-reply at jboss.com
Wed Nov 18 09:17:18 EST 2009


Hi Sebastian,
   You found a bug there. It took sometime to understand why is it behaving that way. I think the problem is there in ForkActivity class. Actually the following things happen in sequence transition from one Fork node to any other node.

   1. Create child execution for each transition
   2. Mark state of each child execution as STATE_ACTIVE_CONCURRENT
   3. take the transition
next few steps are regular transition steps:
   4. Perform transition end activity (for the source node)
   5. Perform transition take activity
   6. Execute transition start event listener on destination
   7. Perform transition start activity (for destination)
   8. Execute the activity behaviour of the destination

   Now for your case, at the last step it will execute ForkActivity again. Here is the fork activity execute method. Please notice the line in bold and the if block surrounding:


  |   public void execute(ExecutionImpl execution) {
  |     Activity activity = execution.getActivity();
  | 
  |     // evaluate the conditions and find the transitions that should be forked
  |     List<Transition> forkingTransitions = new ArrayList<Transition>();
  |     List<Transition> outgoingTransitions = activity.getOutgoingTransitions();
  |     for (Transition transition: outgoingTransitions) {
  |       Condition condition = transition.getCondition();
  |       if  ( (condition==null)
  |             || (condition.evaluate(execution))
  |           ) {
  |         forkingTransitions.add(transition);
  |       }
  |     }
  | 
  |     // if no outgoing transitions should be forked, 
  |     if (forkingTransitions.size()==0) {
  |       // end this execution
  |       execution.end();
  |       
  |     // if there is exactly 1 transition to be taken, just use the incoming execution
  |     } else if (forkingTransitions.size()==1) {
  |       execution.take(forkingTransitions.get(0));
  |       
  |     // if there are more transitions
  |     } else {
  |       ExecutionImpl concurrentRoot = null;
  |      if (Execution.STATE_ACTIVE_ROOT.equals(execution.getState())) {
  |         concurrentRoot = execution;
  |         execution.setState(Execution.STATE_INACTIVE_CONCURRENT_ROOT);
  |         execution.setActivity(null);
  |       } else if (Exec ution.STATE_ACTIVE_CONCURRENT.equals(execution.getState())) {
  |         concurrentRoot = execution.getParent();
  |       }
  | 
  |       for (Transition transition: forkingTransitions) {
  |         // launch a concurrent path of execution
  |         String childExecutionName = transition.getName();
  |         ExecutionImpl concurrentExecution = concurrentRoot.createExecution(childExecutionName);
  |         concurrentExecution.setActivity(activity);
  |         concurrentExecution.setState(Execution.STATE_ACTIVE_CONCURRENT);
  |         concurrentExecution.take(transition);
  |         
  |         if (concurrentRoot.isEnded()) {
  |           break;
  |         }
  |       }
  |     }
  |   }
  | 

For a fork, the state of any child execution is Execution.STATE_ACTIVE_CONCURRENT. So for our nested fork state will be STATE_ACTIVE_CONCURRENT and it will satisfy the else if condition:


  |      if (Execution.STATE_ACTIVE_ROOT.equals(execution.getState())) {
  |         concurrentRoot = execution;
  |         execution.setState(Execution.STATE_INACTIVE_CONCURRENT_ROOT);
  |         execution.setActivity(null);
  |       } else if (Exec ution.STATE_ACTIVE_CONCURRENT.equals(execution.getState())) {
  |         concurrentRoot = execution.getParent();
  |       }
  | 

So the root of the children of the nested fork becomes the first level fork! Now the codes goes into the next loop:


  |       for (Transition transition: forkingTransitions) {
  |         // launch a concurrent path of execution
  |         String childExecutionName = transition.getName();
  |         ExecutionImpl concurrentExecution = concurrentRoot.createExecution(childExecutionName);
  |         concurrentExecution.setActivity(activity);
  |         concurrentExecution.setState(Execution.STATE_ACTIVE_CONCURRENT);
  |         concurrentExecution.take(transition);
  |         
  |         if (concurrentRoot.isEnded()) {
  |           break;
  |         }
  |       }
  | 

Here ExecutionImpl concurrentExecution = concurrentRoot.createExecution(childExecutionName); line creates a child execution of actually the grand parent execution. In the same createExecution method of ExecutionImpl it also sets the propagation to explicit to avoid automatic transition.


  | public ExecutionImpl createExecution(String name) {
  |     // when an activity calls createExecution, propagation is explicit.
  |     // this means that the default propagation (proceed()) will not be called 
  |     propagation = Propagation.EXPLICIT;
  |     ...
  |     ...
  | }
  | 

Now the stuation where we land up is - the grand parent execution of the first fork (fork1) is set to propagation EXPLICIT, but it was already EXPLICIT because the grand parent has its own children and while taking transition to the children it became EXPLICIT. The execution of second lavel fork (fork2) has never been used to create a child execution. So its propagation status remains UNSPECIFIED.

All executions with unspecified propagation status proceeds automatically. So does our fork2. So fork2 takes its default transition again. Which for your case is to Task1. So it goes ahead and executes TaskActivity for Task1 again. Thus we get two Task1 created at the end.

Now if we change the line in bold there to this:

  |      if (Execution.STATE_ACTIVE_ROOT.equals(execution.getState())) {
  |         concurrentRoot = execution;
  |         execution.setState(Execution.STATE_INACTIVE_CONCURRENT_ROOT);
  |         execution.setActivity(null);
  |       } else if (Exec ution.STATE_ACTIVE_CONCURRENT.equals(execution.getState())) {
  |         //concurrentRoot = execution.getParent();
  |         concurrentRoot = execution;
  |       }
  | 

Basically we make the current root always the current execution and create children of that. This will fix your problem.

Now I have no idea why for nested forks the parent of the inner fork is used to create children of the inner fork is not very clear to me. I would expect there must be some usecase which require this.

So the only thing we can do now is to report a bug. You also have the nice flow and testcase ready.

Well, the post became quite big and complex. Hope this is not very confusing!

View the original post : http://www.jboss.org/index.html?module=bb&op=viewtopic&p=4266301#4266301

Reply to the post : http://www.jboss.org/index.html?module=bb&op=posting&mode=reply&p=4266301



More information about the jboss-user mailing list