paed01 / bpmn-engine

BPMN 2.0 execution engine. Open source javascript workflow engine.
MIT License
860 stars 166 forks source link

How to retry failed ScriptTask? #182

Closed sumbricht closed 3 months ago

sumbricht commented 6 months ago

Hi

I'm currently detecting errors on ScriptTasks using a listener, but don't know how to trigger a retry of the script. Could you please give me a hint how to do so?

listener.on('activity.error', (activity, execution) => {
  if(activity.type == 'bpmn:ScriptTask') {
    // how do I trigger a retry? Is there a method on the activity or do I need to publish a specific message using activity.broker.publish?
  }
})

If I'm doing something suboptimal, please let me know as I'm still trying to better understand the inner workings of the engine :-). PS: on that note, is there any documentation/diagram showing all internal messages that the engine sends via the broker? If not, what would be the best place in the code to better understand this?

Thanks for your help! Simon

paed01 commented 6 months ago

Here is a description of the activity lifecycle.

To your retry question, I would catch the error with an error boundary event and loop back. Infinite loops may be accomplished without some safe guarding.

sumbricht commented 6 months ago

Thanks a lot for your input, @paed01. I've done quite some digging around in the code and documentation, plus some experimentation to get one step closer. What I failed to mention in my question above is that my end goal is actually not to automatically retry the ScriptTask right away, but rather to allow an admin to trigger a retry (e.g. after the cause of failure has been resolved). Also, I'd like to avoid changing the process XML if possible.

Therefore a slight update to my question:

What I currently have in mind is something like this:

class MyScriptTaskBehaviour {
  constructor(activity, context) {
    this.activity = activity
    this.context = context
  }

  async execute(executeMessage) {
    const { content } = executeMessage
    const { id, broker } = this.activity

    try {
      await actualCodeThatWillRun[id]() // id == 'script1'; see code at the bottom
      broker.publish('execution', 'execute.completed', { ...content })
    } catch(err) {
      // is 'execute.error' recoverable?
      broker.publish('execution', 'execute.error', { ...content, error: err }, {mandatory: true})

      // is there a possibility to put activity in 'wait' status?
      broker.publish('event', 'activity.wait', { ...content, state: 'wait' })

      // afterwards, wait for admin to manually trigger function manuallyTriggerRestartOfScript1
    }
  }
}

function manuallyTriggerRestartOfScript1() {
  // is it possible to send a message causing a restart of the activity?
  broker.publish('execution', 'execute.start', { ...content }, {mandatory: true})
}

const actualCodeThatWillRun = { // code is generated dynamically and therefore needs to be executed differently from how ScriptTask normally does it
  async script1() {
    // do async calculation
  }
}

Again, thanks a lot for any hint in the right direction!

paed01 commented 6 months ago

Have look at SignalTask to setup api-listening to signal the script task to run again. To clean-up broker consumers you also need to copy the stop functionality.

Publish a execute.restart execute.start message on the execution exchange when the script task is signalled. Most likely you will get that message in the async execute(executeMessage). Thats how the activity execution works.

The mandatory-property on messages should not be used unless you want the broker to throw a return event. The engine uses return event to throw errors.