Bug 975005

Summary: Incorrect execution of fork/join after async node
Product: [JBoss] JBoss Enterprise SOA Platform 5 Reporter: Martin Weiler <mweiler>
Component: JBPM - standalone, JBPM - within SOAAssignee: Nobody <nobody>
Status: VERIFIED --- QA Contact:
Severity: high Docs Contact:
Priority: high    
Version: 5.3.1CC: nwallace, rwagner, soa-p-jira, yann.perey
Target Milestone: GA   
Target Release: ---   
Hardware: Unspecified   
OS: Unspecified   
Whiteboard:
Fixed In Version: Doc Type: Bug Fix
Doc Text:
Story Points: ---
Clone Of: Environment:
Last Closed: Type: Bug
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Embargoed:
Bug Depends On:    
Bug Blocks: 986007    

Description Martin Weiler 2013-06-17 11:10:56 UTC
Description of problem:
Platform issue for JBPM-4030

Comment 1 JBoss JIRA Server 2013-06-17 15:20:49 UTC
Martin Weiler <mweiler> made a comment on jira JBPM-4030

Attaching a test process to reproduce the issue. Note that the end state is reached twice, and that inside the fork/join the parent/child token relationship is empty.

Comment 2 JBoss JIRA Server 2013-06-18 14:24:39 UTC
Martin Weiler <mweiler> made a comment on jira JBPM-4030

Proposal to fix this issue:

{code}
diff --git a/core/src/main/java/org/jbpm/graph/node/Join.java b/core/src/main/java/org/jbpm/graph/node/Join.java
index bcff33a..9260e89 100644
--- a/core/src/main/java/org/jbpm/graph/node/Join.java
+++ b/core/src/main/java/org/jbpm/graph/node/Join.java
@@ -129,6 +129,10 @@ public class Join extends Node {
     }
 
     Token parentToken = arrivingToken.getParent();
+    // JBPM-4030 refresh parent token if necessary
+    if(parentToken.getChildren().size()==0) {    
+        executionContext.getJbpmContext().getSession().refresh(parentToken);
+    }
     boolean reactivateParent;
     // if this is a discriminator
     if (isDiscriminator) {
@@ -173,7 +177,10 @@ public class Join extends Node {
         childToken.setAbleToReactivateParent(false);
       }
       // unlock parent token
-      parentToken.unlock(parentToken.getNode().toString());
+      // JBPM-4030 check if parentToken can be unlocked
+      if(parentToken.isLocked() && parentToken.getLockOwner().equals(parentToken.getNode().toString())) {
+          parentToken.unlock(parentToken.getNode().toString());
+      }
       // leave the join node
       leave(new ExecutionContext(parentToken));
     }
{code}

Comment 3 JBoss JIRA Server 2013-06-24 19:47:26 UTC
Marco Rietveld <marco.rietveld> made a comment on jira JBPM-4030

Martin, this is fantastic work! Thanks very much.

Comment 4 JBoss JIRA Server 2013-06-25 09:35:01 UTC
Marco Rietveld <marco.rietveld> made a comment on jira JBPM-4030

This is a note to myself for future reference: 

One of the main causes of this problem is the following code in the Fork class: 

{code:java}
    // phase two: create child token for each selected transition
    Map childTokens = new HashMap();
    for (Iterator iter = transitionNames.iterator(); iter.hasNext();) {
      String transitionName = (String) iter.next();
      Token childToken = createForkedToken(token, transitionName);
      childTokens.put(transitionName, childToken);
    }
{code}

Specifically, the {code:java}Map childTokens = new HashMap();{code} line is a lot more dangerous than it looks. 

*Why?* Because jBPM 3 uses the Hibernate 3 2nd level cache, which means that many of the objects referred to in the code, are actually hibernate-proxies of the actual objects. You'll notice that the method {{createForkedToken()}} is called, and your suspicion that creates the parent-child token relationship will be correct -- but only partially. 

*Here's what happens:* {{createdForkedToken()}} eventually calls the {{Token(Token parent, String name)}} constructor. And that constructor contains the following code: 
{code:java}
    this.parent = parent;
    parent.addChild(this);
{code}

Great! *NO..* Why? Because the parent object referred to here is the _actual_ object, not the hibernate-proxied Token object. That means that when we return to the original {{execute(...)}} in the {{Fork}} class, the hibernate-proxied (parent) Token object will still have *no* children! Blech.. 

And we return to why {{Map childTokens = new HashMap();}} is so dangerous. It should simply be this: {code:java}Map childTokens = token.getChildren();{code}. That way, we add the children to the hibernate-proxied Token object map. If you've been paying attention, you'll notice that we're now adding this (parent-child) relationship twice: once in the {{Fork.execute(...)}} method (with the above line), and once in the {{createForkedToken(...)}} method. This is okay for 2 reasons: 

# We're adding the same relationship: in both cases, the key is the transition name, and the value is the (same) child token reference.
# Technically, we're adding this relationship to 2 different maps: the first map is from the hibernate-proxied parent Token, and the second map is from the (real) parent Token object.

Comment 5 JBoss JIRA Server 2013-06-25 14:53:29 UTC
Marco Rietveld <marco.rietveld> made a comment on jira JBPM-4030

Wow, there's an even simpler solution: simply initalize the {{Token.children}} (Map) field with a {{new HashMap()}} value so that the hibernate-proxied object also contains that field. LOL.

Comment 6 JBoss JIRA Server 2013-06-28 09:14:53 UTC
Marco Rietveld <marco.rietveld> updated the status of jira JBPM-4030 to Resolved