Sunday, March 31, 2013

Notes on Activiti async and transaction internals

Having the business requirement for each business step to be a separate transaction I 've started using Activiti asynchronous continuations as a way to demarcate transactions in the desired way. Under Jboss AS 7, JTA transactions and stuff, I 've probably made a major mistake to place a CDI listener in the first transition of a subprocess in order to demote all the variables and attachments of the parent process to the subprocess. That subprocess was the first action of the top-level process. The second mistake was that, the call activity of the top-level process was not made asynchronous.

In this demoting-variables methods, the need of also demoting parent process attachments revealed some problems. To my understanding, RuntimeService.startProcessBy*() methods return a ProcessInstance entity when the process has reached a wait state or an async task. That operation is a new transaction initiated by JtaTransactionInterceptor, if no transaction is active at the moment, or a joined transaction otherwise. In my case, the startProcessByKey() method was called by an MDB so no new transaction was initiated. Finally, I was getting the ProcessInstance entity when the process has reached the first async service task in the subprocess.

After that, in the same transactional method, I was trying to store some attachments to the process having in mind that they will be demoted to every subprocess. However, due to omitting setting the first call activity to async, the attachments were pinned to the process but the demote listener wasn't finding anything. Setting that call activity to async, startProcessByKey() is starting the process which immediately writes an entry to ACT_RU_JOB table for the job executor to find it and returns the ProcessInstance entity. Note that at this point no transaction has been committed, so the job executor, which initiates its own transaction, cannot find anything to execute and advance the process forward. Then I am able to store the attachments and be sure that the attachments will be propagated.

Another misunderstanding was the false assumption that using a CDI-injected Activiti service (e.g. TaskService, RuntimeService) means that a new transaction will begin. As I 've seen in the JtaTransactionInterceptor that was not the case and it depends on the transactional context within which a method is called. Initially, to my understanding, being under JTA, it doesn't make any difference to store a variable through DelegateExecution or through RuntimeService. That is probably not the case, because RuntimeService searches for an ExecutionEntity which is not present at the point. So the most safe way is to get/set variables through DelegateExecution at this point.

The final misunderstanding was the use of a listener in the first transition of a subprocess; That I didn't understand from the first place is that Activiti stores entries in ACT_RU_EXECUTION table when it reaches a wait state/async task and it is about to suspend and wait for the user or the job executor. So in the first transition listener, when I 'm about to demote the variables to the subprocess, the respective subprocess execution entity doesn't exist in the transactional context. Trying an execution query to list all execution entities showed that only the parent process execution entity was in the transactional context. Attaching variables to that DelegateExecution entity didn't reveal the problem as it wasn't searching for an execution entity, but demoting attachments through TaskService really did.

1 comment: