How to get User task's cancellation/ending reason from the History service

Hello all,

As far as I can see and the features we are using with the Modeler now, User tasks can be Canceled in two ways. First, if a timer boundary event is added to a User task and it times out before the User task was completed. And second, if the Process that the User task is connected to was deleted from the Cockpit while the User task was active.
In both these cases, User task appears Cancelled on Camunda History.

I am preparing a batch job related to User tasks and I need to know for what reason the User task was Cancelled.
What I found the closest to the need is ActivityState values.

var activityInstanceId = historyService.createHistoricTaskInstanceQuery()
                        .taskId(taskId)
                        .singleResult()
                        .getActivityInstanceId();
var activityInstanceState = ((HistoricActivityInstanceEntity) historyService
                        .createHistoricActivityInstanceQuery()
                        .activityInstanceId(activityInstanceId)
                        .singleResult()).getActivityInstanceState();

There is an explanation for ActivityState values:

//  ActivityState values
//      0: default
//      1: scopeComplete
//      2: cancelled
//      3: starting
//      4: ending

Questions:

  • In which case, which ActivityState value occurs, can you give more detailed information?
  • If activityInstanceState == 2, how can I find out whether the User task was Canceled because the timer boundary event or the Process belonging to the User task was deleted?
  • How would you do if it were you? How can I find out what happened to the User task by looking at the History service?

Thank you.

1 Like

hello qube,

to answer your third question (how would you do ā€¦):

According to the docs, e.g.The Job Executor | docs.camunda.org :

ā€¦ a job is created when a timer event ā€¦ is approached.

So, the interrupting event should be visible as historic job entry with JOB_STATE_ ā€œ2ā€ (success) and a JOB_DEF_TYPE_ ā€œtimer-transitionā€ in Table ACT_HI_JOB_LOG.
Hopefully youā€™ll find the corresponding Api and/or Rest-Query parameters - didnā€™t try it yet myself.

That would be my approach on first thought.

Hi @qube,

you can take a look at history of the process instance: Camunda Platform REST API.

If you get the state EXTERNALLY_TERMINATED, somebody deleted the whole process instance.

Hope this helps, Ingo

@Ingo_Richtsmeier
Thank you for your answer, but I cannot get the state information in the historic process instance.

I tried like this:

HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();

No State information with the result here. I thought maybe something is missing with the Java API and tried with native query like this:

HistoricProcessInstance historicProcessInstance = historyService.createNativeHistoricProcessInstanceQuery()
                    .sql("SELECT * FROM ACT_HI_PROCINST WHERE PROC_INST_ID_ = #{proc_inst_id}")
                    .parameter("proc_inst_id", processInstanceId)
                    .singleResult();

No State information too.

However, if I manually query on the database with the same processInstanceId, I can see the STATE_ column at the end.

Do you have any comments?

@cmalouf
Thanks for your time!
But, in this case, there could not be a timer boundary event attached to the user task all the time. For example, consider this:

cancelled_user_task

This time, the process was externally cancelled, so the user task. But there is not a timer element in the process, then there will be no ACT_HI_JOB_LOG record. If any timer boundary event was attached to the user task, your solution would be ok to go, but I need to find a more generic way matches to all the cases.

Thanks!

@cmalouf @Ingo_Richtsmeier

In our case, only timer boundary events can be used with User Tasks at the moment. So, I will only evaluate the User Taskā€™s timeout/cancelled/completed cases together with it.

The following cases come to my mind:

  1. Manual deletion of the Process (which the User Task was bound), when a User Task was waiting with a timer boundary event and it was not expired yet. (The User Task will be cancelled by the Engine)
  2. Timing out of a User Task with a timer boundary event attached because of the due date was reached (The User Task will be cancelled by the Engine)
  3. Manual deletion of the Process belonging to a User Task that does not have a timer boundary event attached (The User Task will be cancelled by the Engine)
  4. Completion of a User Task with no timer boundary event attached

Considering these cases, I could not find the answer to the question of what happened to the User Task by looking at a single place in the Engine. So, I think I need to combine both answers on some points.

