Let us start with a simple process definition, the classic 'Hello World'. When executed, this process will print out 'Hello, World!' and then complete.
First, we introduce a graphical notation for process definitions and execution. Not all the symbols will make sense immediately, but they will all be explained.
The simplest useful process definition would consist of a simple node. Here is the graphical representation:
How will this process be executed? First the engine needs to determine where to start execution.
Start Node - A node at which a token will be placed when process execution begins.
There are various ways of handling this. For example, there may be a specific type of node designated for start positions. All nodes of this type will have tokens placed in them at process start. Alternately, nodes may have an attribute which indicates whether or not they are a start node, allowing any node to be a start node. Sarasvati takes this second approach.
Assuming that the 'Hello World' node is a start node, execution would begin by creating a new node token at the 'Hello World' node.
Node Token - A token situated at a node. Node tokens track the response of the node guard (see below). They may also have attributes.
With the addition of the node token, the process would now look like:
As you can see, the node now has an active node token stationed on it.
At this point the node has not yet been executed. Before it can be, its guard would need to be invoked.
Node Guard - Nodes have functionality associated with them, which will be executed when a node token is accepted into the node. However, before a node is executed, its guard will be executed. The guard is allowed one of three responses:
Accept - The node will be executed.
Discard - The node token will be marked as discarded and the node will not be executed.
Skip - The node will not be executed, however, processing will continue as if the node had completed execution normally.
By default, a node's guard will return Accept. The node will then be executed . This should cause 'Hello, World!' to be printed out.
Node Execution - When a node is executed, whatever custom logic has been assigned by the developer will run. To complete node execution, the node must inform the engine that that the given node token has been completed. Node completion may happen synchronously as part of the execution of the node function or it may happen later, asynchronously.
As there are no further steps in the process, it is now complete and looks like:
Process Completion - A process with no active tokens is considered complete.
Let's now example a slightly more complicated example. Instead of a single node, we'll have two, the first of which prints out 'Hello', the second prints out 'World'. It looks as follows:
The Hello node is a predecessor of the World node. This dependency is indicated by the directed arc.
As the Hello node is marked as a start node, a node token will be placed there when the process begins executing.
When the node token on Hello is completed, an arc token will be generated on the outgoing arc.
Arc Token - A token situated on an arc. Arc tokens exist so that nodes know when to execute. Arc tokens may not have attributes.
Whenever an arc token is created on an arc, the join strategy of the node at the end of the arc is evaluated, to determine if the node is ready to have a node token created at that node. The only time join strategies are not used is when a process is started. At that time all start node will have node tokens created on them.
join strategy - A join strategy determines if a node token should be created on a node. Evaluation of the join strategy is generally initiated by the processing of an arc token on an incoming arc to the node. The join strategy will determine two things:
Is the join complete? Some join strategies require multiple arc tokens to be present before a node token is created on the node. Others may be satisfied every time an arc token arrives.
Which arc tokens completed the join? Every arc token that participates in completed the join will be marked complete and will be noted as a parent of the new node token, preserving a history of the flow of execution.
Since the arc on which the arc token is situated goes into a node using theor join strategy , a node token will be created on World immediately.
or join strategy - The or join strategy will allow a new node token every time an arc token arrives at the node. This stands in contrast to a label-and join strategy , where active arc tokens must exist on all incoming arcs with the same name.
The process now looks like:
The World node will now run its guard and then execute. Finally the node token will be completed.
Let us now examine an example which contains concurrent execution.
The process describes an approval process.
A request is made
Two approvals must be obtained
The request is granted
The process looks like:
This a simplified system, since it does not allow approvals to be denied.
There is more than one way that this process could be executed.
If the approvals are granted by people, the nodes will almost certainly be executed asynchronously. This means that when a token arrives at Approval 1 , the node will generate a notification to the user who is to do the approval. The token will then enter a wait state . Execution may continue elsewhere in the process, but this token will wait until the user enters the system and grants approval.
If approvals are done by software which does a check and then returns immediately the tokens will not have enter a wait state , but may continue immediately.
Wait State - When a token enters a node and the node is executed, it may choose not to immediately continue process execution at the end of the node method. In this case the token will remain in the node until it is complete asynchronously. While the token is waiting to be completed, it is considered to be in a wait state.
Let us view process execution for both these cases, starting with the case where approvals are done by people and thus tokens will need to enter wait states.
Execution will begin as usual, by placing a node token in the nodes marked as being start nodes.
The Request node will be executed. It generates a task for the requester to complete. Until the requester has filled out out the request and completed the task, the token will be in a wait state. During this time the process will look like:
Question: What happens once the Request has been completed? Which arc or arcs will arc tokens be generated on?
Answer: Sarasvati requires that an arc name be specified when completing a node token. All arcs with this name will have arc tokens generated on them.
Some things to note:
Most arcs have no name specified. They are considered to have the 'default' name.
Usually when completing a node token, the default arc name will be given.
Each arc will have an arc token placed on it in turn. No specific order is guaranteed
When an arc token is placed on an arc, it will continue on to its end node immediately and see if the node can be executed.
So now the node token on Request has been completed and arc tokens will be generated on the outgoing arcs. First a node token will be generated on the upper arc (though order of arc execution is not guaranteed).
This arc leads to a node which can be executed. The arc token will be completed and a node token will be placed in the Approval 1 node.
Here the node token will enter a wait state. Since no further execution can take place here, an arc token will now be generated on the second outgoing arc.
Again, since node Approval 2 can be executed immediately, the arc token will be completed and a node token will be created. It will also enter into a wait state once the notification to the user has been created.
At some point one of the approvals will be completed. Let's say that it's Approval 2 . This will mark the node token complete and generate an arc token on the outgoing arc.
Now the engine will see if the Grant node can be executed. However, as the dashed border indicates, the Grant node is using the label-and join strategy. .
label-and join strategy - When an arc token arrives a node using the label-and join strategy, arc tokens must exist on all other arcs with the same name before the node will accept a node token.
Since there are two arcs with the 'default' name coming into Grant , and only one of them has an arc token, the node can not be executed at this time. Execution will halt at this point.
At some point later, the token at Approval 1 is completed. This generates an arc token on the outgoing node.
Now when the engine tries to execute Grant it finds arc tokens on all the incoming 'default' arcs. These arc tokens are marked complete and a node token is generated on Grant .
Once the Grant task is finished, its node token will also be completed and the process will be complete.
As seen the previous example, a process may have multiple tokens active concurrently. Does this imply that each token executes in a separate thread? No. Concurrency here is like that of multiple programs running on a single chip. Each runs in turns, but may present the appearance of running simultaneously.
However, true multithreading can be done at the node level. Each node when executed, may hand off its work to a background thread. The node token will then enter a wait state, and other nodes may be executed. When the background task is complete, it may then complete the node token, allowing further execution.
Note that only one thread may safely execute the process at any given time, and care must be taken to serialize access to the process itself.
Lets now take a look at the same process, except now the approvals will be done by software and will not require a wait state.
The execution will be the same up to the point where Approval 1 is executing.
Previously, the node token went into a wait state. This time, the approval is done synchronously and the token will be completed. This will generate an arc token on the outgoing arc.
Again, the Grant node is using the label-and join strategy, so it will wait for an arc token on the other incoming arc before executing. Execution will continue on the lower outgoing arc of Request .
Execution will continue into Approval 2 .
This execution will also finish synchronously and an arc token will be generated on the outgoing arc.
Execution will finish as before now that all required incoming arcs have tokens on them.
Now that we've seen how execution can split across arcs and join strategies can bring current executions back together, let us examine how to select which outgoing arcs receive tokens and which nodes get executed.
This example uses almost the same process as the previous example. The difference is that either or both approvals may be optional, depending on what is being requested.
Let us pick up execution after the request has been entered and an arc token generated on the upper arc:
Now the node token will be generated in Approval 1 .
However, remember that this does not mean that the node will immediately execute. First the guard must be invoked. Up until now, the guard has always been assumed to just return Accept . This time however, the guard is intelligent. It will check to see if this approval is required. If not, it will return a Skip response.
Skip - A guard response which indicates that the node should not be executed, but that execution should continue on the outgoing nodes. An arc name may be specifying indicated which arcs should be used. If no arc name is given, arcs with the default name (unnamed arcs) will be used.
Assume that Approval 1 is not required. The node token will marked as having skipped the node, and execution will continue on the outgoing arc.
Having seen Skip, let us examine how to use the Discard response from guards. The same basic process definition is used, only this time, the assumption is that only one of the guards is required.
The graph now looks like:
Because we are using discard, only one token will reach Grant. This is why the Grant node is no longer a label-and join stragy.
Execution begins as normal. We pick up execution where a node token has been generated in Approval 1.
In this case, the guard determines that Approval 1 is not required, and returns a Discard response.
Discard - A guard response indicating that the node token should be marked as discard, the node should not be executed and no tokens will be generated on outgoing arcs.
The process now looks like:
The node token has been discarded, and execution has continued from the completion of Request where an arc token has been generated on the lower outgoing arc. Execution will now continue.
Approval 2 will accept its node token and will continue normally.
Remember, because Grant is using the or join strategy, it will have a node token generated on it as soon as any arc tokens arrived.
This same basic process could be implemented using a guard which returns Skip along with an arc name.
In this variant, a Select node has been inserted after Request . This node has no functionality, it only exists to give the guard a place to run.
Let us pick it up after process started, as Select has a node token generated on it, and its guard is invoked.
The Select guard will return a Skip response which includes the arc name on which to exit. All arcs with this name will have an arc token generated on them . In this case, let us say the guard determines that Approval 2 is required. It returns Skip two . An arc token is then generated on all arcs named two (of which is there only one in this case).
From here execution continues as normal.
As mentioned previously, when a node token is completed, an arc name must be specified. Arc tokens will be generated on all outgoing arcs with that name. So the previous example could also be implemented like this:
Instead of using the guard on the Select node, the Request node will specify which arc to exit on.
If we again specify two , then an arc token will be generated on that arc.
From there, execution will continue.