Stackless Implementation Notes - mrietveld/jbpm GitHub Wiki

WikiStackless 1-PagerStackless Implementaiton Notes
2014-05-09

Notes on the stackless implementation

In RunProcessInstance, we have the following:

private final Stack<Deque<ProcessActionTrigger>> processActionQueueStack = new Stack<Deque<ProcessActionTrigger>>();

This field represents a stack of execution queues.

An execution queue represents one of 3 things

  • The queue used for the ("normal") start of a process instance
  • The queue added and used when an event is signalled
  • The queue added and used when we enter an exception scope

The main reason for having a stack of queues (instead of just one queue) is that there are certain actions that always need to be taken at the end of an event being signalled, or after an exception scope has been handled.

If we try to solve these situations (signal-events, exception scopes) with just one queue, the logic to keep track of when the "end" or "clean-up" action should be taken becomes complex and ugly.

An easier way to understand this is the following: implementation vs execution.

  1. With the "old" recursive execution model: a lot of the implementation logic (such as, how to clean up after an fault node triggered an exception scope) was part of the execution logic.

For example:

  1. The FaultNodeInstance triggers,
  2. .. which triggers the exception scope
  3. The exception scope does it thing
  4. and when the "stack" has returned, the FaultNodeInstance then calls the clean up code.

In this instance, because 2, 3, and 4, happen in sequence, and because the we have an execution model in which 3 does all sorts of things (instead of, for example, returning asynchronously), everything works.

However, what's "built-in" here is that 3 must always happen before 4. In other words, the exception scope must always have done it's things before 4 happens.

But that's a problem: what if we have a save-point, such as a human task? Well, that's our bug and to some extent one of the 3 big challenges with Compensation!

so we want to do this:

  1. The FaultNodeInstance triggers,
  2. .. which triggers the exception scope

Questions / Challenges

  • Mario is developing logic so that some PropgationEntry actions can be excuted in parallel.
    • We need to make sure that this does not happen when we have a timer (or async) job that fires a RunProcessInstance action.

Stack trace

At the moment, an example of the current stack is this:

RuleFlowProcessInstance.internalStart(String) line: 35

* > NodeInstanceImpl.trigger(NodeInstance, String) line: 155
> StartNodeInstance.internalTrigger(NodeInstance, String) line: 43
> StartNodeInstance.triggerCompleted() line: 66
> StartNodeInstance(NodeInstanceImpl).triggerCompleted(String, boolean) line: 296
* > StartNodeInstance(NodeInstanceImpl).triggerNodeInstance(NodeInstance, String) line: 337

* > SplitInstance(NodeInstanceImpl).trigger(NodeInstance, String) line: 155
> SplitInstance.internalTrigger(NodeInstance, String) line: 63
> SplitInstance.executeStrategy(Split, String) line: 117
> SplitInstance(NodeInstanceImpl).triggerConnection(Connection) line: 352
* > SplitInstance(NodeInstanceImpl).triggerNodeInstance(NodeInstance, String) line: 327

* > ActionNodeInstance(NodeInstanceImpl.trigger(NodeInstance, String) line: 155
> ActionNodeInstance.internalTrigger(NodeInstance, String) line: 57
> ActionNodeInstance.triggerCompleted() line: 61
> ActionNodeInstance(NodeInstanceImpl).triggerCompleted(String, boolean) line: 296
* > ActionNodeInstance(NodeInstanceImpl).triggerNodeInstance(NodeInstance, String) line: 337

It looks like the best place to "snip" the stack is between

`NodeInstanceImpl.triggerNodeInstance(NodeInstance, String)`

and

`NodeInstanceImpl.trigger(NodeInstance, String)` methods. 

Stack trace

The NodeInstanceImpl.triggerNodeInstance(NodeInstance, String) method then returns the information (next NodeInstance, connection type String) that is then used in the next 'loop' to trigger that node instance.

The first "loop" of the process becomes this:

RuleFlowProcessInstance.internalStart(String) line: 35
> NodeInstanceImpl.trigger(NodeInstance, String) line: 155
  > StartNodeInstance.internalTrigger(NodeInstance, String) line: 43
    > StartNodeInstance.triggerCompleted() line: 66
      > StartNodeInstance(NodeInstanceImpl).triggerCompleted(String, boolean) line: 296
        > StartNodeInstance(NodeInstanceImpl).triggerNodeInstance(NodeInstance, String) line: 337

The NodeInstance argument in the last triggerNodeInstance(NodeInstance, String) method is a SplitInstance node instance. We use that information to trigger the following node instance, which is then run as follow this (the NodeInstance argument here is the previous node instance, the StartNodeInstance node instance.

> SplitInstance(NodeInstanceImpl).trigger(NodeInstance, String) line: 155
  > SplitInstance.internalTrigger(NodeInstance, String) line: 63
    > SplitInstance.executeStrategy(Split, String) line: 117
      > SplitInstance(NodeInstanceImpl).triggerConnection(Connection) line: 352
        > SplitInstance(NodeInstanceImpl).triggerNodeInstance(NodeInstance, String) line: 327

And so on:

> ActionNodeInstance(NodeInstanceImpl.trigger(NodeInstance, String) line: 155
  > ActionNodeInstance.internalTrigger(NodeInstance, String) line: 57
    > ActionNodeInstance.triggerCompleted() line: 61
      > ActionNodeInstance(NodeInstanceImpl).triggerCompleted(String, boolean) line: 296
        > ActionNodeInstance(NodeInstanceImpl).triggerNodeInstance(NodeInstance, String) line: 337

Check list

Issues

  • Making sure all NodeInstance's do both recursive and queue-based
    • Single-threaded vs multi-threaded implementations [: We leave this to the propagation queue]
  • Marshalling / Save Points
  • Make sure that this solution supports Compensation

Node Instances:

  • ActionNodeInstance

  • StartNodeInstance

  • EndNodeInstance

  • ThrowLinkNodeInstance

  • CatchLinkNodeInstance

  • FaultNodeInstance

  • SplitInstance

  • JoinInstance

  • ExtendedNodeInstanceImpl

    • EventNodeInstance

      • AsyncEventNodeInstance
      • BoundaryEventNodeInstance
    • StateBasedNodeInstance

      • CompositeNodeInstance
        • CompositeNodeStartInstance

        • CompositeNodeEndInstance

        • CompositeContextNodeInstance

          • DynamicNodeInstance

          • EventSubProcessNodeInstance

          • ForEachNodeInstance

            • ForEachJoinNodeInstance
            • ForEachSplitNodeInstance
          • StateNodeInstance

        • MilestoneNodeInstance

        • RuleSetNodeInstance

        • SubProcessNodeInstance

        • TimerNodeInstance

        • WorkItemNodeInstance

          • HumanTaskNodeInstance