Finding out why User Task was deleted

Hello,

Let’s say we have a process like this:

UserTaskTimedOutOrManuallyDeleted

I’m trying to find out why User Task was deleted. I’m aware there are some trigger methods in parse listeners structure, parseUserTask or parseBoundaryTimerEventDefinition, but I am not sure which one to proceed with.

For the above process, User Task can be deleted in two ways. First, either the Timer boundary event connected to the User Task has expired or the process to which the User Task is in the same execution has been manually deleted by someone from the outside while the Timer boundary event has not expired yet. Either way, Camunda Engine deletes User Task, you know.

While the event is happening, that is, the User Task is being deleted, how can I determine if the User Task was deleted because of the Timer boundary event or because of the Process deletion?

There will be async-after selected with the User Task.

Thank you.

P.S.: Starter thread here, you can find some more information and a solution maybe works for you —> How to get User task's cancellation/ending reason from the History service

1 Like

You can try to register an execution listener on the user task’s end event. Then, cast DelegateExecution to ExecutionEntity and call #isExternallyTerminated. I haven’t tested it, so maybe you’ll find it does not work after all. In that case, the only remaining option is to register listeners on the flows leaving the boundary event to know that the boundary event was triggered.

Why an additional thread on this?

I mixed things up a bit in the other thread. The subject has moved away from the question I wanted to ask. I thought it would be good to open a new thread that specifically states my case. Anything wrong here?

Hey @GotnOGuts
Do you have a solution for me? Eh.

That totally makes sense, but it probably would have been good to include a link to the other thread at the start.

From what I recall of your requirements:

  • Must happen automatically
  • Must not require modeler to explicitly model
  • Must easily differentiate between tasks that were manually cancelled and ones that expired (Boundary Timer)

The Task Timeout function mentioned in the other thread is likely the cleanest, but requires the modeler to explicitly add it to the properties.
The script task in the Boundary Timer path is nice in that it gives you lots of flexibility, but also requires the modeler to explicitly call it.

Thorben’s answer of registering an execution listener is likely the best answer, but it might get difficult to troubleshoot and maintain, and is C7 only (Listeners don’t exist in the same way in C8, but in that case, you can monitor the event stream…)

@GotnOGuts edited the first post that shows the starter thread now, thanks!

1 Like

This is where things get really gritty.
You could use a parseUserTask function to automatically add a User Task Listener, since the parse functions happen on the BPMN Deploy, rather than on the running of the Process.
You could use a parseBoundaryTimerEventDefinition to automatically attach the Listener that Thorben referred to.

There’s a lot more pieces to this that I can’t fully wrap my mind around still.

@thorben @GotnOGuts

Actually registering an execution listener on the user task works.
If anything missing, please comment!

ParseListenerPlugin:

@Component
public class ParseListenerPlugin extends AbstractProcessEnginePlugin {
    @Override
    public void preInit(ProcessEngineConfigurationImpl processEngineConfiguration) {
        List<BpmnParseListener> preParseListeners = processEngineConfiguration.getCustomPreBPMNParseListeners();

        if (preParseListeners == null) {
            preParseListeners = new ArrayList<>();
            processEngineConfiguration.setCustomPreBPMNParseListeners(preParseListeners);
        }

        preParseListeners.add(new UserTaskParseListener());
    }
}

UserTaskParseListener:

public class UserTaskParseListener extends AbstractBpmnParseListener {
    @Override
    public void parseUserTask(Element userTaskElement, ScopeImpl scope, ActivityImpl activity) {
        activity.addListener(ExecutionListener.EVENTNAME_END, new UserTaskEndExecutionListener());
}

UserTaskEndExecutionListener:

public class UserTaskEndExecutionListener implements ExecutionListener {
    private static final Logger LOG = LoggerFactory.getLogger(UserTaskEndExecutionListener.class);

    @Override
    public void notify(DelegateExecution delegateExecution) throws Exception {
        LOG.info("*************** UserTaskEndExecutionListener ***************");

        ExecutionEntity executionEntity = (ExecutionEntity) delegateExecution;
        LOG.info("executionEntity: " + executionEntity);
        boolean externallyTerminated = executionEntity.isExternallyTerminated();
        LOG.info("externallyTerminated: " + externallyTerminated);
        boolean canceled = executionEntity.isCanceled();
        LOG.info("cancelled: " + canceled);

        delegateExecution.setVariableLocal("somelocalvar", "somelocalval");
    }
}

When process deletion happens, it logs like this:

*************** UserTaskEndExecutionListener ***************
executionEntity: ScopeExecution[69776c60-fc0a-11ed-b376-a08069929d2b]
externallyTerminated: true
cancelled: true

When the user task timeouts because of the timer boundary event, it logs like this:

*************** UserTaskEndExecutionListener ***************
executionEntity: ScopeExecution[b378955f-fc0a-11ed-b376-a08069929d2b]
externallyTerminated: false
cancelled: true

So… If externallyTerminated: true and cancelled: true I know that the user task canceled because of the process deletion, there is a manual intervention! And, if externallyTerminated: false and cancelled: true, then the user task most probably timed out. Notice the “most probably” part! Being externallyTerminated: false and cancelled: true means always the timeout?

I changed the process a little bit.
UserTaskCanceledByAnInterruption
While the token is waiting on “user task 1” and the timer boundary event has not expired yet, when I correlate a message, that matches the message boundary event, to the process, UserTaskEndExecutionListener prints the log as follows:

*************** UserTaskEndExecutionListener ***************
executionEntity: ScopeExecution[c54d3750-fc18-11ed-9ad1-a08069929d2b]
externallyTerminated: false
cancelled: true

It’s the same as when the user task is actually timed out. UserTaskEndExecutionListener will always print the same log for all kinds of “Interrupting” type events added to the user task. Right?

In this case, I need to distinguish that “user task 1” is “canceled” not because of the message boundary event, but because of the timer boundary event. How can I do that?

Thanks.