My findings are as follows:

  1. If there is a record in the ACT_HI_OP_LOG table with PROC_INST_ID_ (which the User Task is bound) and WHERE condition with ENTITY_TYPE_ = ā€˜ProcessInstanceā€™ AND OPERATION_TYPE_ = ā€˜Deleteā€™, then this Process has been manually deleted. Therefore, I can say that User Task has been deleted manually too.
  2. When I query with History Service in the ACT_HI_PROCINST table, if STATE_ = EXTERNALLY_TERMINATED, I can say that Process and User Task have been deleted manually.
  3. When looking at the record with PROCESS_INSTANCE_ID_ (which the User Task is bound) in the ACT_HI_JOB_LOG table, according to the JOB_STATE_ information, if JOB_STATE_ = 2, the User Task was cancelled because it was timed out, if JOB_STATE_ = 3, the User Task was manually deleted before it was timed out (I can say that because the User Taskā€™is Process was deleted manually).

Eh. Any solution for me?

  • I need STATE_ information in ACT_HI_PROCINST table, I cannot get this information with Java API.
  • I want to be able to query the ACT_HI_OP_LOG table with custom SQL, but the createNativeHistoricOperationLogQuery method is not implemented in the History Service, so I canā€™t quite get the case I said.
  • When I query the ACT_HI_JOB_LOG table with History Service, there is no JOB_STATE_ information in the returned HistoricJobLog instance.

Thanks.

Hi @qube,

Could you not find any historic process instance?
Or do you find a historic process instance but without state?

Which version of Camunda do you use?
Which history level do you have enabled?

Cheers, Ingo

1 Like

Hi @Ingo_Richtsmeier Ingo,

Iā€™m very sorry that I think I misunderstood youā€¦
I run it like:

HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
logger.info("historicProcessInstance: " + historicProcessInstance);

historicProcessInstance print:

historicProcessInstance: HistoricProcessInstanceEntity[businessKey=default, startUserId=null, superProcessInstanceId=null, rootProcessInstanceId=cfa945f6-f54c-11ed-9b42-a08069929d2b, superCaseInstanceId=null, deleteReason=null, durationInMillis=8417, startTime=Thu May 18 10:22:56 TRT 2023, endTime=Thu May 18 10:23:05 TRT 2023, removalTime=null, endActivityId=null, startActivityId=StartEvent_1, id=cfa945f6-f54c-11ed-9b42-a08069929d2b, eventType=null, executionId=null, processDefinitionId=TimerProcess_0qc1smu:1:edf2a7b9-f543-11ed-9c4a-a08069929d2b, processInstanceId=cfa945f6-f54c-11ed-9b42-a08069929d2b, tenantId=null]

It doesnā€™t contain the state information at first look, but:

String historicProcessInstanceState = historicProcessInstance.getState();
logger.info("historicProcessInstanceState: " + historicProcessInstanceState);

I can actually get the process instance state:

historicProcessInstanceState: EXTERNALLY_TERMINATED

This should work, I guess. Iā€™ll look more into it.

Thanks a lot!

Actually, what Iā€™m trying to understand is whether the User Task was deleted because of timeout or because the Process was deleted. In this case, I think it is not enough to look at the Process state information only. It will not always give the right result. Letā€™s look at the following BPMN:

ā€œuser task 1ā€ was deleted by the Engine as it was indeed timeout. While the Process was waiting in ā€œuser task 2ā€, I manually deleted Process. So, if I look at the Process state, itā€™s EXTERNALLY_TERMINATED, but no, ā€œuser task 1ā€ was actually deleted because it was timeout, not because I manually deleted the Process.

I really need to capture the status of User Task while it is timeout. As far as I can see, there is no STATE information for Task as in Process.

What can i do for this requirement, then?

  1. Can we make to run EVENTNAME_TIMEOUT in TaskListener? For some reason, this event is not triggered when any User Task timeouts.

Related post I found in the forum: How to add task listener with parseUserTask for user task timeouts

  1. While the User Task is timeout, I can create a local variable related to this situation and then just look at this local variable? (Assuming the deletion of a User Task because of the timeout and the Timer boundary event execution are in the same transaction.)

