ADF Prematurely Terminated Task Flows

In this post I'll describe some interesting side effects on task flow transactions if a task flow terminates/finalizes earlier than expected.  To demonstrate this we'll use the following application built in JDev

The app based on the Oracle HR schema renders a single page:

Before describing the prematurely terminating task flow behavior let's describe the characteristics of the application first:

1) The app makes use of 2 independent view objects DepartmentsView and EmployeesView.

2) The overall page is Main.jspx which has an embedded region calling the departments-task-flow which itself has another embedded region calling the employees-task-flow.  The departments task flow has the editable departments form and navigation buttons to walk the departments, the employees task flow the table of relating employees for the department.

3) As the user navigates between departments records, the department ID is passed to the employees task flow which calls an ExecuteWithParams activity then displays the resulting employees.  The employees task flow binding has it's refresh = ifNeeded and the associated region has partialTriggers on the navigation buttons, ensuring the employees task flow is updated as the user navigates the departments using the supplied buttons.

4) Of particular interest, the departments-task-flow is using the Always Begin New Transaction task flow transaction behaviour and has an isolated data control scope:

And the employees-task-flow is using Use Existing Transaction if Possible and a shared data control scope:

If you run this application and navigate amongst the departments using the navigation buttons, the application works as expected.  Both the departments and employees records move onto the next departments ID after each button click.

In the application I've also added some ADFLoggers which help capture the current behaviour:

<TaskFlowBean> <taskFlowInit> Task flow /WEB-INF/departments-task-flow.xml#departments-task-flow initialized
<AppModuleImpl> <create> AppModuleImpl created as ROOT AM
<AppModuleImpl> <prepareSession> AppModuleImpl prepareSession() called as ROOT AM
<AppModuleImpl> <create> AppModuleImpl created as NESTED AM under AppModule
<TaskFlowBean> <taskFlowInit> Task flow /WEB-INF/employees-task-flow.xml#employees-task-flow initialized
<TaskFlowBean> <taskFlowFinalizer> Task flow /WEB-INF/employees-task-flow.xml#employees-task-flow finalized
<TaskFlowBean> <taskFlowInit> Task flow /WEB-INF/employees-task-flow.xml#employees-task-flow initialized

As the page first renders we can see the departments task flow initialized then the associated application module created and prepared to serve the data for the DepartmentsView.  Subsequently we can see the employees task flow initialized.  We don't see a new application module as the employees task flow is sharing the data control. From here each time we step onto another departments record, we'll see the employees task flow finalizer called, then the employees task flow intializer called.  This occurs because the ifNeeded refresh property on the employees task flow is restarting the task flow each time the department ID is changed.

This restarting of the task flow is what I coin the "premature termination" of the task flow.  Essentially the calling parent has forced the framework to terminate the employees task flow, rather than the task flow gracefully exiting via a task flow return activity.

At the moment though, this is still a "So what?" scenario.  What do we care?  Everything appears to work?

Let's change the setup slightly to demonstrate something unexpected.  Return to the application and set the departments task flow transaction option to <No Controller Transaction> (and leave the data control scope option = isolated/unselected):

Rerun the application.  Note now when it runs and we press one of the navigation buttons, besides a screen refresh nothing happens.  We don't walk onto a new departments record in the departments task flow, and we don't see the associated employees for the expected new department.  The application seems stuck on the first department.

A clue to what's going on occurs in the logs:

<TaskFlowBean> <taskFlowInit> Task flow /WEB-INF/departments-task-flow.xml#departments-task-flow initialized
<AppModuleImpl> <create> AppModuleImpl created as ROOT AM
<AppModuleImpl> <prepareSession> AppModuleImpl prepareSession() called as ROOT AM
<TaskFlowBean> <taskFlowInit> Task flow /WEB-INF/employees-task-flow.xml#employees-task-flow initialized
<TaskFlowBean> <taskFlowFinalizer> Task flow /WEB-INF/employees-task-flow.xml#employees-task-flow finalized
<AppModuleImpl> <beforeRollback> AppModuleImpl beforeRollback() called as ROOT AM
<TaskFlowBean> <taskFlowInit> Task flow /WEB-INF/employees-task-flow.xml#employees-task-flow initialized

Notice in between the last employees task flow finalizer/initializer pair we see the application module has performed a rollback.  This partially explains the behaviour we're seeing.  When a rollback is issued, a rollback resets the current row indicators for all view objects attached to the application module.  This is why we can't move onto another record.

But why is the rollback called in this scenario?

The answer is wrapped around the concept of task flow transactions and the associated data control frame.

In the first scenario the departments task flow initiated the task flow transaction and associated data control frame.  In turn the employees task flow joined the departments transaction and data control frame.  Only the initiator of a task flow transaction can commit/rollback the overall transaction associated with the data control frame.  In the case where the employees task flow is prematurely terminated, as it a secondary citizen in the overall task flow transaction, the framework leaves the initiator of the task flow to tidy up the transaction. No automatic rollback occurs on the work done by the secondary task flow.

In the second scenario the departments task flow is not initiating the task flow transaction as it's chosen the <No Controller Transaction> option.  Instead the employees task flow initiates the transaction because when the Use Existing Transaction if Possible option finds no transaction open it defaults to the equivalent of Always Begin New Transaction behaviour.

In remembering the initiator of a task flow transaction can commit/rollback the overall transaction, the framework automatically rolls back the employees task flow and this is the cause of the behaviour we're seeing.  Even though the departments task flow is using <No Controller Transaction> this doesn't mean the underlying view object doesn't participate in a transaction, it just doesn't participate in a task flow transaction (which is an abstraction sitting about the data control transactions).  As the two task flows share data controls, there is only a single application module shared by both task flows, a rollback from one task flow will result in a roll back in both task flows.

The solution? Either revert back to the original settings where the bookings task flow uses Always Begin New Transaction, or alternatively use an isolated data control scope for the employees task flow.

Nice explanation Chris! In this complex area it's easy to get lost and not appreciate the subtle or not so subtle side effects of seeming innocent decisions...

Posted by Duncan on April 20, 2012 at 11:38 AM WST #

Nice article, It clarifies the grey area regarding task flow transactions and shared data control usage.

Posted by Ramandeep Nanda on April 25, 2012 at 10:10 PM WST #

Thanks for your information.
Gracias por la informacion prestada. ;)

Posted by guest on June 20, 2012 at 09:21 PM WST #

Learnt more concepts on task flow transactions and shared data control usage. Thanks

Posted by Deepak Siddappa on June 26, 2012 at 06:51 PM WST #

Post a Comment:
Comments are closed for this entry.

Not a selfie
Chris Muir
Oracle Mobility and Development Tools Product Manager

The views expressed on this blog are my own and do not necessarily reflect the views of Oracle.



« July 2016