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;
}
}
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;
}
}