paed01/bpmn-engine

Engine stop and resume

Closed this issue · 3 comments

Hi! i'm stuck with this.

Capturar

For sample, I have a process with many User Tasks, in my software, each User Task send a dynamic form for a user by e-mail and next usertask will only run after user fill received form.
When start process, I have one listener.on('activity.wait') inside I do some stuff, send email... and stop engine.
In engine.on('stop') I get state and store in database, and when user fill and submit my form I start a engine and resume execution loading previous state.

But it's always run process from start, re-executing previous UserTask.

How I can set Task as completed before get and store the engine state?

User tasks have to be signaled to continue. Hence, when the execution is resumed the task has to be signaled. Then, the process will proceed to the next task.

Perhaps something like this:

listener.on('activity.wait', stopAndSaveState);

async function stopAndSaveState(activityApi, execution) {
  if (activityApi.content.isRecovered) return; // if task is recovered ignore

  await sendEmail(activityApi.content); // send your email
  execution.stop();
  await saveState(execution.getState()); // persist state somewhere
}

When the engine is resumed by some trigger you can signal the execution with the id of the user task:

async function resumeAndSignal(message) {
  const state = await getState(); // get persisted state
  const engine = Engine().recover(state);
  execution = await engine.resume({listener});
  execution.signal(message);
}

resumeAndSignal({id: 'task1'}); // message object should have id or executionId to identify user task

That's it!
Thanks, and congratulation on this awesome work with this engine.

Hello @paed01 , I am trying to run a user task, when user submit the task on the basis of user input the engine should recognise the next process that should be executed, but it is failing to do so.
``app.post("/submit-form", async (req, res) => {
let responseSent = false;

try {
    const { processId, taskId, formData } = req.body;   

    const activeProcess = await activeProcesses.get(processId);

    if (!activeProcess) {
        return sendResponse(res, 404, { error: 'Process not found' });
    }

    let { elementApi, engine } = activeProcess;

    // Persist current engine state before signaling
    const currentState = await engine.getState();
    await engineStates.set(processId, currentState);
  
    await elementApi.signal(formData);

    const listener = new EventEmitter();

    // Attach event listeners
    listener.on("wait", async(nextElementApi) => {

        if (nextElementApi.type === 'bpmn:UserTask') {
            console.log("Handling User Task");
            const formInfo = extractFormInfo(nextElementApi);
            await activeProcesses.set(processId, { engine, elementApi: nextElementApi });
          
        } else if (nextElementApi.type === 'bpmn:ExclusiveGateway') {
            handleExclusiveGateway(nextElementApi, engine);
        } else {
            console.log(`Signaling element of type: ${nextElementApi.type}`);
            nextElementApi.signal();
        }
    });

    listener.on("activity.end", (elementApi, engineApi) => {
        if (elementApi.content.output) {
            engineApi.environment.output[elementApi.id] = elementApi.content.output;
            console.log(`Output set for element ${elementApi.id}:`, elementApi.content.output);
        }
    });

    // Ensure proper logging and error handling
    engine.on('error', (err) => {
        console.error("Engine error:", err);
        sendResponse(res, 500, { error: 'Engine error occurred' });
    });
    
    
    // Restore state from storage
    const savedState =await engineStates.get(processId);
    // console.log("Retrieved saved engine state:", JSON.stringify(savedState, null, 2));

    if (!savedState) {
        console.error("Saved engine state not found");
        return sendResponse(res, 500, { error: 'Saved engine state not found' });
    }

    // Initialize engine with saved state
    let recoveredEngine;
    try {
        recoveredEngine = await Engine().recover(savedState);
        if (!recoveredEngine) {
            throw new Error('Recovered engine is undefined');
        }
    } catch (err) {
        console.error("Error during engine recovery:", err);
        return sendResponse(res, 500, { error: 'Engine recovery failed' });
    }

    try {
        const execution = await recoveredEngine.resume({listener});
        await execution.signal({id:taskId});
        const newState = await recoveredEngine.getState();
        const output = listener;
        const result = await recoveredEngine.execute({listener});
        console.log({result})
      
    } catch (err) {
        console.error("Error resuming engine:", err);
        sendResponse(res, 500, { error: 'Process resumption failed' });
    }

} catch (error) {
    console.error("Unhandled error:", error);
    sendResponse(res, 500, { error: 'An unexpected error occurred' });
}

function sendResponse(res, statusCode, data) {
    if (!responseSent) {
        responseSent = true;
        res.status(statusCode).json(data);
        console.log(`Response sent with status ${statusCode}:`, data);
    } else {
        console.log("Attempted to send multiple responses. Ignored.");
    }
}

});

Here is the xml for more reference which i am trying to read via bpmn-engine

