Process Instance Attribute Update
Introduction
When you need to change a Process Instance attribute in all running Processes, simply looping over all Process Instances would acquire Process Instance write locks for each and every Process Instance — and therefore the loop would not finish and/or block everything else in the system.
This article provides three possible ways of achieving your aim for a small number of Process Instances, as well as for a larger number.
com.nm.cluster.utils.ClusterUtils.commit($piId);is safe to use for all the solutions outlined below. However, make sure you never call commit($piId) in the middle of handling a regular HTTP request. As well as releasing the write lock, it also releases your reference to the Process Instance itself. Any changes to the Process Instance after the commit ($piId) will be lost.
Simple Loop
This method can be used when you have a small number of Process Instances. Here is how you can loop:
//
// loop over all process instances (no write lock is acquired here)
Integer $count := 0;
Indexed WorkflowInstance $pisRO := ToIndexed(CONTEXT().getWorkflowInstanceService().getWorkflowInstancesRO(), 'WorkflowInstance');
ForEach WorkflowInstance $piRO In $pisRO Do
String $piId := $piRO.getId();
$count := $count + 1;
//
// re-acquire process instance with a write lock
WorkflowInstance $pi := CONTEXT().getWorkflowInstanceService().getWorkflowInstance($piId);
//
// set attribute
$pi.setAttribute('eyeColor', 'golden');
//
// commit change and release write lock
com.nm.cluster.utils.ClusterUtils.commit($piId);
End
PRINTLN($count & ' process instances updated');
When tested using two nodes, 2000 process instances took 5.6 seconds.
The solution above, however, has one limitation: There could be issues when using it on larger numbers of Process Instances.
Loop for a Large Number of Process Instances
Using the implementation above on a system where you have more than 50,000 Process Instances might cause a 'write-behind queue limit reached' issue, meaning that more than 50,000 write commands are still scheduled to be executed and therefore the write-behind queue has reached its limit. One possibility is increasing the write-behind queue size. A better approach, however, is introducing a slight delay, e.g. waiting 1 second after every 1000 Process Instances.
Here is an example:
//
// loop over all process instances (no write lock is acquired here)
Integer $count := 0;
Indexed WorkflowInstance $pisRO := ToIndexed(CONTEXT().getWorkflowInstanceService().getWorkflowInstancesRO(), 'WorkflowInstance');
ForEach WorkflowInstance $piRO In $pisRO Do
String $piId := $piRO.getId();
$count := $count + 1;
//
// re-acquire process instance with a write lock
WorkflowInstance $pi := CONTEXT().getWorkflowInstanceService().getWorkflowInstance($piId);
//
// set attribute
$pi.setAttribute('eyeColor', 'golden2');
//
// commit change and release write lock
com.nm.cluster.utils.ClusterUtils.commit($piId);
//
// delay execution by 1 second every 1000 process instances
If $count % 1000 == 0 Then
PAUSE(1000);
End
End
PRINTLN($count & ' process instances updated');
When tested using two nodes, 2000 process instances took 8.3 seconds.
This implementation should work on any size of installation. However, it could be improved by making use of two nodes, thus splitting the work for each node, as explained in the section below.
Loop for a Large Number of Process Instances (Two Nodes)
-
Create a Script Function as follows:
CopyFunction UpdateAttributeAllLocalPIs() : String Begin
//
// loop over all *local* process instances (no write lock is acquired here)
Integer $count := 0;
Indexed String $piIds := ToIndexed(CONTEXT().getWorkflowInstanceService().getLocalWorkflowInstanceIds(), 'String');
ForEach String $piId In $piIds Do
$count := $count + 1;
//
// acquire process instance with a write lock
WorkflowInstance $pi := CONTEXT().getWorkflowInstanceService().getWorkflowInstance($piId);
//
// set attribute
$pi.setAttribute('eyeColor', 'golden3');
//
// commit change and release write lock
com.nm.cluster.utils.ClusterUtils.commit($piId);
//
// delay execution by 1 second every 1000 process instances
If $count % 1000 == 0 Then
PAUSE(1000);
End
End
Return $count & ' process instances updated';
End -
On any node, call the following Script in the Interactive Script Editor:
CopyIndexed Any $results := ExecuteDistributed(UpdateAttributeAllLocalPIs());
ForEach Any $result In $results Do
PRINTLN($result);
EndThis now executes in 3.7 seconds.
To make this absolutely bullet-proof, you should add a test after acquiring the Process Instance to test if the Process Instance still exists. This allows you to execute this script while the system is under load.
CopyFunction UpdateAttributeAllLocalPIs() : String Begin
//
// loop over all *local* process instances (no write lock is acquired here)
Integer $count := 0;
Indexed String $piIds := ToIndexed(CONTEXT().getWorkflowInstanceService().getLocalWorkflowInstanceIds(), 'String');
ForEach String $piId In $piIds Do
$count := $count + 1;
//
// acquire process instance with a write lock (ignore no longer existing process instances)
WorkflowInstance $pi := CONTEXT().getWorkflowInstanceService().getWorkflowInstance($piId);
If $pi == null Then
Continue;
End
//
// set attribute
$pi.setAttribute('eyeColor', 'golden3');
//
// commit change and release write lock
com.nm.cluster.utils.ClusterUtils.commit($piId);
//
// delay execution by 1 second every 1000 process instances
If $count % 1000 == 0 Then
PAUSE(1000);
End
End
Return $count & ' process instances updated';
End