Java ProcessEngine - Initial Variables Lost (Timers and Re-Run Processes)

I am executing a process using the following Java API call.

ProcessInstanceWithVariables process= processEngine.getRuntimeService()
    .createProcessInstanceByKey(processKey).processDefinitionTenantId(tenantId)
    .setVariables(variables).executeWithVariablesInReturn();

If the Workflow starts with a timer, it appears as if the variables are NOT set during the execution. Furthermore, if the engine is stopped (server crash) then restarted, the variables are also lost on subsequent executions. In other words, the engine attempts to run the process again, but the variables are null.

I do see the variables within the ACT_HI_VARINST table on the first execution. However, they are missing from the subsequent executions. Furthermore, the START_USER_ID is properly set and saved in ACT_HI_PROCINST, but is lost on subsequent executions.

Is there a way to ensure these variables set during the initial execution are always utilized? Is this a possible bug with the Timer Event?

Using Camunda Engine Plugin Connect 7.6.0 and Camunda BPM Spring Boot Starter 2.0.0

Thanks for any and all input.

Lukas

Is your process using the Camunda “async” feature?
You may need to aggregate or correlate process execution results into a some sort of a message event. And, when the process instance is ready to return the full payload, forward event back to waiting caller.

I’ve used:

  • rxJava
  • Camel (seda, jms, etc.)
  • direct event-management framework integration.

Depending on performance/reliability requirements… there are options.

Here’s a somewhat verbose example from my blog (includes pictures/source).

Noting that there are other approaches to help manage process token data (process variables). For example, you could manage process data within a document store - these include JSON databases.

Something to consider is segmenting process data from business information.

Thanks for your response, Gary. Not at this time. It’s a process that has a timer as the first action. The problem isn’t with the results, it’s with the variables coming into the process. Please see above:

.setVariables(variables)

Does the setVariables() method simply not work for processes with Timers? Furthermore, why would the START_USER_ID be null on subsequent executions?

Thanks again.

Please post the BPMN XML of the process.

Thank you for your help.

The all delegates are defined as Spring Beans, and execute correctly.

