We have seen in several customer scenarios where a Parent BPEL process calls a child BPEL process asynchronously. If child process runs into any kind of business or system fault, fault handler sends callback to parent process with failure information. Please note, since parent had invoked child process asynchronously, child can't throw fault back to parent. Callback is only mechanism to inform parent about the failure in child process execution during asynchronous interaction. This pattern works fine if transaction of child process is not getting rolled back. If child process encounters some error where underlying JTA transaction is marked for rollback, callback may not reach parent process if parent and child are collocated. This is because, callback invoke from child to parent will use the child's JTA transaction to save callback in dehydration store, but since child's transaction is in rollback state, callback message information would be rolled back from dehydration store after JTA transaction rolls back.
Second issue is while trying to terminate an instance after it has encountered error which results into transaction rollback. Since terminate state update of instance in dehydration store will be rolled back with transaction, any attempt to execute terminate in fault handler will be futile.
We are recommending a pattern that could help resolve both of above problems.
Please look at the flow:
In above business flow, you could see that we have Controller BPEL process (aka parent process) which spawns a Slave BPEL process (aka child process) to do some work. Slave BPEL process encounters transaction rollback due to some reason. For example, Slave process might invoke a stored procedure on Oracle database thru DB adapter and if stored procedure runs for more than JTA transaction timeout, transaction would be marked for rollback, or if stored procedure encounters some error, Database will mark transaction for rollback.
To handle this kind of scenario, we recommend to model child process like below:
Here Slave BPEL process has a EventHandler with onMessage that is listening for events with Operation name that indicate failure and instance needs to be terminated. We also have a dehydrate activity to ensure event handler subscription is properly saved before we move ahead with instance. If your BPEL process already has some kind of dehydrate before transaction rollback could be encountered, you do not need to explicitly add dehydrate.
That operation to indicate to Slave process that something is wrong and instance should be terminated is in our case "kill" which is defined inside wsdl of Slave as below :
Event handler is listening for "kill" event as below in Slave.bpel process :
Inside MAIN flow of Slave, if any error is encountered that indicate transaction might be getting rolled back, instance of Slave process calls another Synchronous BPEL process KillSyncProcess which would initiate procedure for terminate of Slave instance.
KillSyncProcess has "bpel.config.transaction" property set to "requiresNew". This is to ensure KillSyncProcess executes inside a brand new transaction and not participate in Slave's transaction which is already marked for rollback. Please see how this property is defined :
KillSyncProcess's "Receive" bpel activity takes input as status information that needs to be send back to Controller. It assigns it to appropriate internal variable and sends a callback to Slave process with operation "kill" for which Slave's event handler is listening. Please see below how KillSyncProcess is defined :
Slave process would receive Event "kill" with status information in new transaction. Event handler of Slave would process Event "kill" in new transaction which would send Callback to Controller with all the information (such as error code or error message) that Controller needs to process fault from Child process. Event handler of Slave then terminate Slave process's instance thru "Terminate" bpel activity.
Here KillSyncProcess is transient sync process, so inMemoryOptimization=true property could be used which would execute KillSyncProcess inside memory without any instance save overhead.
This whole project is uploaded for reference here.