Maybe related other forum posts:
Get User Task of Timer Boundary Event - #4 by thorben
Examples of 7.12 webinar - #2 by thorben
Get origin of a Boundary Event - #3 by joseluisluri
Lookup Timer Due date for a Boundary event from Task Create Listener - #2 by Philipp_Ossler

  1. Your other elegant solution!

I just have to use Parse Listeners to catch when User Task is timeout. For the user side (designing the Process in Camunda Modeler), if you are using a Timer boundary event connected to any User Task, then you have to add the following bla bla bla should not be the solution.

Thanks.

Hi @qube,

you can check the historic activities for this process instance. If they include the timer event, then the first user task is timed out.

Hope this helps, Ingo

1 Like

Hi @Ingo_Richtsmeier

Actually, I tried this before. I can correlate historic Task and Job records with the executionId. Running it like this:

HistoricTaskInstance historicTaskInstance = historyService.createHistoricTaskInstanceQuery().taskId(taskId).singleResult();
String executionId = historicTaskInstance.getExecutionId();
List<HistoricJobLog> historicJobLogs = historyService.createHistoricJobLogQuery().executionIdIn(executionId).list();
for (HistoricJobLog historicJobLog:historicJobLogs){
logger.info("historicJobLog: " + historicJobLog);
}

It prints historicJobLog instances with this:

historicJobLog: HistoricJobLogEventEntity[id=cfaacca1-f54c-11ed-9b42-a08069929d2b, eventType=null, executionId=cfaaa58e-f54c-11ed-9b42-a08069929d2b, processDefinitionId=TimerProcess_0qc1smu:1:edf2a7b9-f543-11ed-9c4a-a08069929d2b, processInstanceId=cfa945f6-f54c-11ed-9b42-a08069929d2b, rootProcessInstanceId=cfa945f6-f54c-11ed-9b42-a08069929d2b, removalTime=null]

No job state information in the logs. Also, there is no such method like historicJobLog.getState() as in historicProcessInstance above. Because, as far as I can see JobState can have different values. For example, it means ā€œsuccessfulā€ if JobState = 2 or means ā€œdeletedā€ if JobState = 3. If JobState = 2, I can say that the Task timed out indeed. However, if JobState = 3, the Timer boundary event connected to the Task was deleted so the Task by Process deletion before it could run.

In other words, if there is any historic Job record belonging to a Task which has a Timer boundary event, we cannot say that this Task has definitely timed out.

Do you know how we can get JobState information from historicalJobLog instance records of the Task?

Thanks for your effort so far! :slight_smile:

By the way, @Ingo_Richtsmeier what do you think about item 1 above?

I could never make it to run. To know the Taskā€™s timeout state while the Task was really being timed out in the Process would be more proper solution for me. Then Iā€™ll be in the Taskā€™s timeout moment, and I can leave a variable maybe in the Process or the Taskā€™s local about this timeout event, and then itā€™ll be enough to just look at this variable value to understand if the Task was timed out or not.

Do you know how can I trigger TaskListenerā€™s Timeout event?

Thanks.

Why not just add a script task between the timer and User Task 2 that sets a process level variable?
It sounds like ā€œRecord User Task 1 Timeoutā€ is a business flow requirement, so modeling it would be a valid use-case.

1 Like

Hi @GotnOGuts

Actually, I can handle this need by adding a few extra things on the Modeler, but as I said above, I shouldnā€™t make the user do anything extra in the Modeler for the timeouts. User will only draw User Task and Timer boundary event on User Task in the Modeler, thatā€™s all. In this case, I need to catch the timeout event completely in the background. I think, Camunda Parse listeners were created for such needs.

Thank you for your answer, @GotnOGuts

Hi @qube,

The timeout event (Delegation Code | docs.camunda.org) is a hidden attached timer boundary event. It is triggered when the due date is reached.

Hope this helps, Ingo

So, you are saying that you are looking in the wrong place, you should use parseBoundaryTimerEventDefinition instead of parseUserTask?

Sorry, but Iā€™m just trying to understand the internals.

Thanks!