1 // Copyright (c) Microsoft Corp., 2004. All rights reserved.
2 #region Using directives
8 using System.Threading;
9 using System.Reflection;
10 using System.Collections;
11 using System.Diagnostics;
12 using System.Runtime.Remoting;
13 using System.Collections.Generic;
14 using System.Collections.ObjectModel;
15 using System.Runtime.Serialization;
16 using System.Workflow.Runtime;
17 using System.Workflow.Runtime.Hosting;
18 using System.Runtime.InteropServices;
19 using System.Runtime.Remoting.Channels;
20 using System.Workflow.ComponentModel;
21 using System.Workflow.ComponentModel.Compiler;
22 using System.Workflow.ComponentModel.Design;
23 using System.Workflow.ComponentModel.Serialization;
24 using System.Runtime.Serialization.Formatters;
25 using System.Runtime.Remoting.Channels.Ipc;
26 using System.Configuration;
27 using System.Security.Permissions;
28 using System.Globalization;
29 using Microsoft.Win32;
30 using System.Security.AccessControl;
31 using System.Security.Principal;
34 namespace System.Workflow.Runtime.DebugEngine
36 internal static class RegistryKeys
38 internal static readonly string ProductRootRegKey = @"SOFTWARE\Microsoft\Net Framework Setup\NDP\v3.0\Setup\Windows Workflow Foundation";
39 internal static readonly string DebuggerSubKey = ProductRootRegKey + @"\Debugger";
42 [Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")]
43 public sealed class DebugController : MarshalByRefObject
47 private Guid programId;
48 private string hostName;
49 private int attachTimeout;
50 private ProgramPublisher programPublisher;
51 private DebugControllerThread debugControllerThread;
52 private Timer attachTimer;
53 private WorkflowRuntime serviceContainer;
54 private IpcChannel channel;
55 private IWorkflowDebugger controllerConduit;
56 private bool isZombie;
57 private bool isAttached;
58 private ManualResetEvent eventConduitAttached;
59 private InstanceTable instanceTable;
60 private Dictionary<Type, Guid> typeToGuid;
61 private Dictionary<byte[], Guid> xomlHashToGuid;
62 bool isServiceContainerStarting;
63 private const string rootExecutorGuid = "98fcdc7a-8ab4-4fb7-92d4-20f437285729";
64 private object eventLock;
65 private object syncRoot = new object();
66 private static readonly string ControllerConduitTypeName = "ControllerConduitTypeName";
70 #region Security related methods
71 private delegate void ExceptionNotification(Exception e);
73 internal static void InitializeProcessSecurity()
75 // Spawn off a separate thread to that does RevertToSelf and adjusts DACLs.
76 // This is because RevertToSelf terminates client impersonation on the thread
77 // that calls it. We do not want to change that on the current thread when
78 // the runtime is hosted inside ASP.net for example.
79 Exception workerThreadException = null;
80 ProcessSecurity processSecurity = new ProcessSecurity();
81 Thread workerThread = new Thread(new ThreadStart(processSecurity.Initialize));
83 processSecurity.exceptionNotification += delegate(Exception e)
85 workerThreadException = e;
88 workerThread.Start(); workerThread.Join();
89 if (workerThreadException != null)
90 throw workerThreadException;
93 private class ProcessSecurity
95 internal ExceptionNotification exceptionNotification;
97 internal void Initialize()
101 // This is needed if the thread calling the method is impersonating
102 // a client call (ASP.net hosting scenarios).
103 if (!NativeMethods.RevertToSelf())
104 Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
106 // Get the DACL for process token. Add TOKEN_QUERY permissions for the Administrators group.
107 // Set the updated DACL for process token.
108 RawAcl tokenDacl = GetCurrentProcessTokenDacl();
109 CommonAce adminsGroupAceForToken = new CommonAce(AceFlags.None, AceQualifier.AccessAllowed, NativeMethods.TOKEN_QUERY, new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null), false, null);
110 int i = FindIndexInDacl(adminsGroupAceForToken, tokenDacl);
112 tokenDacl.InsertAce(i, adminsGroupAceForToken);
113 SetCurrentProcessTokenDacl(tokenDacl);
117 // Communicate any exceptions from this thread back to the thread
119 if (exceptionNotification != null)
120 exceptionNotification(e);
124 private RawAcl GetCurrentProcessTokenDacl()
126 IntPtr hProcess = IntPtr.Zero;
127 IntPtr hProcessToken = IntPtr.Zero;
128 IntPtr securityDescriptorPtr = IntPtr.Zero;
132 hProcess = NativeMethods.GetCurrentProcess();
134 if (!NativeMethods.OpenProcessToken(hProcess, NativeMethods.TOKEN_ALL_ACCESS, out hProcessToken))
135 Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
137 // Get security descriptor associated with the kernel object, read the DACL and return
138 // that to the caller.
141 NativeMethods.GetKernelObjectSecurity(hProcessToken, NativeMethods.SECURITY_INFORMATION.DACL_SECURITY_INFORMATION, IntPtr.Zero, 0, out returnLength);
142 int lasterror = Marshal.GetLastWin32Error(); //#pragma warning disable 56523 doesnt recognize 56523
144 securityDescriptorPtr = Marshal.AllocCoTaskMem((int)returnLength);
146 if (!NativeMethods.GetKernelObjectSecurity(hProcessToken, NativeMethods.SECURITY_INFORMATION.DACL_SECURITY_INFORMATION, securityDescriptorPtr, returnLength, out returnLength))
147 Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
149 byte[] sdBytes = new byte[returnLength];
150 Marshal.Copy(securityDescriptorPtr, sdBytes, 0, (int)returnLength);
152 RawSecurityDescriptor rawSecurityDescriptor = new RawSecurityDescriptor(sdBytes, 0);
154 return rawSecurityDescriptor.DiscretionaryAcl;
158 if (hProcess != IntPtr.Zero && hProcess != (IntPtr)(-1))
159 if (!NativeMethods.CloseHandle(hProcess))
160 Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
162 if (hProcessToken != IntPtr.Zero)
163 if (!NativeMethods.CloseHandle(hProcessToken))
164 Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
166 if (securityDescriptorPtr != IntPtr.Zero)
167 Marshal.FreeCoTaskMem(securityDescriptorPtr);
170 private void SetCurrentProcessTokenDacl(RawAcl dacl)
172 IntPtr hProcess = IntPtr.Zero;
173 IntPtr hProcessToken = IntPtr.Zero;
174 IntPtr securityDescriptorPtr = IntPtr.Zero;
177 hProcess = NativeMethods.GetCurrentProcess();
179 if (!NativeMethods.OpenProcessToken(hProcess, NativeMethods.TOKEN_ALL_ACCESS, out hProcessToken))
180 Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
182 // Get security descriptor associated with the kernel object and modify it.
185 NativeMethods.GetKernelObjectSecurity(hProcessToken, NativeMethods.SECURITY_INFORMATION.DACL_SECURITY_INFORMATION, IntPtr.Zero, 0, out returnLength);
186 int lasterror = Marshal.GetLastWin32Error(); //#pragma warning disable 56523 doesnt recognize 56523
188 securityDescriptorPtr = Marshal.AllocCoTaskMem((int)returnLength);
190 if (!NativeMethods.GetKernelObjectSecurity(hProcessToken, NativeMethods.SECURITY_INFORMATION.DACL_SECURITY_INFORMATION, securityDescriptorPtr, returnLength, out returnLength))
191 Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
193 byte[] sdBytes = new byte[returnLength];
194 Marshal.Copy(securityDescriptorPtr, sdBytes, 0, (int)returnLength);
196 RawSecurityDescriptor rawSecurityDescriptor = new RawSecurityDescriptor(sdBytes, 0);
197 rawSecurityDescriptor.DiscretionaryAcl = dacl;
199 sdBytes = new byte[rawSecurityDescriptor.BinaryLength];
200 rawSecurityDescriptor.GetBinaryForm(sdBytes, 0);
201 Marshal.FreeCoTaskMem(securityDescriptorPtr);
202 securityDescriptorPtr = Marshal.AllocCoTaskMem(rawSecurityDescriptor.BinaryLength);
203 Marshal.Copy(sdBytes, 0, securityDescriptorPtr, rawSecurityDescriptor.BinaryLength);
205 if (!NativeMethods.SetKernelObjectSecurity(hProcessToken, NativeMethods.SECURITY_INFORMATION.DACL_SECURITY_INFORMATION, securityDescriptorPtr))
206 Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
210 if (hProcess != IntPtr.Zero && hProcess != (IntPtr)(-1))
211 if (!NativeMethods.CloseHandle(hProcess))
212 Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
214 if (hProcessToken != IntPtr.Zero)
215 if (!NativeMethods.CloseHandle(hProcessToken))
216 Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
218 if (securityDescriptorPtr != IntPtr.Zero)
219 Marshal.FreeCoTaskMem(securityDescriptorPtr);
224 // The preferred order in which ACEs are added to DACLs is
225 // documented here: http://search.msdn.microsoft.com/search/results.aspx?qu=Order+of+ACEs+in+a+DACL&View=msdn&st=b.
226 // This routine follows that logic to determine the position of an ACE in the DACL.
227 private int FindIndexInDacl(CommonAce newAce, RawAcl dacl)
230 for (i = 0; i < dacl.Count; i++)
232 if (dacl[i] is CommonAce && ((CommonAce)dacl[i]).SecurityIdentifier.Value == newAce.SecurityIdentifier.Value && dacl[i].AceType == newAce.AceType)
238 if (newAce.AceType == AceType.AccessDenied && dacl[i].AceType == AceType.AccessDenied && !newAce.IsInherited && !dacl[i].IsInherited)
241 if (newAce.AceType == AceType.AccessDenied && !newAce.IsInherited)
244 if (newAce.AceType == AceType.AccessAllowed && dacl[i].AceType == AceType.AccessAllowed && !newAce.IsInherited && !dacl[i].IsInherited)
247 if (newAce.AceType == AceType.AccessAllowed && !newAce.IsInherited)
257 #region Constructor and Lifetime methods
259 internal DebugController(WorkflowRuntime serviceContainer, string hostName)
261 if (serviceContainer == null)
262 throw new ArgumentNullException("serviceContainer");
266 this.programPublisher = new ProgramPublisher();
270 // If we are unable to create the ProgramPublisher, this means that VS does not exist on this machine, so we can't debug.
274 this.serviceContainer = serviceContainer;
275 this.programId = Guid.Empty;
276 this.controllerConduit = null;
278 this.isZombie = false;
279 this.hostName = hostName;
281 AppDomain.CurrentDomain.ProcessExit += OnDomainUnload;
282 AppDomain.CurrentDomain.DomainUnload += OnDomainUnload;
284 this.serviceContainer.Started += this.Start;
285 this.serviceContainer.Stopped += this.Stop;
288 public override object InitializeLifetimeService()
290 // We can't use a sponser because VS doesn't like to be attached when the lease renews itself - the
291 // debugee gets an Access Violation and VS freezes. Returning null implies that the proxy shim will be
292 // deleted only when the App Domain unloads. However, we will have disconnected the shim so no
293 // one will be able to attach to it and the same proxy is used everytime a debugger attaches.
299 #region Attach and Detach methods
301 internal void Attach(Guid programId, int attachTimeout, int detachPingInterval, out string hostName, out string uri, out int controllerThreadId, out bool isSynchronousAttach)
303 Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "WDE: DebugController.Attach(): programId = {0}", programId));
306 hostName = String.Empty;
308 controllerThreadId = 0;
309 isSynchronousAttach = false;
312 // During the call to Attach() if Uninitialize() is also called, we should ignore the call to Attach() and
313 // just return. The Zombie flag and lock(this) help us recognize the ----.
319 // The isAttached flat along with lock(this) catch the ---- where a debugger may have detached which
320 // we haven't detected yet and another debugger may have attached, so we force detach from the first
326 this.isAttached = true;
328 this.programId = programId;
329 this.debugControllerThread = new DebugControllerThread();
330 this.instanceTable = new InstanceTable(this.debugControllerThread.ManagedThreadId);
331 this.typeToGuid = new Dictionary<Type, Guid>();
332 this.xomlHashToGuid = new Dictionary<byte[], Guid>((IEqualityComparer<byte[]>)new DigestComparer());
334 this.debugControllerThread.RunThread(this.instanceTable);
336 // Publish our MBR object.
337 IDictionary providerProperties = new Hashtable();
338 providerProperties["typeFilterLevel"] = "Full";
339 BinaryServerFormatterSinkProvider sinkProvider = new BinaryServerFormatterSinkProvider(providerProperties, null);
341 Hashtable channelProperties = new Hashtable();
342 channelProperties["name"] = string.Empty;
343 channelProperties["portName"] = this.programId.ToString();
344 SecurityIdentifier si = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null);
345 IdentityReference idRef = si.Translate(typeof(NTAccount));
346 channelProperties["authorizedGroup"] = idRef.ToString();
347 this.channel = new IpcChannel(channelProperties, null, sinkProvider);
348 ChannelServices.RegisterChannel(this.channel, true);
350 ObjRef o = RemotingServices.Marshal(this, this.programId.ToString());
351 hostName = this.hostName;
353 uri = this.channel.GetUrlsForUri(this.programId.ToString())[0];
354 controllerThreadId = this.debugControllerThread.ThreadId;
355 isSynchronousAttach = !this.isServiceContainerStarting;
357 this.attachTimeout = attachTimeout;
358 this.attachTimer = new Timer(AttachTimerCallback, null, attachTimeout, detachPingInterval);
362 private void Detach()
364 Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "WDE: DebugController.Detach():"));
366 using (new DebuggerThreadMarker())
371 AppDomain.CurrentDomain.AssemblyLoad -= OnAssemblyLoad;
373 // See comments in Attach().
374 if (this.isZombie || !this.isAttached)
377 this.isAttached = false;
379 // Undone: AkashS - At this point wait for all event handling to complete to avoid exceptions.
381 this.programId = Guid.Empty;
383 if (this.debugControllerThread != null)
385 this.debugControllerThread.StopThread();
386 this.debugControllerThread = null;
389 if (this.attachTimer != null)
391 this.attachTimer.Change(Timeout.Infinite, Timeout.Infinite);
392 this.attachTimer = null;
395 RemotingServices.Disconnect(this);
396 if (this.channel != null)
398 ChannelServices.UnregisterChannel(this.channel);
402 this.controllerConduit = null;
404 this.eventConduitAttached.Reset();
405 this.instanceTable = null;
406 this.typeToGuid = null;
407 this.xomlHashToGuid = null;
409 // Do this only after we perform the previous cleanup! Otherwise
410 // we may get exceptions from the runtime that may cause the cleanup
413 if (!this.serviceContainer.IsZombie)
415 foreach (WorkflowInstance instance in this.serviceContainer.GetLoadedWorkflows())
417 WorkflowExecutor executor = instance.GetWorkflowResourceUNSAFE();
418 using (executor.ExecutorLock.Enter())
420 if (executor.IsInstanceValid)
421 executor.WorkflowExecutionEvent -= OnInstanceEvent;
425 this.serviceContainer.WorkflowExecutorInitializing -= InstanceInitializing;
426 this.serviceContainer.DefinitionDispenser.WorkflowDefinitionLoaded -= ScheduleTypeLoaded;
432 private void AttachTimerCallback(object state)
434 Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "WDE: DebugController.AttachTimerCallback():"));
440 // See comments in Attach().
441 if (this.isZombie || !this.isAttached)
444 if (!Debugger.IsAttached)
446 // The debugger had attached and has now detached, so cleanup, or Attach() was called on the
447 // Program Node, but the process of attach failed thereafter and so we were never actually
449 this.attachTimer.Change(Timeout.Infinite, Timeout.Infinite);
456 // Avoid throwing unhandled exceptions!
460 private void OnDomainUnload(object sender, System.EventArgs e)
462 Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "WDE: DebugController.OnDomainUnload():"));
464 Stop(null, default(WorkflowRuntimeEventArgs));
469 #region Methods for the DE
471 public void AttachToConduit(Uri url)
474 throw new ArgumentNullException("url");
478 using (new DebuggerThreadMarker())
482 RegistryKey debugEngineSubKey = Registry.LocalMachine.OpenSubKey(RegistryKeys.DebuggerSubKey);
483 if (debugEngineSubKey != null)
485 string controllerConduitTypeName = debugEngineSubKey.GetValue(ControllerConduitTypeName, String.Empty) as string;
486 if (!String.IsNullOrEmpty(controllerConduitTypeName) && Type.GetType(controllerConduitTypeName) != null)
487 this.controllerConduit = Activator.GetObject(Type.GetType(controllerConduitTypeName), url.AbsolutePath) as IWorkflowDebugger;
492 if (this.controllerConduit == null)
494 const string controllerConduitTypeFormat = "Microsoft.Workflow.DebugEngine.ControllerConduit, Microsoft.Workflow.DebugController, Version={0}.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35";
496 // Try versions 12.0.0.0, 11.0.0.0, 10.0.0.0
497 Type controllerConduitType = null;
498 for (int version = 12; controllerConduitType == null && version >= 10; --version)
502 controllerConduitType = Type.GetType(string.Format(CultureInfo.InvariantCulture, controllerConduitTypeFormat, version));
504 catch (TypeLoadException)
506 // Fall back to next-lower version
510 if (controllerConduitType != null)
512 this.controllerConduit = Activator.GetObject(controllerConduitType, url.AbsolutePath) as IWorkflowDebugger;
515 Debug.Assert(this.controllerConduit != null, "Failed to create Controller Conduit");
516 if (this.controllerConduit == null)
519 this.eventLock = new object();
522 // We hook up to the AssemblyLoad event, the Schedule events and Instance events handler
523 // before we iterate over all loaded assemblies. This means that we need to deal with duplicates in the
527 // Further the order in which we hook up handlers/iterate is important to avoid ----s if the events fire
528 // before the iterations complete. We need to hook and iterate over the assemblies, then the schedule
529 // types and finally the instances. This guarantees that we always have all the assemblies when we load
530 // schedules types and we always have all the schedule types when we load instances.
532 AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoad;
533 foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
535 if (!assembly.IsDynamic
536 && !(assembly is System.Reflection.Emit.AssemblyBuilder)
537 && !(string.IsNullOrEmpty(assembly.Location)))
539 this.controllerConduit.AssemblyLoaded(this.programId, assembly.Location, assembly.GlobalAssemblyCache);
542 this.serviceContainer.DefinitionDispenser.WorkflowDefinitionLoaded += ScheduleTypeLoaded;
544 // In here we load all schedule types defined as they are - with no dynamic updates
545 ReadOnlyCollection<Type> types;
546 ReadOnlyCollection<Activity> values;
547 this.serviceContainer.DefinitionDispenser.GetWorkflowTypes(out types, out values);
548 for (int i = 0; i < types.Count; i++)
550 Type scheduleType = types[i];
551 Activity rootActivity = values[i];
552 LoadExistingScheduleType(GetScheduleTypeId(scheduleType), scheduleType, false, rootActivity);
555 ReadOnlyCollection<byte[]> keys;
556 this.serviceContainer.DefinitionDispenser.GetWorkflowDefinitions(out keys, out values);
557 for (int i = 0; i < keys.Count; i++)
559 byte[] scheduleDefHash = keys[i];
560 Activity rootActivity = values[i];
561 Activity workflowDefinition = (Activity)rootActivity.GetValue(Activity.WorkflowDefinitionProperty);
562 ArrayList changeActions = null;
563 if (workflowDefinition != null)
564 changeActions = (ArrayList)workflowDefinition.GetValue(WorkflowChanges.WorkflowChangeActionsProperty);
565 LoadExistingScheduleType(GetScheduleTypeId(scheduleDefHash), rootActivity.GetType(), (changeActions != null && changeActions.Count != 0), rootActivity);
568 this.serviceContainer.WorkflowExecutorInitializing += InstanceInitializing;
570 foreach (WorkflowInstance instance in this.serviceContainer.GetLoadedWorkflows())
572 WorkflowExecutor executor = instance.GetWorkflowResourceUNSAFE();
573 using (executor.ExecutorLock.Enter())
575 LoadExistingInstance(instance, true);
579 this.eventConduitAttached.Set();
584 // Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches
585 // and closes the remoting channel.
586 Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "WDE: Failure in DebugController.AttachToConduit: {0}, Call stack:{1}", e.Message, e.StackTrace));
593 #region Methods for the Runtime
595 private void Start(object source, WorkflowRuntimeEventArgs e)
597 Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "WDE: DebugController.ServiceContainerStarted():"));
599 this.isZombie = false;
600 this.isAttached = false;
601 this.eventConduitAttached = new ManualResetEvent(false);
602 this.isServiceContainerStarting = true;
604 bool published = this.programPublisher.Publish(this);
606 // If the debugger is already attached, then the DE will invoke AttachToConduit() on a separate thread.
607 // We need to wait for that to happen to prevent new instances being created and causing a ----. See
608 // comments in ControllerConduit.ProgramCreated(). However, if the DE never calls AttachToConduit(),
609 // and the detaches instead, we set a wait timeout to that of our Attach Timer.
610 // Note that when we publish the program node, if the debugger is attached, isAttached will be set to true
611 // when the debugger calls Attach() on the Program Node!
613 while (published && this.isAttached && !this.eventConduitAttached.WaitOne(attachTimeout, false));
614 this.isServiceContainerStarting = false;
617 private void Stop(object source, WorkflowRuntimeEventArgs e)
619 Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "WDE: DebugController.ServiceContainerStopped():"));
626 this.programPublisher.Unpublish();
628 // See comments in Attach().
629 this.isZombie = true;
634 // Do not throw exceptions back!
638 internal void Close()
640 //Unregister from Appdomain event to remove ourselves from GCRoot.
641 AppDomain.CurrentDomain.ProcessExit -= OnDomainUnload;
642 AppDomain.CurrentDomain.DomainUnload -= OnDomainUnload;
646 Stop(null, new WorkflowRuntimeEventArgs(false));
651 private void OnInstanceEvent(object sender, WorkflowExecutor.WorkflowExecutionEventArgs e)
655 case WorkflowEventInternal.Completed:
656 InstanceCompleted(sender, new WorkflowEventArgs(((WorkflowExecutor)sender).WorkflowInstance));
658 case WorkflowEventInternal.Terminated:
659 InstanceTerminated(sender, new WorkflowEventArgs(((WorkflowExecutor)sender).WorkflowInstance));
661 case WorkflowEventInternal.Unloaded:
662 InstanceUnloaded(sender, new WorkflowEventArgs(((WorkflowExecutor)sender).WorkflowInstance));
664 case WorkflowEventInternal.Changed:
665 OnWorkflowChanged(sender, e);
667 case WorkflowEventInternal.HandlerInvoking:
668 OnHandlerInvoking(sender, e);
670 case WorkflowEventInternal.HandlerInvoked:
671 OnHandlerInvoked(sender, e);
673 case WorkflowEventInternal.ActivityStatusChange:
674 OnActivityStatusChanged(sender, (WorkflowExecutor.ActivityStatusChangeEventArgs)e);
676 case WorkflowEventInternal.ActivityExecuting:
677 OnActivityExecuting(sender, (WorkflowExecutor.ActivityExecutingEventArgs)e);
685 private void InstanceInitializing(object sender, WorkflowRuntime.WorkflowExecutorInitializingEventArgs e)
690 LoadExistingInstance(((WorkflowExecutor)sender).WorkflowInstance, true);
692 LoadExistingInstance(((WorkflowExecutor)sender).WorkflowInstance, false);
696 // Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches
697 // and closes the remoting channel.
701 private void InstanceCompleted(object sender, WorkflowEventArgs args)
705 UnloadExistingInstance(args.WorkflowInstance);
709 // Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches
710 // and closes the remoting channel.
714 private void InstanceTerminated(object sender, WorkflowEventArgs args)
718 UnloadExistingInstance(args.WorkflowInstance);
722 // Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches
723 // and closes the remoting channel.
727 private void InstanceUnloaded(object sender, WorkflowEventArgs args)
729 // Treat this as if the instance completed so that it won't show up in the UI anymore.
732 UnloadExistingInstance(args.WorkflowInstance);
736 // Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches
737 // and closes the remoting channel.
741 private void ScheduleTypeLoaded(object sender, WorkflowDefinitionEventArgs args)
745 if (args.WorkflowType != null)
747 Activity rootActivity = ((WorkflowRuntime)sender).DefinitionDispenser.GetWorkflowDefinition(args.WorkflowType);
748 LoadExistingScheduleType(GetScheduleTypeId(args.WorkflowType), args.WorkflowType, false, rootActivity);
752 Activity rootActivity = ((WorkflowRuntime)sender).DefinitionDispenser.GetWorkflowDefinition(args.WorkflowDefinitionHashCode);
753 LoadExistingScheduleType(GetScheduleTypeId(args.WorkflowDefinitionHashCode), rootActivity.GetType(), false, rootActivity);
758 // Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches
759 // and closes the remoting channel.
763 private void OnActivityExecuting(object sender, WorkflowExecutor.ActivityExecutingEventArgs eventArgs)
765 if (this.isZombie || !this.isAttached)
770 lock (this.eventLock)
772 IWorkflowCoreRuntime workflowCoreRuntime = (IWorkflowCoreRuntime)sender;
773 Guid scheduleTypeId = GetScheduleTypeId(workflowCoreRuntime);
775 // When the activity starts executing, update its handler list for stepping.
776 EnumerateEventHandlersForActivity(scheduleTypeId, eventArgs.Activity);
777 this.controllerConduit.BeforeActivityStatusChanged(this.programId, scheduleTypeId, workflowCoreRuntime.InstanceID, eventArgs.Activity.QualifiedName, GetHierarchicalId(eventArgs.Activity), eventArgs.Activity.ExecutionStatus, GetContextId(eventArgs.Activity));
778 this.controllerConduit.ActivityStatusChanged(this.programId, scheduleTypeId, workflowCoreRuntime.InstanceID, eventArgs.Activity.QualifiedName, GetHierarchicalId(eventArgs.Activity), eventArgs.Activity.ExecutionStatus, GetContextId(eventArgs.Activity));
783 // Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches
784 // and closes the remoting channel.
788 private void OnActivityStatusChanged(object sender, WorkflowExecutor.ActivityStatusChangeEventArgs eventArgs)
790 if (this.isZombie || !this.isAttached)
795 lock (this.eventLock)
797 // We will receive an event when Activity.Execute() is about to be called.
798 if (eventArgs.Activity.ExecutionStatus == ActivityExecutionStatus.Executing)
801 IWorkflowCoreRuntime workflowCoreRuntime = (IWorkflowCoreRuntime)sender;
802 Guid scheduleTypeId = GetScheduleTypeId(workflowCoreRuntime);
804 // When the activity starts executing, update its handler list for stepping.
805 if (eventArgs.Activity.ExecutionStatus == ActivityExecutionStatus.Executing)
806 EnumerateEventHandlersForActivity(scheduleTypeId, eventArgs.Activity);
808 this.controllerConduit.BeforeActivityStatusChanged(this.programId, scheduleTypeId, workflowCoreRuntime.InstanceID, eventArgs.Activity.QualifiedName, GetHierarchicalId(eventArgs.Activity), eventArgs.Activity.ExecutionStatus, GetContextId(eventArgs.Activity));
809 this.controllerConduit.ActivityStatusChanged(this.programId, scheduleTypeId, workflowCoreRuntime.InstanceID, eventArgs.Activity.QualifiedName, GetHierarchicalId(eventArgs.Activity), eventArgs.Activity.ExecutionStatus, GetContextId(eventArgs.Activity));
814 // Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches
815 // and closes the remoting channel.
819 private void OnHandlerInvoking(object sender, EventArgs eventArgs)
821 // Undone: AkashS - We need to remove EnumerateHandlersForActivity() and set the CPDE
822 // breakpoints from here. This is handle the cases where event handlers are modified
826 private void OnHandlerInvoked(object sender, EventArgs eventArgs)
828 if (this.isZombie || !this.isAttached)
833 lock (this.eventLock)
835 IWorkflowCoreRuntime workflowCoreRuntime = sender as IWorkflowCoreRuntime;
836 this.controllerConduit.HandlerInvoked(this.programId, workflowCoreRuntime.InstanceID, NativeMethods.GetCurrentThreadId(), GetHierarchicalId(workflowCoreRuntime.CurrentActivity));
841 // Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches
842 // and closes the remoting channel.
846 private void OnWorkflowChanged(object sender, EventArgs eventArgs)
848 if (this.isZombie || !this.isAttached)
853 lock (this.eventLock)
855 IWorkflowCoreRuntime workflowCoreRuntime = (IWorkflowCoreRuntime)sender;
857 // Get cached old root activity.
858 Activity oldRootActivity = this.instanceTable.GetRootActivity(workflowCoreRuntime.InstanceID);
860 Guid scheduleTypeId = workflowCoreRuntime.InstanceID; // From now on we will treat the instance id as a dynamic schedule type id.
861 LoadExistingScheduleType(scheduleTypeId, oldRootActivity.GetType(), true, oldRootActivity);
863 // And now reload the instance.
864 this.instanceTable.UpdateRootActivity(workflowCoreRuntime.InstanceID, oldRootActivity);
866 // The DE will update the schedule type on the thread that is running the instance.
867 // DE should be called after the instance table entry is replaced.
868 this.controllerConduit.InstanceDynamicallyUpdated(this.programId, workflowCoreRuntime.InstanceID, scheduleTypeId);
873 // Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches
874 // and closes the remoting channel.
880 #region Helper methods and properties
882 // Callers of this method should acquire the executor lock only if they
883 // are not being called in the runtime thread.(
884 private void LoadExistingInstance(WorkflowInstance instance, bool attaching)
886 WorkflowExecutor executor = instance.GetWorkflowResourceUNSAFE();
887 if (!executor.IsInstanceValid)
889 IWorkflowCoreRuntime runtimeService = (IWorkflowCoreRuntime)executor;
890 Activity rootActivity = runtimeService.RootActivity;
891 Guid scheduleTypeId = GetScheduleTypeId(runtimeService);
893 // If we are just attaching, need to LoadExistingScheduleType with the dynamic definition and type
894 // since the OnDynamicUpdateEvent has never been executed.
895 if (attaching && runtimeService.IsDynamicallyUpdated)
896 LoadExistingScheduleType(scheduleTypeId, rootActivity.GetType(), true, rootActivity);
898 // Add to the InstanceTable before firing the DE event !
899 this.instanceTable.AddInstance(instance.InstanceId, rootActivity);
901 this.controllerConduit.InstanceCreated(this.programId, instance.InstanceId, scheduleTypeId);
903 // Take a lock so that SetInitialActivityStatus is always called before next status events.
904 lock (this.eventLock)
906 executor.WorkflowExecutionEvent += OnInstanceEvent;
907 foreach (Activity activity in DebugController.WalkActivityTree(rootActivity))
911 ReplicatorActivity replicator = activity as ReplicatorActivity;
912 if (replicator != null)
914 foreach (Activity queuedChildActivity in replicator.DynamicActivities)
915 activityQueue.Enqueue(queuedChildActivity);
919 UpdateActivityStatus(scheduleTypeId, instance.InstanceId, activity);
923 ActivityExecutionContext rootExecutionContext = new ActivityExecutionContext(rootActivity);
924 foreach (ActivityExecutionContext executionContext in DebugController.WalkExecutionContextTree(rootExecutionContext))
926 Activity instanceActivity = executionContext.Activity;
927 foreach (Activity childInstance in DebugController.WalkActivityTree(instanceActivity))
929 UpdateActivityStatus(scheduleTypeId, instance.InstanceId, childInstance);
935 private void UpdateActivityStatus(Guid scheduleTypeId, Guid instanceId, Activity activity)
937 if (activity == null)
938 throw new ArgumentNullException("activity");
940 // first update its handler list
941 if (activity.ExecutionStatus == ActivityExecutionStatus.Executing)
942 EnumerateEventHandlersForActivity(scheduleTypeId, activity);
944 //report only states different from the initialized
945 if (activity.ExecutionStatus != ActivityExecutionStatus.Initialized)
947 Activity contextActivity = ContextActivityUtils.ContextActivity(activity);
948 int context = ContextActivityUtils.ContextId(contextActivity);
949 this.controllerConduit.SetInitialActivityStatus(this.programId, scheduleTypeId, instanceId, activity.QualifiedName, GetHierarchicalId(activity), activity.ExecutionStatus, context);
954 private static IEnumerable WalkActivityTree(Activity rootActivity)
956 if (rootActivity == null || !rootActivity.Enabled)
960 yield return rootActivity;
962 // Go through the children as well
963 if (rootActivity is CompositeActivity)
965 foreach (Activity childActivity in ((CompositeActivity)rootActivity).Activities)
967 foreach (Activity nestedChild in WalkActivityTree(childActivity))
968 yield return nestedChild;
973 private static IEnumerable WalkExecutionContextTree(ActivityExecutionContext rootContext)
975 if (rootContext == null)
978 yield return rootContext;
980 foreach (ActivityExecutionContext executionContext in rootContext.ExecutionContextManager.ExecutionContexts)
982 foreach (ActivityExecutionContext nestedContext in WalkExecutionContextTree(executionContext))
983 yield return nestedContext;
988 private void UnloadExistingInstance(WorkflowInstance instance)
990 // Fire DE event before removing from the InstanceTable!
991 this.controllerConduit.InstanceCompleted(this.programId, instance.InstanceId);
992 this.instanceTable.RemoveInstance(instance.InstanceId);
995 private void LoadExistingScheduleType(Guid scheduleTypeId, Type scheduleType, bool isDynamic, Activity rootActivity)
997 if (rootActivity == null)
998 throw new InvalidOperationException();
1000 using (StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture))
1002 using (XmlWriter xmlWriter = Helpers.CreateXmlWriter(stringWriter))
1004 WorkflowMarkupSerializer serializer = new WorkflowMarkupSerializer();
1005 serializer.Serialize(xmlWriter, rootActivity);
1006 string fileName = null;
1007 string md5Digest = null;
1008 Attribute[] attributes = scheduleType.GetCustomAttributes(typeof(WorkflowMarkupSourceAttribute), false) as Attribute[];
1009 if (attributes != null && attributes.Length == 1)
1011 fileName = ((WorkflowMarkupSourceAttribute)attributes[0]).FileName;
1012 md5Digest = ((WorkflowMarkupSourceAttribute)attributes[0]).MD5Digest;
1015 this.controllerConduit.ScheduleTypeLoaded(this.programId, scheduleTypeId, scheduleType.Assembly.FullName, fileName, md5Digest, isDynamic, scheduleType.FullName, scheduleType.Name, stringWriter.ToString());
1020 private string GetHierarchicalId(Activity activity)
1022 string id = string.Empty;
1023 while (activity != null)
1025 string iterationId = string.Empty;
1027 Activity contextActivity = ContextActivityUtils.ContextActivity(activity);
1028 int context = ContextActivityUtils.ContextId(contextActivity);
1029 iterationId = activity.Name + ((context > 1 && activity == contextActivity) ? "(" + context + ")" : string.Empty);
1031 id = (id.Length > 0) ? iterationId + "." + id : iterationId;
1033 activity = activity.Parent;
1039 private int GetContextId(Activity activity)
1041 Activity contextActivity = ContextActivityUtils.ContextActivity(activity);
1042 return ContextActivityUtils.ContextId(contextActivity);
1045 private Guid GetScheduleTypeId(IWorkflowCoreRuntime workflowCoreRuntime)
1047 Activity rootActivity = workflowCoreRuntime.RootActivity;
1049 if (workflowCoreRuntime.IsDynamicallyUpdated)
1050 return workflowCoreRuntime.InstanceID;
1051 else if (string.IsNullOrEmpty(rootActivity.GetValue(Activity.WorkflowXamlMarkupProperty) as string))
1052 return GetScheduleTypeId(rootActivity.GetType());
1054 return GetScheduleTypeId(rootActivity.GetValue(WorkflowDefinitionDispenser.WorkflowDefinitionHashCodeProperty) as byte[]);
1057 private Guid GetScheduleTypeId(Type scheduleType)
1059 // We cannot use the GUID from the type because that is not guaranteed to be unique, especially when
1060 // multiple versions are loaded and the stamps a GuidAttribute.
1061 lock (this.typeToGuid)
1063 if (!this.typeToGuid.ContainsKey(scheduleType))
1064 this.typeToGuid[scheduleType] = Guid.NewGuid();
1066 return (Guid)this.typeToGuid[scheduleType];
1070 private Guid GetScheduleTypeId(byte[] scheduleDefHashCode)
1072 // We use the same hashtable to store schedule definition to Guid mapping.
1073 lock (this.xomlHashToGuid)
1075 if (!this.xomlHashToGuid.ContainsKey(scheduleDefHashCode))
1076 this.xomlHashToGuid[scheduleDefHashCode] = Guid.NewGuid();
1078 return (Guid)this.xomlHashToGuid[scheduleDefHashCode];
1082 private void OnAssemblyLoad(object sender, AssemblyLoadEventArgs args)
1084 // Call assembly load on the conduit for assemblies loaded from disk.
1085 if (args.LoadedAssembly.Location != String.Empty)
1089 this.controllerConduit.AssemblyLoaded(this.programId, args.LoadedAssembly.Location, args.LoadedAssembly.GlobalAssemblyCache);
1093 // Don't throw exceptions to the CLR. Ignore exceptions that may occur if the debugger detaches
1094 // and closes the remoting channel.
1099 private void EnumerateEventHandlersForActivity(Guid scheduleTypeId, Activity activity)
1101 List<ActivityHandlerDescriptor> handlerMethods = new List<ActivityHandlerDescriptor>();
1102 MethodInfo getInvocationListMethod = activity.GetType().GetMethod("System.Workflow.ComponentModel.IDependencyObjectAccessor.GetInvocationList", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
1104 foreach (EventInfo eventInfo in activity.GetType().GetEvents(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy))
1106 DependencyProperty dependencyEvent = DependencyProperty.FromName(eventInfo.Name, activity.GetType());
1108 if (dependencyEvent != null)
1112 MethodInfo boundGetInvocationListMethod = getInvocationListMethod.MakeGenericMethod(new Type[] { dependencyEvent.PropertyType });
1113 foreach (Delegate handler in (boundGetInvocationListMethod.Invoke(activity, new object[] { dependencyEvent }) as Delegate[]))
1115 MethodInfo handlerMethodInfo = handler.Method;
1116 ActivityHandlerDescriptor handlerMethod;
1117 handlerMethod.Name = handlerMethodInfo.DeclaringType.FullName + "." + handlerMethodInfo.Name;
1118 handlerMethod.Token = handlerMethodInfo.MetadataToken;
1119 handlerMethods.Add(handlerMethod);
1128 this.controllerConduit.UpdateHandlerMethodsForActivity(this.programId, scheduleTypeId, activity.QualifiedName, handlerMethods);