<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="1.6.0">
  <bpmn:process id="Timer" isExecutable="true">
    <bpmn:extensionElements>
      <camunda:executionListener delegateExpression="${securityInitListener}" event="start" />
    </bpmn:extensionElements>
    <bpmn:serviceTask id="AppSettingTask" name="Settings" camunda:delegateExpression="${savedAppSettingsExecutor}">
      <bpmn:extensionElements>
        <camunda:inputOutput>
          <camunda:inputParameter name="name">USER_dbarnhurst_CONFIG</camunda:inputParameter>
          <camunda:outputParameter name="setting">${result}</camunda:outputParameter>
        </camunda:inputOutput>
      </bpmn:extensionElements>
      <bpmn:incoming>SequenceFlow_1kddwpc</bpmn:incoming>
      <bpmn:outgoing>SequenceFlow_1yeznn0</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:endEvent id="EndEvent_1uabbr8">
      <bpmn:incoming>SequenceFlow_1yeznn0</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:endEvent id="ErrorEndEvent" name="Error">
      <bpmn:incoming>SequenceFlow_0tw5wfd</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:boundaryEvent id="BoundaryEvent_1cdga7q" attachedToRef="AppSettingTask">
      <bpmn:outgoing>SequenceFlow_0tw5wfd</bpmn:outgoing>
      <bpmn:errorEventDefinition camunda:errorCodeVariable="errorCode" camunda:errorMessageVariable="errorMessage" />
    </bpmn:boundaryEvent>
    <bpmn:startEvent id="TimerStartEvent">
      <bpmn:outgoing>SequenceFlow_1kddwpc</bpmn:outgoing>
      <bpmn:timerEventDefinition>
        <bpmn:timeCycle xsi:type="bpmn:tFormalExpression">R/PT30S</bpmn:timeCycle>
      </bpmn:timerEventDefinition>
    </bpmn:startEvent>
    <bpmn:sequenceFlow id="SequenceFlow_1kddwpc" sourceRef="TimerStartEvent" targetRef="AppSettingTask" />
    <bpmn:sequenceFlow id="SequenceFlow_1yeznn0" sourceRef="AppSettingTask" targetRef="EndEvent_1uabbr8" />
    <bpmn:sequenceFlow id="SequenceFlow_0tw5wfd" sourceRef="BoundaryEvent_1cdga7q" targetRef="ErrorEndEvent" />
  </bpmn:process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_1">
    <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Timer">
      <bpmndi:BPMNEdge id="SequenceFlow_1kddwpc_di" bpmnElement="SequenceFlow_1kddwpc">
        <di:waypoint xsi:type="dc:Point" x="226" y="136" />
        <di:waypoint xsi:type="dc:Point" x="346" y="136" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="241" y="121" width="90" height="0" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="ServiceTask_02huvjv_di" bpmnElement="AppSettingTask">
        <dc:Bounds x="346" y="96" width="100" height="80" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="EndEvent_1uabbr8_di" bpmnElement="EndEvent_1uabbr8">
        <dc:Bounds x="503" y="118" width="36" height="36" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="476" y="154" width="90" height="0" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="SequenceFlow_1yeznn0_di" bpmnElement="SequenceFlow_1yeznn0">
        <di:waypoint xsi:type="dc:Point" x="446" y="136" />
        <di:waypoint xsi:type="dc:Point" x="503" y="136" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="430" y="121" width="90" height="0" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="BoundaryEvent_1qbaxbj_di" bpmnElement="BoundaryEvent_1cdga7q">
        <dc:Bounds x="428" y="158" width="36" height="36" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="401" y="194" width="90" height="0" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="EndEvent_18vbpka_di" bpmnElement="ErrorEndEvent">
        <dc:Bounds x="428" y="258" width="36" height="36" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="433" y="294" width="25" height="13" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="SequenceFlow_0tw5wfd_di" bpmnElement="SequenceFlow_0tw5wfd">
        <di:waypoint xsi:type="dc:Point" x="446" y="194" />
        <di:waypoint xsi:type="dc:Point" x="446" y="258" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="416" y="216" width="90" height="0" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="StartEvent_1j120na_di" bpmnElement="TimerStartEvent">
        <dc:Bounds x="190" y="118" width="36" height="36" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="118" y="154" width="0" height="13" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</bpmn:definitions>

A little more information:

On the first pass, the listener correctly retrieves the variables as set on line 56 of StartProcessInstanceCmd. (Camunda Engine 7.6.0).

return new ProcessInstanceWithVariablesImpl(processInstance, variablesListener.getVariables());

Again, those variables are set using the following command:

ProcessInstanceWithVariables process= processEngine.getRuntimeService()
.createProcessInstanceByKey(processKey).processDefinitionTenantId(tenantId)
.setVariables(variables).executeWithVariablesInReturn();

However, on the second pass of StartProcessInstanceCmd, the variableStore on the ProcessInstance is empty. Nothing has been set.

It looks like your process is configured to start via timer-event: “R/PT30S”.

This means that you really shouldn’t be using “executeWithVariablesInReturn()” - however, noting that you mentioned that your process did start on its 1st invocation is interesting. Something of note… meaning it really should have thrown an error from the very begging given you appear to be requesting an “executeWithVariables” from a process that is already modeled to start on its own every 30 seconds (if I’m reading this correctly).

