OdeToCode IC Logo

Handling Faults in Windows Workflow Custom Activities

Friday, August 24, 2007

Here is one I had to track down recently.

The code in the following activity is trivial, but FaultyActivity represents any activity that might throw an exception during Execute and overrides HandleFault (which the WF runtime will schedule to run when it catches the exception tossed up by Execute).

public partial class FaultyActivity: Activity
{
   
public FaultyActivity()
   {
      InitializeComponent();
   }

    
protected override ActivityExecutionStatus Execute(
                        
ActivityExecutionContext Context)
    {
        
// something went wrong
        throw new InvalidOperationException();
    }

    
protected override ActivityExecutionStatus HandleFault(
                        
ActivityExecutionContext context,
                        
Exception exception)
    {
        
// something still went wrong
        throw new InvalidOperationException();
    }
}

The problem is that once an activity enters the Faulting state, it can only transition to Closed or back to Faulting. If another exception escapes during fault handling, the activity transitions back into the Faulting state, and WF schedules HandleFault to execute again - it’s an infinite loop if the exception continues to occur.

Adding some tracing to the app config file..

<system.diagnostics>
    <
switches>
        <
add name="System.Workflow.Runtime" value="All" />
    </
switches>
</
system.diagnostics>

.. confirms the problem (abridged version):

Activity Status Change - Activity: faultyActivity1 Old:Initialized; New:Executing
Scheduling entry: ActivityOperation((1)faultyActivity1, Execute)
Execute of Activity faultyActivity1 threw System.InvalidOperationException
Activity Status Change - Activity: faultyActivity1 Old:Executing; New:Faulting
Scheduling entry: ActivityOperation((1)faultyActivity1, HandleFault)
Compensate of Activity faultyActivity1 threw System.InvalidOperationException
Activity Status Change - Activity: faultyActivity1 Old:Faulting; New:Faulting
Scheduling entry: ActivityOperation((1)faultyActivity1, HandleFault)
Compensate of Activity faultyActivity1 threw System.InvalidOperationException
Activity Status Change - Activity: faultyActivity1 Old:Faulting; New:Faulting
Scheduling entry: ActivityOperation((1)faultyActivity1, HandleFault)
Compensate of Activity faultyActivity1 threw System.InvalidOperationException ...

The sinister aspect to this behavior is that if enough workflows get into the infinite faulting loop, the threadpool becomes starved and the entire application just sits and spins with no work getting done.

Moral of the story – never let an exception escape from HandleFault.