8 Replies Latest reply on Oct 4, 2006 7:44 AM by George Mournos

    Possible bug in fork-joins

    George Mournos Newbie

      I have a process definition where there is a join of 3 input tokens. It seems that the join consumes only the two of the tokens and gets passed. Is this a bug ?

      The code is the following:

      public static ProcessDefinition createForkJoinProcessDefinition() {
       ProcessDefinition pd = new ProcessDefinition(
       new String[]{"start-state start",
       "fork f1",
       "state s1",
       "fork f2",
       "state s2",
       "join j1",
       "state s3",
       "end-state end"},
       new String[]{"start --> f1", "f1 --to_s1--> s1", "f1 --to_j1_1--> j1",
       "s1 --> f2", "f2 --to_j1_2--> j1", "f2 --to_s2--> s2",
       "s2 --to_j1_3--> j1", "j1 --> s3", "s3 --> end"});
       return pd;
       }
      
       public void testComplex2() {
       ProcessDefinition pd = createForkJoinProcessDefinition();
       ProcessInstance pi = pd.createProcessInstance();
       pi.signal();
       Token root = pi.getRootToken();
       Token to_s1 = root.getChild("to_s1");
       Token to_j1_1 = root.getChild("to_j1_1");
       assertNotNull(to_s1);
       assertNotNull(to_j1_1);
       to_s1.signal();
       Token to_s2 = to_s1.getChild("to_s2");
       Token to_j1_2 = to_s1.getChild("to_j1_2");
       assertNotNull(to_s2);
       assertNotNull(to_j1_2);
       to_s2.signal();
      
      
       try {
       assertSame(root.getNode(), pd.getNode("s3"));
       } catch (junit.framework.AssertionFailedError e) {
       System.out.println("this is failing but should be succeding");
       }
      
       try {
       assertNotSame(to_s1.getNode(), pd.getNode("s3"));
       } catch (junit.framework.AssertionFailedError e) {
       System.out.println("This is wrong, j1 has consumed only " +
       "tokens to_s2 and to_j12_2. Token to_j1_1 remained unconsumed");
       }
      
       try {
       to_s1.signal();
       assertTrue(pi.hasEnded());
       } catch (junit.framework.AssertionFailedError e) {
       System.out.println("This is wrong, we should have finished by now");
       }
      
       to_j1_1.signal();
       to_j1_1.signal();
       assertTrue(pi.hasEnded());
       System.out.println("This is wrong, token to_j1_1 should have already been consumed by j1");
       }



        • 1. Re: Possible bug in fork-joins
          Jitendra Singh Newbie

          Hi,

          The fork and join work in tandem, the join expects exact number of incoming transitions as the fork sent out.

          In the code above you have fork 1 giving out 2 and fork 2 splitting one into 2 and all three then meeting into the single join.

          To get this to work it will need another join to be added after fork2 and the output from it will go into join1.

          something like:


          public static ProcessDefinition createForkJoinProcessDefinition() {
           ProcessDefinition pd = new ProcessDefinition(
           new String[]{"start-state start",
           "fork f1",
           "state s1",
           "fork f2",
           "state s2",
           "join j1",
           "join j2",
           "state s3",
           "end-state end"},
           new String[]{"start --> f1", "f1 --to_s1--> s1", "f1 --to_j1_1--> j1",
           "s1 --> f2", "f2 --to_j2_1--> j2", "f2 --to_s2--> s2",
           "s2 --to_j2_2--> j2","j2 --to_j1_2-->j1", "j1 --> s3", "s3 --> end"});
           return pd;
           }
          


          • 2. Re: Possible bug in fork-joins
            George Mournos Newbie

            Thx for the fast reply.

            Well, this coupling of forks and joins puts me off a bit.
            I would rather that jbpm would be smart enough to figure out that the join is "joining" both parent forks, instead of introducing structural constraints that cannot be automatically validated at design time.

            Is there an AUTOMATIC way to decouple joins from forks by setting any of the flags (e.g. discriminator and NoutofM)? Or an AUTOMATIC way to achieve behavior similar to introducing a second join.

            • 3. Re: Possible bug in fork-joins
              Jitendra Singh Newbie

              I do not think its possible to decouple them completely. But will get some ideas on partial de-coupling, by reading the execute method of the Join.

              It comes with some options like activating/executing the join on:
              1) first token arrival, i.e. discriminator
              2) n count token arrival, i.e. nOutOfM
              3) it allows for a script to be placed in the join tag of process definition xml.
              4) can specify exact names of the tokens to expect for activation.
              5) simple plain vanilla complete coupling.

              thats all for AUTOMATIC decoupling.. or you can write a spl join that will check for all active child tokens to be arriving for a join to be executed/activated.

              hope it helps...

              • 4. Re: Possible bug in fork-joins
                Jeremy Warren Newbie

                I recently battled this very issue. Attached is the code for a custom join node that seems to work in all scenarios. (I edited out some of our specific code so as posted may not exactly work but you should get the gist.)

                
                public class join implements ActionHandler
                {
                 private static final long serialVersionUID = 1L;
                 private static final Log log = LogFactory.getLog(Join.class);
                
                 public void execute(ExecutionContext executionContext)
                 {
                 Node ThisNode = executionContext.getNode();
                 int tokenCount = 0;
                 try
                 {
                 tokenCount = (Integer) executionContext.getVariable(ThisNode.getName() + "tokenCount");
                 }
                 catch (Exception e)
                 {
                 log.debug(e);
                 }
                 Token token = executionContext.getToken();
                 if(token.isAbleToReactivateParent())
                 {
                 log.debug("Set token: " + token.getFullName()+ " now unable to reactivate parent.");
                 token.setAbleToReactivateParent(false);
                 }
                 if(!token.hasEnded())
                 {
                 token.end();
                 log.debug("Set token:" + token.getFullName()+ " now set to ended.");
                 }
                 //Sanity Check
                 log.debug("Inbound token ended status=" + token.hasEnded());
                 tokenCount++;
                
                 int transitionCount = ThisNode.getArrivingTransitions().size();
                
                 // If we have all of our tokens, then we can proceed.
                 //But we need to see if our parent has all it needs done or not.
                 // Decide if the parent fork is ready to go or not.
                
                 ExecutionContext exitContext = CalculateCurrentExitContext(token);
                 if(tokenCount >= transitionCount)
                 {
                 log.debug("All tokens are here..keep going...");
                 ThisNode.leave(exitContext);
                 }
                 else
                 {
                 log.debug("We received: " + token.getFullName()+ " and it is able to reactivate parent = " + token.isAbleToReactivateParent());
                 log.debug("Received: " + tokenCount + " tokens out of: " + transitionCount + " paths. Waiting for remaining");
                 }
                 executionContext.setVariable(ThisNode.getName() + "tokenCount", tokenCount);
                 executionContext.getJbpmContext().save(executionContext.getProcessInstance());
                 }
                
                 // Returns the current Execution Context to leave the current node with
                 public ExecutionContext CalculateCurrentExitContext(Token Token)
                 {
                 // We need to work our way up the tree to find the lowest fork that all
                 // paths are complete and use this to set our context.
                
                 ExecutionContext exitContext = null;
                 Token parentToken = Token.getParent();
                 ExecutionContext currentContext = new ExecutionContext(Token);
                 // See if we have a parent token
                 if ( parentToken != null )
                 {
                 log.debug("Parent Token = "+parentToken.toString());
                 // See if that parent has any active children
                 if (!parentToken.hasActiveChildren())
                 {
                 // NO:
                 // Recursively call ourselves to see if the parent of
                 // the parent has active children
                 log.debug("No Active Children - Making Recursive Call");
                 exitContext = CalculateCurrentExitContext(parentToken);
                 }else{
                 // YES:
                 // Our parent has active children then
                 // our current context is this token, not the parent
                 log.debug("Active Children: "+parentToken.getActiveChildren().toString());
                 log.debug("Children - Setting to currentContext");
                 exitContext = currentContext;
                 }
                 }else{
                 // If parentToken = null then we have no parent
                 exitContext = currentContext;
                 }
                 log.debug("Calculated Highest Possible execution context:" + exitContext.toString());
                 return exitContext;
                 }
                }
                


                • 5. Re: Possible bug in fork-joins
                  George Mournos Newbie

                  Hi Jerwah,

                  Good work. I cannot understand what the class is doing cos I am new to this project, but it is passing my testcase and joining all parents, and this is great.

                  One comment, the line:

                  executionContext.getJbpmContext().save(executionContext.getProcessInstance());

                  was throwing a null pointer exception in my test cases, so I commented it out.
                  In the test cases I am not using persistency, just testing the navigation.
                  But shouldn't the persistency be out of the code for the join?



                  • 6. Re: Possible bug in fork-joins
                    Jeremy Warren Newbie

                    Glad I could help. We too are very new to bpm.

                    As for the persistence, in our situation we have a very slow process (Months) which is externally moved forward via a custom webapp, so obviously we need to persist the token counter. It seemed most logical to us to persist it in the join at update time to ensure we always had the accurate count but if you are persisting the process elsewhere, I would suspect it would work fine too.

                    • 7. Re: Possible bug in fork-joins
                      pedro costa Apprentice

                      I've saw the code of Jerwah, but i don't understand one thing. How we can call join class. Is it defined in the process definition?

                      Can you give me an example?

                      Thanks,
                      Pedro

                      • 8. Re: Possible bug in fork-joins
                        George Mournos Newbie

                        Hi Pedro,
                        The easiest way would be to override the corresponding class from jbpm...

                        I am not using this code. I switched back to the default jpbm implementations