Or… is the “Settings” service task failing to gather variable values (but you mentioned executeWithVariables" API call).

Here’s my interpretation:

  1. Process initially starts per expectations via API call “executeWithVariablesInReturn”. However, noting that a formal BPMN “start event” interpretation implies that it really shouldn’t given that it’s already setup to start per timer definition.

  2. During this first execution (start via executeWithVariablesInReturn), we see variables values flow in and are verified during execution.

  3. When the process starts up again by itself the variables aren’t set. But, this appears to be running correctly because on n+1 we have a new instance - this instance started via “start timer event”. And, not being started via Java API, there are no variables flowing in.

Though it seems reasonable that our process definition must have an initial instance, something that begins emitting “start events”, we find this isn’t the case.

  • When this process model is loaded into Camunda, it will begin execution immediately and start launching new instances as defined - every 30 seconds. Each instance is essentially a new invocation of itself EXCEPT that we don’t get a 2nd start timer event - meaning, that we don’t now have n+1 timer event instances and subsequent event inflation.

Personally, I avoid starting a process from a timer “start-process” event. Though it functions correctly as defined, I have issue with a process type (model) starting itself… My general workaround is to configure a quartz timer (or similar) to emit events which are then captured and hooked into some code that directly starts the process via API. This workaround has more to do with configuring/re-configuring the timer-even without requiring redeployment.

External timer services (i.e. quartz) also provide, depending on your libraries, run-time configuration via web-console such as Hawtio. Most importantly… you don’t end up with a process type (model) firing unwanted events into potentially previous BPMN model versions - not forgetting that we have a sort-of process instance starting itself every 30 seconds.

Correct. We also want to be able to set variables (or pass arguments, parameters, ANYTHING) that will be accessible on each subsequent execution of that workflow.

As you note, perhaps executeWithVariablesInReturn() isn’t the proper method. If so, what is preferred?

That’s exactly correct.

Is there a way to ensure variables will be available to each “loop” of workflow? We could load arguments via a hard-coded value in the workflow itself (from a database, message queue, etc) but I’d rather be more flexible; passing in variables is preferable.

Thank you again for your insight and help.

You can add an execution listener to the start event and add any variables you like.

Cheers,
Thorben

Sorry for changes in previous post… my dogs demanded breakfast (no exceptions - all work stops).

Option A - start via Process:

Option B - start process via formal timer service (i.e. Quartz)

Option C - etc…

The example I provided above includes an execution listener. The variables are not available there either.

Sorry, maybe I wasn’t clear enough. You could use the listener to set the variables. That way, every instance has the variables once the listener has executed.

I understand completely. I won’t disgust you with what my cat has started doing to get me to feed her… it’s… terrible.

The problem is on the “Gather variable values, etc” step in the Timer Service is: how will I know from where to gather the variables? If I gather them from a database, I’d like to provide connection information, a table, etc. Or, as above, I’d LOVE to just pass in the variables themselves. It works on the first pass, but the second pass the variables are gone.

Yes, I think this might be the approach we take if variables are lost in Camunda process timer “loops.”

Same issue as Option A: I’d need to hard-code the location of that Rest Service. What if the server name changes? How would I specify the server name in different environments? It would be great to pass that in as a variable, but that variable would be lost on the second execution of the loop, and the process wouldn’t know where the Rest Service is located.

Do you feel this is a missing feature, or a bug? Is the intended functionality such that the variables SHOULD be lost on subsequent loops? Keep in mind, the authorized user stored in START_USER_ID is also lost on subsequent executions.

Thank you so much for taking the time with this.

Understood. Please see my Option C response above. How would the Listener know from where to get the settings without a variable? That location (Rest Service, by example) would be located on different servers in different environments. I’d rather not have a different workflow for each environment.

Can you please elaborate how you intended to achieve this with the code in your original post? Or stated differently, where would you like to keep the dynamic context (e.g. rest service location) that you need to set the variables? Then we can try to adapt the execution listener solution to that.

@Autowired
CamundaBpmnService camundaBpmnService;

@Override
public void notify(DelegateExecution execution) throws Exception {
        String appId = execution.getTenantId();
        String name = (String) execution.getVariable("settingsName");
        AppSetting retrievedAppSettings = camundaBpmnService.getSettings(appId, name);
        execution.setVariableLocal("result", retrievedAppSettings.getSettings());
}

The camundaBpmnService above is tasked with loading the AppSetting object. Implementations may get it from a web service, database, etc. What’s important is that “settingsName” determine from where and how the settings are loaded. Without settingsName being available from the execution variables on every loop, it is not possible to retrieve the AppSetting object.

Okay, how is the value of settingsName managed? Is it for example an environment variable?

Edit: Or is there a Spring bean that always holds the currently valid value?

It is passed in as a variable:

ProcessInstanceWithVariables process= processEngine.getRuntimeService()
.createProcessInstanceByKey(processKey).processDefinitionTenantId(tenantId)
.setVariables(variables).executeWithVariablesInReturn();

I know. Who/what determines the value of the variable?

The user who starts the execution of the workflow.