EventHandlingScopeActivity not calling Unsubscribe when faulting

J

jmr

I think I've discovered a bug in Windows Workflow Foundation's
EventHandlingScopeActivity; I cannot find a WWF specific newsgroup so I am
posting here.

The upshot is that when an EventHandlingScopeActivity's event handlers
faults and is not handled, the EventHandlingScopeActivity does not call
IEventActivity.Unsubscribe on the event activities it contains. I ran into
this while developing and testing my own IEventActivity activities, and then
verified the bug by duplicating it with the DelayActivity.

Here is a program to reproduce the problem. It consists of a main program
that responds to the workflow suspended event to capture the number of
outstanding queues, and a workflow definition that has an EventHandlingScope
activity with a main activity of a delay of 4 seconds and two other
EventDriven activities with delays, one of which has a Throw. Finally, the
workflow suspends itself so that the main program can capture the queues.

This program should execute without exception, but because DelayActivity's
IEventActivity.Unsubscribe method is never called it leaves some
WorkflowQueue queues hanging around.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Workflow.Runtime;
using System.Workflow.Runtime.Hosting;
using System.Workflow.ComponentModel;
using System.Workflow.Activities;
using System.ComponentModel;

namespace TestEventHandlingScopeFaulting
{

class Program
{
static void Main (string [] args)
{
try
{
using (WorkflowRuntime workflowRuntime = new WorkflowRuntime
())
{
AutoResetEvent waitHandle = new AutoResetEvent (false);
int queueCount = 0;

// Record the count of queues in the workflow.
workflowRuntime.WorkflowSuspended += (sender, eventArgs)
=>
{
var workflowQueueData =
eventArgs.WorkflowInstance.GetWorkflowQueueData ();
queueCount = workflowQueueData.Count;
waitHandle.Set ();
};

var dontThrowArguments = new Dictionary<string, object>
();
dontThrowArguments ["ShouldThrow"] = false;

var doThrowArguments = new Dictionary<string, object> ();
doThrowArguments ["ShouldThrow"] = true;

var instance1 = workflowRuntime.CreateWorkflow (
typeof
(TestEventHandlingScopeFaulting.EventHandlingScopeWorkflow),
dontThrowArguments);
instance1.Start ();

waitHandle.WaitOne ();

if (queueCount != 0)
{
throw new Exception (String.Format (
"Expected a queue count of zero, but was {0}",
queueCount));
}

var instance2 = workflowRuntime.CreateWorkflow (
typeof
(TestEventHandlingScopeFaulting.EventHandlingScopeWorkflow),
doThrowArguments);
instance2.Start ();

waitHandle.WaitOne ();

if (queueCount != 0)
{
throw new Exception (String.Format (
"Expected a queue count of zero, but was {0}",
queueCount));
}

}
}
catch (Exception e)
{
Console.WriteLine ("Caught exception: {0}", e);
}
}
}
}



namespace TestEventHandlingScopeFaulting
{
public sealed partial class EventHandlingScopeWorkflow :
SequentialWorkflowActivity
{
public EventHandlingScopeWorkflow ()
{
InitializeComponent ();
}

public static DependencyProperty ShouldThrowProperty =
DependencyProperty.Register ("ShouldThrow", typeof (bool), typeof
(EventHandlingScopeWorkflow));

[DescriptionAttribute ("ShouldThrow")]
[CategoryAttribute ("ShouldThrow Category")]
[BrowsableAttribute (true)]
[DesignerSerializationVisibilityAttribute
(DesignerSerializationVisibility.Visible)]
public bool ShouldThrow
{
get
{
return ((bool) (base.GetValue
(EventHandlingScopeWorkflow.ShouldThrowProperty)));
}
set
{
base.SetValue
(EventHandlingScopeWorkflow.ShouldThrowProperty, value);
}
}

public static DependencyProperty QueueNamesCountProperty =
DependencyProperty.Register ("QueueNamesCount", typeof (int), typeof
(EventHandlingScopeWorkflow));

[DescriptionAttribute ("QueueNamesCount")]
[CategoryAttribute ("QueueNamesCount Category")]
[BrowsableAttribute (true)]
[DesignerSerializationVisibilityAttribute
(DesignerSerializationVisibility.Visible)]
public int QueueNamesCount
{
get
{
return ((int) (base.GetValue
(EventHandlingScopeWorkflow.QueueNamesCountProperty)));
}
set
{
base.SetValue
(EventHandlingScopeWorkflow.QueueNamesCountProperty, value);
}
}

void IfElseCondition (object sender, ConditionalEventArgs e)
{
e.Result = this.ShouldThrow;
}

}

}


