Service task with "Retry Time Cycle" - how to "send a notification/throw a message" on each retry

service task with “Retry Time Cycle” - how to “send a notification/throw a message” on each retry

Hello,
I have a service task with a “Retry Time Cycle” configured. When there is no retries left a BpmnError is thrown (working perfectly).
I would like also to be able to send a notification on each retry to notify a third party service that the service task is still running and is performing retries.
I tried to implement this feature using the runtime service and the createMessageCorrelation method but this is not working (probably because the createMessageCorrelation method is called in the catch exception block, transaction rollback ?).
Is there another way to achieve this (also give a try to Signal event with no luck)?

Enclosed you will find

  • the BPMN file ‘prsu100c_submission’ (main flow)
  • the BPMN file ‘prsu100c unexpected submission error handler’ (notify third party service on each retry)
private void notifyRetry(@NonNull final FailingOnLastRetry annotation, @NonNull final ProceedingJoinPoint joinPoint, @NonNull final ContextualException exception, final int retries) {
        if (StringUtils.hasText(annotation.onErrorMessageCallback())) {
            var signature = (MethodSignature) joinPoint.getSignature();
            var logger = LoggerFactory.getLogger(signature.getDeclaringType());
            try {
                for (var arg : joinPoint.getArgs()) {
                    if (arg instanceof DelegateExecution) {
                        var execution = (DelegateExecution) arg;
                        RuntimeService runtimeService = execution.getProcessEngineServices().getRuntimeService();
                        logger.debug("error while processing activity [{}], create message correlation : message [{}], key [{}], error message [{}], remaining_retries [{}]", execution.getCurrentActivityName(), annotation.onErrorMessageCallback(), execution.getProcessBusinessKey(), exception.getMessage(), retries);
                        runtimeService.createMessageCorrelation(annotation.onErrorMessageCallback().trim())
                                .processInstanceBusinessKey(execution.getProcessBusinessKey())
                                .setVariable(CamundaConstants.VAR_REMAINING_RETRIES, retries)
                                .setVariable(CamundaConstants.VAR_EXCEPTION, exception.toString())
                                .correlate();
                    }
                }
            } catch (Exception e) {
                String exceptionText = "cannot create message correlation";
                var exceptionContext = new LinkedHashMap<String, String>();
                exceptionContext.put("message_name", annotation.onErrorMessageCallback());
                logger.error(new BPMException(exceptionText, e, exceptionContext).toString());
            }
        }
    }

Spring boot : 2.6.11
Camunda : 7.16.0

Regards

Grégory
prsu100c_submission.bpmn (29.9 KB)
prsu100c_unexpected_submission_error.bpmn (3.3 KB)

I think you’re right that a rollback of the transaction is preventing you from commiting any additional action as part of the service task execution. I think a rough outline of how you could do this would be:

Make sure you catch the RuntimeException that’s occuring and rethrow a checked exception. That will prevent the transaction for the service task from being marked for rollback. This occurs when a RuntimeException passes a bean boundary. In your delegate, catch the checked exception and do two things: invoke another bean with a @Transaction(requires=new) annotation. In that bean, perform the additional action. Because it requires a new transaction, it is not affected by whatever happens in the original transaction. There, you could send a message or escalation, etc to a non-interrupting boundary event on the service task and do some action. The second action inside the delegate is to throw a new RuntimeException, which Camunda will catch and will cause the service task invocation to fail and the retries to continue to decrement as normal.

1 Like

Thanks, I will try your solution. 2 questions :
“you could send a message or escalation” : is it better to send a message or an escalation ? using the API is it possible to send an escalation message , Did not find how to do that in the doc.
“to a non-interrupting boundary event” : is it mandatory to define a “boundary event” ?

Regards

I guess the choice between message and escalation is more of a semantic one. I know you can send an escalation through the API, but it may only pertain to user tasks now that you mention it :slight_smile:

I assumed that you wanted the notification to be visible in the model. In that case, a non-interrupting boundary event makes the most sense. But if it’s more of a technical facility you’re creating with the notifications, you could obviously do without. The main point is that whatever action you want to do that will perform an action with a Camunda API or even just store something in the database, it needs to run in a separate transaction so that it doesn’t get wiped out by the transaction being marked for rollback because a RuntimeException occurs.

Hi Tiese

I finally get it working. No need to throw a checked exception but in conjunction with @Transaction(requires=new), I also instruct camunda to declare a new Context with ProcessEngineContext.requiresNew() (see camunda-docs-manual/transactions.md at master · camunda/camunda-docs-manual · GitHub)

Regards

Greg