<?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:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:zeebe="http://camunda.org/schema/zeebe/1.0" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:custom="http://custom/ns" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn"> <bpmn:process id="Process_1" isExecutable="true"> <bpmn:extensionElements> <zeebe:userTaskForm id="UserTaskForm_3j9vacp">{"components":[{"values":[{"label":"Yes","value":"1"},{"label":"No","value":"0"}],"label":"is high priority","type":"radio","layout":{"row":"Row_1fyvaen","columns":null},"id":"Field_04ga5qi","key":"isHighPriority"},{"action":"submit","label":"Button","type":"button","layout":{"row":"Row_1yqgq5i","columns":null},"id":"Field_1aux2op"}],"type":"default","schemaVersion":16,"id":"Form_1t4hoyd"}</zeebe:userTaskForm> </bpmn:extensionElements> <bpmn:startEvent id="StartEvent_1"> <bpmn:outgoing>SequenceFlow_0b6cm13</bpmn:outgoing> </bpmn:startEvent> <bpmn:sequenceFlow id="SequenceFlow_0b6cm13" sourceRef="StartEvent_1" targetRef="Task_0zlv465" /> <bpmn:userTask id="Task_0zlv465" name="Do work02" camunda:formRef="" camunda:assignee="vrundali" camunda:candidateUsers="clientss" camunda:candidateGroups="devloper"> <bpmn:extensionElements> <zeebe:formDefinition formKey="camunda-forms:bpmn:UserTaskForm_3j9vacp" /> </bpmn:extensionElements> <bpmn:incoming>SequenceFlow_0b6cm13</bpmn:incoming> <bpmn:outgoing>Flow_1cy19yx</bpmn:outgoing> </bpmn:userTask> <bpmn:exclusiveGateway id="Gateway_1ukvnlg"> <bpmn:incoming>Flow_1cy19yx</bpmn:incoming> <bpmn:outgoing>Flow_0h1vbqu</bpmn:outgoing> <bpmn:outgoing>Flow_1pj2ri0</bpmn:outgoing> </bpmn:exclusiveGateway> <bpmn:sequenceFlow id="Flow_1cy19yx" sourceRef="Task_0zlv465" targetRef="Gateway_1ukvnlg"> <bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">=isHighPriority</bpmn:conditionExpression> </bpmn:sequenceFlow> <bpmn:sequenceFlow id="Flow_0h1vbqu" sourceRef="Gateway_1ukvnlg" targetRef="Activity_1avsex8"> <bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">=isHighPriority ="1"</bpmn:conditionExpression> </bpmn:sequenceFlow> <bpmn:sequenceFlow id="Flow_1pj2ri0" sourceRef="Gateway_1ukvnlg" targetRef="Activity_0vbud71"> <bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">=isHighPriority !="1"</bpmn:conditionExpression> </bpmn:sequenceFlow> <bpmn:scriptTask id="Activity_1avsex8" camunda:resultVariable="OutputVariable_2ovksdh"> <bpmn:extensionElements> <zeebe:ioMapping> <zeebe:output source="=i am first one running" target="OutputVariable_2ovksdh" /> </zeebe:ioMapping> </bpmn:extensionElements> <bpmn:incoming>Flow_0h1vbqu</bpmn:incoming> <bpmn:script>i am first one</bpmn:script>chiefangel820@gmail.com </bpmn:scriptTask> <bpmn:scriptTask id="Activity_0vbud71" camunda:resultVariable="OutputVariable_0bupce0"> <bpmn:extensionElements> <zeebe:ioMapping> <zeebe:output source="=i am second one running" target="OutputVariable_0bupce0" /> </zeebe:ioMapping> </bpmn:extensionElements> <bpmn:incoming>Flow_1pj2ri0</bpmn:incoming> <bpmn:script>i am second one</bpmn:script> </bpmn:scriptTask> </bpmn:process> <bpmndi:BPMNDiagram id="BPMNDiagram_1"> <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1"> <bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1"> <dc:Bounds x="173" y="188" width="36" height="36" /> <bpmndi:BPMNLabel> <dc:Bounds x="146" y="224" width="90" height="20" /> </bpmndi:BPMNLabel> </bpmndi:BPMNShape> <bpmndi:BPMNShape id="Activity_0adecy5_di" bpmnElement="Task_0zlv465"> <dc:Bounds x="256" y="166" width="100" height="80" /> <bpmndi:BPMNLabel /> </bpmndi:BPMNShape> <bpmndi:BPMNShape id="Gateway_1ukvnlg_di" bpmnElement="Gateway_1ukvnlg" isMarkerVisible="true"> <dc:Bounds x="405" y="181" width="50" height="50" /> </bpmndi:BPMNShape> <bpmndi:BPMNShape id="Activity_079ux8f_di" bpmnElement="Activity_1avsex8"> <dc:Bounds x="510" y="166" width="100" height="80" /> </bpmndi:BPMNShape> <bpmndi:BPMNShape id="Activity_0demez9_di" bpmnElement="Activity_0vbud71"> <dc:Bounds x="510" y="280" width="100" height="80" /> </bpmndi:BPMNShape> <bpmndi:BPMNEdge id="SequenceFlow_0b6cm13_di" bpmnElement="SequenceFlow_0b6cm13"> <di:waypoint x="209" y="206" /> <di:waypoint x="256" y="206" /> <bpmndi:BPMNLabel> <dc:Bounds x="192.5" y="110" width="90" height="20" /> </bpmndi:BPMNLabel> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge id="Flow_1cy19yx_di" bpmnElement="Flow_1cy19yx"> <di:waypoint x="356" y="206" /> <di:waypoint x="405" y="206" /> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge id="Flow_0h1vbqu_di" bpmnElement="Flow_0h1vbqu"> <di:waypoint x="455" y="206" /> <di:waypoint x="510" y="206" /> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge id="Flow_1pj2ri0_di" bpmnElement="Flow_1pj2ri0"> <di:waypoint x="430" y="231" /> <di:waypoint x="430" y="320" /> <di:waypoint x="510" y="320" /> </bpmndi:BPMNEdge> </bpmndi:BPMNPlane> </bpmndi:BPMNDiagram> </bpmn:definitions>