namespace TestEventHandlingScopeFaulting
{
partial class EventHandlingScopeWorkflow
{
#region Designer generated code

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
[System.Diagnostics.DebuggerNonUserCode]
private void InitializeComponent ()
{
this.CanModifyActivities = true;
System.Workflow.Activities.CodeCondition codecondition1 = new
System.Workflow.Activities.CodeCondition ();
this.throwActivity1 = new
System.Workflow.ComponentModel.ThrowActivity ();
this.ifElseBranchActivity1 = new
System.Workflow.Activities.IfElseBranchActivity ();
this.ifElseActivity1 = new
System.Workflow.Activities.IfElseActivity ();
this.delayActivity1 = new
System.Workflow.Activities.DelayActivity ();
this.delayActivity2 = new
System.Workflow.Activities.DelayActivity ();
this.faultHandlerActivity1 = new
System.Workflow.ComponentModel.FaultHandlerActivity ();
this.eventDrivenActivity2 = new
System.Workflow.Activities.EventDrivenActivity ();
this.eventDrivenActivity1 = new
System.Workflow.Activities.EventDrivenActivity ();
this.faultHandlersActivity1 = new
System.Workflow.ComponentModel.FaultHandlersActivity ();
this.eventHandlersActivity1 = new
System.Workflow.Activities.EventHandlersActivity ();
this.delayActivity3 = new
System.Workflow.Activities.DelayActivity ();
this.suspendActivity1 = new
System.Workflow.ComponentModel.SuspendActivity ();
this.eventHandlingScopeActivity1 = new
System.Workflow.Activities.EventHandlingScopeActivity ();
//
// throwActivity1
//
this.throwActivity1.FaultType = typeof
(System.InvalidOperationException);
this.throwActivity1.Name = "throwActivity1";
//
// ifElseBranchActivity1
//
this.ifElseBranchActivity1.Activities.Add (this.throwActivity1);
codecondition1.Condition += new
System.EventHandler<System.Workflow.Activities.ConditionalEventArgs>
(this.IfElseCondition);
this.ifElseBranchActivity1.Condition = codecondition1;
this.ifElseBranchActivity1.Name = "ifElseBranchActivity1";
//
// ifElseActivity1
//
this.ifElseActivity1.Activities.Add (this.ifElseBranchActivity1);
this.ifElseActivity1.Name = "ifElseActivity1";
//
// delayActivity1
//
this.delayActivity1.Name = "delayActivity1";
this.delayActivity1.TimeoutDuration = System.TimeSpan.Parse
("00:00:02");
//
// delayActivity2
//
this.delayActivity2.Name = "delayActivity2";
this.delayActivity2.TimeoutDuration = System.TimeSpan.Parse
("03:00:00");
//
// faultHandlerActivity1
//
this.faultHandlerActivity1.FaultType = typeof (System.Exception);
this.faultHandlerActivity1.Name = "faultHandlerActivity1";
//
// eventDrivenActivity2
//
this.eventDrivenActivity2.Activities.Add (this.delayActivity1);
this.eventDrivenActivity2.Activities.Add (this.ifElseActivity1);
this.eventDrivenActivity2.Name = "eventDrivenActivity2";
//
// eventDrivenActivity1
//
this.eventDrivenActivity1.Activities.Add (this.delayActivity2);
this.eventDrivenActivity1.Name = "eventDrivenActivity1";
//
// faultHandlersActivity1
//
this.faultHandlersActivity1.Activities.Add
(this.faultHandlerActivity1);
this.faultHandlersActivity1.Name = "faultHandlersActivity1";
//
// eventHandlersActivity1
//
this.eventHandlersActivity1.Activities.Add
(this.eventDrivenActivity1);
this.eventHandlersActivity1.Activities.Add
(this.eventDrivenActivity2);
this.eventHandlersActivity1.Name = "eventHandlersActivity1";
//
// delayActivity3
//
this.delayActivity3.Name = "delayActivity3";
this.delayActivity3.TimeoutDuration = System.TimeSpan.Parse
("00:00:04");
//
// suspendActivity1
//
this.suspendActivity1.Name = "suspendActivity1";
//
// eventHandlingScopeActivity1
//
this.eventHandlingScopeActivity1.Activities.Add
(this.delayActivity3);
this.eventHandlingScopeActivity1.Activities.Add
(this.eventHandlersActivity1);
this.eventHandlingScopeActivity1.Activities.Add
(this.faultHandlersActivity1);
this.eventHandlingScopeActivity1.Name =
"eventHandlingScopeActivity1";
//
// EventHandlingScopeWorkflow
//
this.Activities.Add (this.eventHandlingScopeActivity1);
this.Activities.Add (this.suspendActivity1);
this.Name = "EventHandlingScopeWorkflow";
this.CanModifyActivities = false;

}

#endregion

private FaultHandlerActivity faultHandlerActivity1;
private FaultHandlersActivity faultHandlersActivity1;
private SuspendActivity suspendActivity1;
private DelayActivity delayActivity3;
private DelayActivity delayActivity1;
private DelayActivity delayActivity2;
private EventDrivenActivity eventDrivenActivity2;
private EventDrivenActivity eventDrivenActivity1;
private EventHandlersActivity eventHandlersActivity1;
private ThrowActivity throwActivity1;
private IfElseBranchActivity ifElseBranchActivity1;
private IfElseActivity ifElseActivity1;
private EventHandlingScopeActivity eventHandlingScopeActivity1;

}
}
 
J

jmr

The consequences of this bug are:

1. A memory leak whenever a task repeatedly faults from within an
EventHandlingScopeActivity's EventDrivenActivity and there are multiple
EventDrivenActivity instances in the EventHandlingScope.

2. Some subscriptions that rely on external systems will not properly
update the external system unless the Activity unsubscribes with the remote
system in Uninitialize, which IS called, but is inappropriate in some
contexts (such as needing access to other activities, which requires an
ActivityExecutionContext).
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top