14 Replies Latest reply on May 5, 2006 6:45 AM by Ronald van Kuijk

    Child tokens do not end at Join ??

    Michael Holtzman Newbie

      I have a workflow that forks into two branches and are later joined.

      <?xml version="1.0" encoding="UTF-8"?>
      <process-definition xmlns:jpdl="http://jbpm.org/3/jpdl"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://jbpm.org/3/jpdl http://jbpm.org/xsd/jpdl-3.1.xsd" name="TestFork">
      <start-state name="Start"><transition name="StartTransition" to="InitializeVariables"/></start-state>
      <node name="InitializeVariables">
      <action class="com.olf.workflowmgr.action.VariableInitActionHandler">
      <VarList>
      <el>Proceed|Boolean|False|</el>
      <el>Flag|CustomList|One|One,Two,Three</el>
      <el>ReturnStatus|PickList||BPM_RETURN_CODE_TABLE</el>
      </VarList></action>
      <transition name="StartProcess" to="Fork"/></node>
      <end-state name="Done"/>
      <fork name="Fork"><transition name="T1_1_Task" to="Task"/><transition name="T1_2_Var" to="Var"/></fork>
      <node name="Var">
      <action class="com.olf.workflowmgr.action.VariableUpdateActionHandler">
      <VarList>
      <el>Flag|Two</el></VarList>
      </action><transition name="T2_1_Join" to="Join"/></node>
      <task-node name="Task">
      <task name="Task_3" blocking="true">
      <assignment class="com.olf.workflowmgr.action.TaskAssignmentHandler">
      <assignee>trader1</assignee>
      </assignment>
      <controller>
      <variable name="Flag" access="read"/>
      <variable name="Proceed" access="read,write,required"/>
      </controller>
      </task>
      <transition name="T3_3_Join" to="Join"/>
      <transition name="Task_Expire_Expired" to="Expired"/>
      <timer name="Task_Expire" duedate="1 minute" transition="Task_Expire_Expired"/>
      </task-node>
      <node name="Expired">
      <action class="com.olf.workflowmgr.action.VariableUpdateActionHandler">
      <VarList>
      <el>Flag|Three</el>
      </VarList>
      </action>
      <transition name="T4_1_Join" to="Join"/>
      </node>
      <join name="Join"><transition name="T5_1_Mail" to="Mail"/>
      </join>
      <node name="Mail"><action class="com.olf.workflowmgr.action.EmailActionHandler">
      <From>trader1</From>
      <To>trader1</To>
      <Subject>Fork completed</Subject>
      <Body>Flag value is $Flag;</Body></action><transition name="T6_1_WWW" to="WWW"/></node>
      <state name="WWW">
      <timer name="WWW_Expire" duedate="1 minute" transition="WWW_Expire_WWW"/>
      <transition name="T7_7_Done" to="Done"/>
      <transition name="WWW_Expire_WWW" to="WWW"/>
      </state>
      </process-definition>
      


      When I look at the process instance in its final state ("WWW"), it appears as if the child tokens from the fork are still active. Is this the correct behavior? I would expect that the join would end the child tokens when the parent is reactivated.

      Please explain. Thanx.


        • 1. Re: Child tokens do not end at Join ??
          Alejandro Guizar Master

          The built-in join node requires a token on all incoming transitions before proceeding. I see you have two transitions from the task node to the join, one for the task and the other for the timer. Since they are mutually exclusive, the join should not proceed.

          However, you state the process instance does reach the final state, so I am somewhat confused. Please tell us what tokens are there (Token.getFullName()) and where (Token.getNode()).

          • 2. Re: Child tokens do not end at Join ??
            Michael Holtzman Newbie

            Ah ha! I restructured the process definition so that the task and timer converge at an intermediate node and then proceed to the join:

            <?xml version="1.0" encoding="UTF-8"?>
            <process-definition xmlns:jpdl="http://jbpm.org/3/jpdl"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://jbpm.org/3/jpdl http://jbpm.org/xsd/jpdl-3.1.xsd" name="TestFork">
            
             <start-state name="Start">
             <transition name="StartTransition" to="InitializeVariables" />
             </start-state>
            
             <node name="InitializeVariables">
             <action class="com.olf.workflowmgr.action.VariableInitActionHandler">
             <VarList>
             <el>Proceed|Boolean|False|</el>
            
             <el>Flag|CustomList|One|One,Two,Three</el>
            
             <el>ReturnStatus|PickList||BPM_RETURN_CODE_TABLE</el>
             </VarList>
             </action>
            
             <transition name="StartProcess" to="Fork" />
             </node>
            
             <end-state name="Done" />
            
             <fork name="Fork">
             <transition name="T1_1_Task" to="Task" />
            
             <transition name="T1_2_Var" to="Var" />
             </fork>
            
             <node name="Var">
             <action class="com.olf.workflowmgr.action.VariableUpdateActionHandler">
             <VarList>
             <el>Flag|Two</el>
             </VarList>
             </action>
            
             <transition name="T2_1_Join" to="Join" />
             </node>
            
             <task-node name="Task">
             <task name="Task_3" blocking="true" duedate="1 minute">
             <assignment class="com.olf.workflowmgr.action.TaskAssignmentHandler">
             <assignee>trader1</assignee>
             </assignment>
            
             <controller>
             <variable name="Flag" access="read" />
            
             <variable name="Proceed" access="read,write,required" />
             </controller>
             </task>
            
             <transition name="T3_3_WWW" to="WWW" />
            
             <transition name="Task_Expire_Expired" to="Expired" />
            
             <timer name="Task_Expire" duedate="1 minute" transition="Task_Expire_Expired" />
             </task-node>
            
             <node name="Expired">
             <action class="com.olf.workflowmgr.action.VariableUpdateActionHandler">
             <VarList>
             <el>Flag|Three</el>
             </VarList>
             </action>
            
             <transition name="T4_1_WWW" to="WWW" />
             </node>
            
             <state name="WWW">
             <timer name="WWW_Expire" duedate="1 minute" transition="WWW_Expire_Join" />
            
             <transition name="T5_5_Join" to="Join" />
            
             <transition name="WWW_Expire_Join" to="Join" />
             </state>
            
             <join name="Join">
             <transition name="T6_1_Mail" to="Mail" />
             </join>
            
             <node name="Mail">
             <action class="com.olf.workflowmgr.action.EmailActionHandler">
             <From>trader1</From>
            
             <To>trader1</To>
            
             <Subject>Fork completed</Subject>
            
             <Body>Flag value is $Flag;</Body>
             </action>
            
             <transition name="T7_1_AlmostDone" to="AlmostDone" />
             </node>
            
             <state name="AlmostDone">
             <transition name="T8_8_Done" to="Done" />
             </state>
            </process-definition>
            


            In my "running BPM viewer" I now see the expected results ... the process hits the fork. A child token goes to Var, and then to Join. The other child token goes to Task, to Expired, to WWW, and finally to Join as each timer expires. The parent token then proceeds to Mail and then AlmostDone. However, the two child tokens remain at the Join node, and are not "ended" (token.hasEnded() returns false).

            Is that the correct behavior? I expected the child tokens to end when they both hit the join node.

            Thanx.


            • 3. Re: Child tokens do not end at Join ??
              Ronald van Kuijk Master

              hmm... my expectation was that a join needs as many incomming tokens as there were in the corresponding fork. (even the same tokens). This would explain the behaviour I see myself in a model where there are two leaving tokens in a fork and 4 incomming transitions of which only 2 will be ever taken.

              Ronald

              • 4. Re: Child tokens do not end at Join ??
                Michael Holtzman Newbie

                 

                my expectation was that a join needs as many incomming tokens as there were in the corresponding fork


                The revised process definition satisifies that requirement, and the transition out of the join is taken. I'm just wondering about the fate of the child tokens after the fork/join segment is traversed.

                • 5. Re: Child tokens do not end at Join ??
                  Ronald van Kuijk Master

                  Michael,

                  With incomming tokens I do not mean incomming transitions.

                  Suppose a fork splits of two tokens T1 and T2. Each of which goes to a descision (T1 to D1 and T2 to D2) which in turn have two transitions to tasks (1A, 1B and 2A, 2B). So either 1A or 1B AND 2A or 2B are taken. Below this is the join. This will result in 4 transitions to the join, but only ever 2 tokens. Perhaps I have to remodel things, but if a join is related to a fork, the number of tokens should count, not the number of transitions.

                  For this reason, I'm always in favour of having a fork/join pair and not orphaned forks or joins ;-)

                  Ronald

                  • 6. Re: Child tokens do not end at Join ??
                    Ronald van Kuijk Master

                    btw, your question about child tokens could be related to http://jira.jboss.org/jira/browse/JBPM-288

                    It is closed due to an incomplete testcase. Maybe your case can help.

                    • 7. Re: Child tokens do not end at Join ??
                      Michael Holtzman Newbie

                      Yes, this looks like the same issue ( http://jira.jboss.org/jira/browse/JBPM-288 ).

                      My contention is that when a join node reactivates the parent token, all child tokens at that join node should be ended. The same is probably true for child tokens that arrive at the join after the parent has been reactivated.

                      From jbpm3.1, join node:

                       public void execute(ExecutionContext executionContext) {
                       Token token = executionContext.getToken();
                      
                       // if this token is not able to reactivate the parent,
                       // we don't need to check anything
                       if ( token.isAbleToReactivateParent() ) {
                      
                       // the token arrived in the join and can only reactivate
                       // the parent once
                       token.setAbleToReactivateParent(false);
                      
                       Token parentToken = token.getParent();
                       if ( parentToken != null ) {
                      
                       boolean reactivateParent = true;
                      
                       // if this is a discriminator
                       if ( isDiscriminator ) {
                       // reactivate the parent when the first token arrives in the
                       // join. this must be the first token arriving because otherwise
                       // the isAbleToReactivateParent() of this token should have been false
                       // above.
                       reactivateParent = true;
                      
                       // if a fixed set of tokenNames is specified at design time...
                       } else if ( tokenNames != null ) {
                       // check reactivation on the basis of those tokenNames
                       reactivateParent = mustParentBeReactivated(parentToken, tokenNames.iterator() );
                      
                       // if a script is specified
                       } else if ( script != null ) {
                      
                       // check if the script returns a collection or a boolean
                       Object result = script.eval( token );
                       // if the result is a collection
                       if ( result instanceof Collection ) {
                       // it must be a collection of tokenNames
                       Collection runtimeTokenNames = (Collection) result;
                       reactivateParent = mustParentBeReactivated(parentToken, runtimeTokenNames.iterator() );
                      
                      
                       // if it's a boolean...
                       } else if ( result instanceof Boolean ) {
                       // the boolean specifies if the parent needs to be reactivated
                       reactivateParent = ((Boolean)result).booleanValue();
                       }
                      
                       // if a nOutOfM is specified
                       } else if ( nOutOfM != -1 ) {
                      
                       int n = 0;
                       // wheck how many tokens already arrived in the join
                       Iterator iter = parentToken.getChildren().values().iterator();
                       while ( iter.hasNext() ) {
                       Token concurrentToken = (Token)iter.next();
                       if (this.equals(concurrentToken.getNode())) {
                       n++;
                       }
                       }
                       if ( n < nOutOfM ) {
                       reactivateParent = false;
                       }
                      
                       // if no configuration is specified..
                       } else {
                       // the default behaviour is to check all concurrent tokens and reactivate
                       // the parent if the last token arrives in the join
                       reactivateParent = mustParentBeReactivated(parentToken, parentToken.getChildren().keySet().iterator() );
                       }
                      
                       // if the parent token needs to be reactivated from this join node
                       if (reactivateParent) {
                      
                       // write to all child tokens that the parent is already reactivated
                       Iterator iter = parentToken.getChildren().values().iterator();
                       while ( iter.hasNext() ) {
                       // ((Token)iter.next()).setAbleToReactivateParent( false );
                       Token tok = ((Token)iter.next());
                       tok.setAbleToReactivateParent( false );
                       if (this.equals(tok.getNode()) tok.end();
                       }
                      
                       // write to all child tokens that the parent is already reactivated
                       ExecutionContext parentContext = new ExecutionContext(parentToken);
                       leave(parentContext);
                       }
                       }
                       }
                       }
                      



                      • 8. Re: Child tokens do not end at Join ??
                        Ronald van Kuijk Master

                        Michael,

                        Could you create a unit test and attach both the processdefinition and the unit test to the bug report? I reopend it and refered it to this topic

                        Thanks,

                        Ronald

                        • 9. Re: Child tokens do not end at Join ??
                          Michael Holtzman Newbie

                          Also posted to JIRA http://jira.jboss.org/jira/browse/JBPM-288


                          <?xml version="1.0" encoding="UTF-8"?>
                          <process-definition
                           xmlns="urn:jbpm.org:jpdl-3.1"
                           name="simple">
                           <start-state name="start">
                           <transition name="" to="fork1"></transition>
                           </start-state>
                           <end-state name="end"></end-state>
                           <fork name="fork1">
                           <transition name="fk1" to="node1"></transition>
                           <transition name="fk2" to="node2"></transition>
                           </fork>
                           <node name="node1">
                           <transition name="jn1" to="join1"></transition>
                           </node>
                           <node name="node2">
                           <transition name="jn2" to="join1"></transition>
                           </node>
                           <state name="state1">
                           <transition name="" to="end"></transition>
                           </state>
                           <join name="join1">
                           <transition name="" to="state1"></transition>
                           </join>
                          </process-definition>
                          


                          package com.sample;
                          
                          import junit.framework.TestCase;
                          import java.util.*;
                          
                          import org.jbpm.graph.def.*;
                          import org.jbpm.graph.exe.*;
                          
                          public class SimpleProcessTest extends TestCase {
                          
                           public void testSimpleProcess() throws Exception {
                          
                           // Extract a process definition from the processdefinition.xml file.
                           ProcessDefinition definition =
                           ProcessDefinition.parseXmlResource("simple.par/processdefinition.xml");
                           assertNotNull("Definition should not be null", definition);
                          
                           // Create an instance of the process definition.
                           ProcessInstance instance = new ProcessInstance(definition);
                           assertEquals(
                           "Instance is in start state",
                           instance.getRootToken().getNode().getName(),
                           "start");
                          
                           // Move the process instance from its start state to the first state.
                           instance.signal();
                           assertEquals(
                           "Instance is in wait state",
                           instance.getRootToken().getNode().getName(),
                           "state1");
                          
                           Map children = instance.getRootToken().getChildren();
                           Collection kids = children.values();
                           for (Iterator it = kids.iterator(); it.hasNext();) {
                           Token tok = (Token)it.next();
                           assertTrue("Fork child token " + tok.getFullName() + " has ended", tok.hasEnded());
                           }
                           // Move the process instance to the end state.
                           instance.signal();
                           assertEquals(
                           "Instance is in end state",
                           instance.getRootToken().getNode().getName(),
                           "end");
                           assertTrue("Instance has ended", instance.hasEnded());
                           }
                          
                          }
                          




                          • 11. Re: Child tokens do not end at Join ??
                            Andy Morris Newbie

                            I too have been experiencing issues with this particular bug and unsure how to proceed.

                            I have a workflow which has an initial wait state which then traverses to a fork which then concludes in a join, followed by a transition back to the initial wait state.

                            My workflow never terminates as such.

                            The issue I am seeing is on the persistence side. Given that my workflow never terminates and that I need there to be only one running instance, I use a mechanism which replaces the running workflow with a newer version when one comes along.

                            During JBoss startup, a check is made for a newer PAR version and that is then deployed as necessary.

                            In order to keep my dbase tidy, I call the following methods in order to clear up the old instance:

                            - cancelTimersForProcessInstance
                            - deleteProcessInstance

                            The problem I am now seeing is that once I have > 2000 child tokens - the deleteProcessInstance method is throwing an exception "Prepared or callable statement has more than 2000 parameter markers."

                            Essentially this is thrown because of the following operation in deleteProcessInstance:

                             private static final String selectLogsForTokens =
                             "select pl " +
                             "from org.jbpm.logging.log.ProcessLog as pl " +
                             "where pl.token in (:tokens)";
                            


                            The IN clause contains > 2000 tokens!

                            I also note that none of these old tokens ever get deleted!!!!?

                            Any thoughts on what the best strategy would be to fix this?

                            Many thanks,

                            Andy

                            • 12. Re: Child tokens do not end at Join ??
                              Andy Morris Newbie

                              Following up my own post.
                              I decided to implement michaelholtzman's fix to the Join node.

                              Essentially:

                               if (this.equals(tok.getNode()) tok.end(false);
                              


                              However, I chose to pass in false to the end method, such that the parent token and associated process instance doesn't also get ended.

                              Shouldn't this fix be in core JBPM anyway? It 's omission contradicts the documentation which states:

                              A join will end every token that enters the join.


                              As the 3.1 and below code currently stands, this is a lie.

                              To overcome my problem with deleteProcessInstance and it's issue with > 2000 tokens, I've also implemented an ActionHandler on the node-leave event of my join node, which now removes these old (and now ENDED) child tokens.

                              Andy

                              • 13. Re: Child tokens do not end at Join ??
                                James Depoorter Novice

                                I also think you should do tok.end(false) and not tok.end() (which seems dangerous). Will it be fixed? I'm regularly patching/extending the jBpm core.

                                • 14. Re: Child tokens do not end at Join ??
                                  Ronald van Kuijk Master

                                  Why not try to get commit access (you don't have that, right), so you can help fixing them. Try to attach patches to the jira issues and notify Tom. We'd appreciate that very much.