// Copyright (c) Microsoft Corp., 2004. All rights reserved. #region Using directives using System; using System.IO; using System.Xml; using System.Text; using System.Threading; using System.Reflection; using System.Collections; using System.Diagnostics; using System.Runtime.Remoting; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Runtime.Serialization; using System.Workflow.Runtime; using System.Workflow.Runtime.Hosting; using System.Runtime.InteropServices; using System.Runtime.Remoting.Channels; using System.Workflow.ComponentModel; using System.Workflow.ComponentModel.Compiler; using System.Workflow.ComponentModel.Design; using System.Workflow.ComponentModel.Serialization; using System.Runtime.Serialization.Formatters; using System.Runtime.Remoting.Channels.Ipc; using System.Configuration; using System.Security.Permissions; using System.Globalization; using Microsoft.Win32; using System.Security.AccessControl; using System.Security.Principal; #endregion namespace System.Workflow.Runtime.DebugEngine { internal static class RegistryKeys { internal static readonly string ProductRootRegKey = @"SOFTWARE\Microsoft\Net Framework Setup\NDP\v3.0\Setup\Windows Workflow Foundation"; internal static readonly string DebuggerSubKey = ProductRootRegKey + @"\Debugger"; } [Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")] public sealed class DebugController : MarshalByRefObject { #region Data members private Guid programId; private string hostName; private int attachTimeout; private ProgramPublisher programPublisher; private DebugControllerThread debugControllerThread; private Timer attachTimer; private WorkflowRuntime serviceContainer; private IpcChannel channel; private IWorkflowDebugger controllerConduit; private bool isZombie; private bool isAttached; private ManualResetEvent eventConduitAttached; private InstanceTable instanceTable; private Dictionary typeToGuid; private Dictionary xomlHashToGuid; bool isServiceContainerStarting; private const string rootExecutorGuid = "98fcdc7a-8ab4-4fb7-92d4-20f437285729"; private object eventLock; private object syncRoot = new object(); private static readonly string ControllerConduitTypeName = "ControllerConduitTypeName"; #endregion #region Security related methods private delegate void ExceptionNotification(Exception e); internal static void InitializeProcessSecurity() { // Spawn off a separate thread to that does RevertToSelf and adjusts DACLs. // This is because RevertToSelf terminates client impersonation on the thread // that calls it. We do not want to change that on the current thread when // the runtime is hosted inside ASP.net for example. Exception workerThreadException = null; ProcessSecurity processSecurity = new ProcessSecurity(); Thread workerThread = new Thread(new ThreadStart(processSecurity.Initialize)); processSecurity.exceptionNotification += delegate(Exception e) { workerThreadException = e; }; workerThread.Start(); workerThread.Join(); if (workerThreadException != null) throw workerThreadException; } private class ProcessSecurity { internal ExceptionNotification exceptionNotification; internal void Initialize() { try { // This is needed if the thread calling the method is impersonating // a client call (ASP.net hosting scenarios). if (!NativeMethods.RevertToSelf()) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); // Get the DACL for process token. Add TOKEN_QUERY permissions for the Administrators group. // Set the updated DACL for process token. RawAcl tokenDacl = GetCurrentProcessTokenDacl(); CommonAce adminsGroupAceForToken = new CommonAce(AceFlags.None, AceQualifier.AccessAllowed, NativeMethods.TOKEN_QUERY, new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null), false, null); int i = FindIndexInDacl(adminsGroupAceForToken, tokenDacl); if (i != -1) tokenDacl.InsertAce(i, adminsGroupAceForToken); SetCurrentProcessTokenDacl(tokenDacl); } catch (Exception e) { // Communicate any exceptions from this thread back to the thread // that spawned it. if (exceptionNotification != null) exceptionNotification(e); } } private RawAcl GetCurrentProcessTokenDacl() { IntPtr hProcess = IntPtr.Zero; IntPtr hProcessToken = IntPtr.Zero; IntPtr securityDescriptorPtr = IntPtr.Zero; try { hProcess = NativeMethods.GetCurrentProcess(); if (!NativeMethods.OpenProcessToken(hProcess, NativeMethods.TOKEN_ALL_ACCESS, out hProcessToken)) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); // Get security descriptor associated with the kernel object, read the DACL and return // that to the caller. uint returnLength; NativeMethods.GetKernelObjectSecurity(hProcessToken, NativeMethods.SECURITY_INFORMATION.DACL_SECURITY_INFORMATION, IntPtr.Zero, 0, out returnLength); int lasterror = Marshal.GetLastWin32Error(); //#pragma warning disable 56523 doesnt recognize 56523 securityDescriptorPtr = Marshal.AllocCoTaskMem((int)returnLength); if (!NativeMethods.GetKernelObjectSecurity(hProcessToken, NativeMethods.SECURITY_INFORMATION.DACL_SECURITY_INFORMATION, securityDescriptorPtr, returnLength, out returnLength)) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); byte[] sdBytes = new byte[returnLength]; Marshal.Copy(securityDescriptorPtr, sdBytes, 0, (int)returnLength); RawSecurityDescriptor rawSecurityDescriptor = new RawSecurityDescriptor(sdBytes, 0); return rawSecurityDescriptor.DiscretionaryAcl; } finally { if (hProcess != IntPtr.Zero && hProcess != (IntPtr)(-1)) if (!NativeMethods.CloseHandle(hProcess)) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); if (hProcessToken != IntPtr.Zero) if (!NativeMethods.CloseHandle(hProcessToken)) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); if (securityDescriptorPtr != IntPtr.Zero) Marshal.FreeCoTaskMem(securityDescriptorPtr); } } private void SetCurrentProcessTokenDacl(RawAcl dacl) { IntPtr hProcess = IntPtr.Zero; IntPtr hProcessToken = IntPtr.Zero; IntPtr securityDescriptorPtr = IntPtr.Zero; try { hProcess = NativeMethods.GetCurrentProcess(); if (!NativeMethods.OpenProcessToken(hProcess, NativeMethods.TOKEN_ALL_ACCESS, out hProcessToken)) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); // Get security descriptor associated with the kernel object and modify it. uint returnLength; NativeMethods.GetKernelObjectSecurity(hProcessToken, NativeMethods.SECURITY_INFORMATION.DACL_SECURITY_INFORMATION, IntPtr.Zero, 0, out returnLength); int lasterror = Marshal.GetLastWin32Error(); //#pragma warning disable 56523 doesnt recognize 56523 securityDescriptorPtr = Marshal.AllocCoTaskMem((int)returnLength); if (!NativeMethods.GetKernelObjectSecurity(hProcessToken, NativeMethods.SECURITY_INFORMATION.DACL_SECURITY_INFORMATION, securityDescriptorPtr, returnLength, out returnLength)) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); byte[] sdBytes = new byte[returnLength]; Marshal.Copy(securityDescriptorPtr, sdBytes, 0, (int)returnLength); RawSecurityDescriptor rawSecurityDescriptor = new RawSecurityDescriptor(sdBytes, 0); rawSecurityDescriptor.DiscretionaryAcl = dacl; sdBytes = new byte[rawSecurityDescriptor.BinaryLength]; rawSecurityDescriptor.GetBinaryForm(sdBytes, 0); Marshal.FreeCoTaskMem(securityDescriptorPtr); securityDescriptorPtr = Marshal.AllocCoTaskMem(rawSecurityDescriptor.BinaryLength); Marshal.Copy(sdBytes, 0, securityDescriptorPtr, rawSecurityDescriptor.BinaryLength); if (!NativeMethods.SetKernelObjectSecurity(hProcessToken, NativeMethods.SECURITY_INFORMATION.DACL_SECURITY_INFORMATION, securityDescriptorPtr)) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); } finally { if (hProcess != IntPtr.Zero && hProcess != (IntPtr)(-1)) if (!NativeMethods.CloseHandle(hProcess)) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); if (hProcessToken != IntPtr.Zero) if (!NativeMethods.CloseHandle(hProcessToken)) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); if (securityDescriptorPtr != IntPtr.Zero) Marshal.FreeCoTaskMem(securityDescriptorPtr); } } // The preferred order in which ACEs are added to DACLs is // documented here: http://search.msdn.microsoft.com/search/results.aspx?qu=Order+of+ACEs+in+a+DACL&View=msdn&st=b. // This routine follows that logic to determine the position of an ACE in the DACL. private int FindIndexInDacl(CommonAce newAce, RawAcl dacl) { int i = 0; for (i = 0; i < dacl.Count; i++) { if (dacl[i] is CommonAce && ((CommonAce)dacl[i]).SecurityIdentifier.Value == newAce.SecurityIdentifier.Value && dacl[i].AceType == newAce.AceType) { i = -1; break; } if (newAce.AceType == AceType.AccessDenied && dacl[i].AceType == AceType.AccessDenied && !newAce.IsInherited && !dacl[i].IsInherited) continue; if (newAce.AceType == AceType.AccessDenied && !newAce.IsInherited) break; if (newAce.AceType == AceType.AccessAllowed && dacl[i].AceType == AceType.AccessAllowed && !newAce.IsInherited && !dacl[i].IsInherited) continue; if (newAce.AceType == AceType.AccessAllowed && !newAce.IsInherited) break; } return i; } } #endregion #region Constructor and Lifetime methods internal DebugController(WorkflowRuntime serviceContainer, string hostName) { if (serviceContainer == null) throw new ArgumentNullException("serviceContainer"); try { this.programPublisher = new ProgramPublisher(); } catch { // If we are unable to create the ProgramPublisher, this means that VS does not exist on this machine, so we can't debug. return; } this.serviceContainer = serviceContainer; this.programId = Guid.Empty; this.controllerConduit = null; this.channel = null; this.isZombie = false; this.hostName = hostName; AppDomain.CurrentDomain.ProcessExit += OnDomainUnload; AppDomain.CurrentDomain.DomainUnload += OnDomainUnload; this.serviceContainer.Started += this.Start; this.serviceContainer.Stopped += this.Stop; } public override object InitializeLifetimeService() { // We can't use a sponser because VS doesn't like to be attached when the lease renews itself - the // debugee gets an Access Violation and VS freezes. Returning null implies that the proxy shim will be // deleted only when the App Domain unloads. However, we will have disconnected the shim so no // one will be able to attach to it and the same proxy is used everytime a debugger attaches. return null; } #endregion #region Attach and Detach methods internal void Attach(Guid programId, int attachTimeout, int detachPingInterval, out string hostName, out string uri, out int controllerThreadId, out bool isSynchronousAttach) { Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "WDE: DebugController.Attach(): programId = {0}", programId)); lock (this.syncRoot) { hostName = String.Empty; uri = String.Empty; controllerThreadId = 0; isSynchronousAttach = false; // Race condition: // During the call to Attach() if Uninitialize() is also called, we should ignore the call to Attach() and // just return. The Zombie flag and lock(this) help us recognize the ----. if (this.isZombie) return; // Race condition: // The isAttached flat along with lock(this) catch the ---- where a debugger may have detached which // we haven't detected yet and another debugger may have attached, so we force detach from the first // debugger. if (this.isAttached) Detach(); this.isAttached = true; this.programId = programId; this.debugControllerThread = new DebugControllerThread(); this.instanceTable = new InstanceTable(this.debugControllerThread.ManagedThreadId); this.typeToGuid = new Dictionary(); this.xomlHashToGuid = new Dictionary((IEqualityComparer)new DigestComparer()); this.debugControllerThread.RunThread(this.instanceTable); // Publish our MBR object. IDictionary providerProperties = new Hashtable(); providerProperties["typeFilterLevel"] = "Full"; BinaryServerFormatterSinkProvider sinkProvider = new BinaryServerFormatterSinkProvider(providerProperties, null); Hashtable channelProperties = new Hashtable(); channelProperties["name"] = string.Empty; channelProperties["portName"] = this.programId.ToString(); SecurityIdentifier si = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null); IdentityReference idRef = si.Translate(typeof(NTAccount)); channelProperties["authorizedGroup"] = idRef.ToString(); this.channel = new IpcChannel(channelProperties, null, sinkProvider); ChannelServices.RegisterChannel(this.channel, true); ObjRef o = RemotingServices.Marshal(this, this.programId.ToString()); hostName = this.hostName; uri = this.channel.GetUrlsForUri(this.programId.ToString())[0]; controllerThreadId = this.debugControllerThread.ThreadId; isSynchronousAttach = !this.isServiceContainerStarting; this.attachTimeout = attachTimeout; this.attachTimer = new Timer(AttachTimerCallback, null, attachTimeout, detachPingInterval); } } private void Detach() { Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "WDE: DebugController.Detach():")); using (new DebuggerThreadMarker()) { lock (this.syncRoot) { AppDomain.CurrentDomain.AssemblyLoad -= OnAssemblyLoad; // See comments in Attach(). if (this.isZombie || !this.isAttached) return; this.isAttached = false; // Undone: AkashS - At this point wait for all event handling to complete to avoid exceptions. this.programId = Guid.Empty; if (this.debugControllerThread != null) { this.debugControllerThread.StopThread(); this.debugControllerThread = null; } if (this.attachTimer != null) { this.attachTimer.Change(Timeout.Infinite, Timeout.Infinite); this.attachTimer = null; } RemotingServices.Disconnect(this); if (this.channel != null) { ChannelServices.UnregisterChannel(this.channel); this.channel = null; } this.controllerConduit = null; this.eventConduitAttached.Reset(); this.instanceTable = null; this.typeToGuid = null; this.xomlHashToGuid = null; // Do this only after we perform the previous cleanup! Otherwise // we may get exceptions from the runtime that may cause the cleanup // to not happen. if (!this.serviceContainer.IsZombie) { foreach (WorkflowInstance instance in this.serviceContainer.GetLoadedWorkflows()) { WorkflowExecutor executor = instance.GetWorkflowResourceUNSAFE(); using (executor.ExecutorLock.Enter()) { if (executor.IsInstanceValid) executor.WorkflowExecutionEvent -= OnInstanceEvent; } } this.serviceContainer.WorkflowExecutorInitializing -= InstanceInitializing; this.serviceContainer.DefinitionDispenser.WorkflowDefinitionLoaded -= ScheduleTypeLoaded; } } } } private void AttachTimerCallback(object state) { Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "WDE: DebugController.AttachTimerCallback():")); try { lock (this.syncRoot) { // See comments in Attach(). if (this.isZombie || !this.isAttached) return; if (!Debugger.IsAttached) { // The debugger had attached and has now detached, so cleanup, or Attach() was called on the // Program Node, but the process of attach failed thereafter and so we were never actually // debugged. this.attachTimer.Change(Timeout.Infinite, Timeout.Infinite); Detach(); } } } catch { // Avoid throwing unhandled exceptions! } } private void OnDomainUnload(object sender, System.EventArgs e) { Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "WDE: DebugController.OnDomainUnload():")); Stop(null, default(WorkflowRuntimeEventArgs)); } #endregion #region Methods for the DE public void AttachToConduit(Uri url) { if (url == null) throw new ArgumentNullException("url"); try { using (new DebuggerThreadMarker()) { try { RegistryKey debugEngineSubKey = Registry.LocalMachine.OpenSubKey(RegistryKeys.DebuggerSubKey); if (debugEngineSubKey != null) { string controllerConduitTypeName = debugEngineSubKey.GetValue(ControllerConduitTypeName, String.Empty) as string; if (!String.IsNullOrEmpty(controllerConduitTypeName) && Type.GetType(controllerConduitTypeName) != null) this.controllerConduit = Activator.GetObject(Type.GetType(controllerConduitTypeName), url.AbsolutePath) as IWorkflowDebugger; } } catch { } if (this.controllerConduit == null) { const string controllerConduitTypeFormat = "Microsoft.Workflow.DebugEngine.ControllerConduit, Microsoft.Workflow.DebugController, Version={0}.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"; // Try versions 12.0.0.0, 11.0.0.0, 10.0.0.0 Type controllerConduitType = null; for (int version = 12; controllerConduitType == null && version >= 10; --version) { try { controllerConduitType = Type.GetType(string.Format(CultureInfo.InvariantCulture, controllerConduitTypeFormat, version)); } catch (TypeLoadException) { // Fall back to next-lower version } } if (controllerConduitType != null) { this.controllerConduit = Activator.GetObject(controllerConduitType, url.AbsolutePath) as IWorkflowDebugger; } } Debug.Assert(this.controllerConduit != null, "Failed to create Controller Conduit"); if (this.controllerConduit == null) return; this.eventLock = new object(); // Race Condition: // We hook up to the AssemblyLoad event, the Schedule events and Instance events handler // before we iterate over all loaded assemblies. This means that we need to deal with duplicates in the // debugee. // Race Condition: // Further the order in which we hook up handlers/iterate is important to avoid ----s if the events fire // before the iterations complete. We need to hook and iterate over the assemblies, then the schedule // types and finally the instances. This guarantees that we always have all the assemblies when we load // schedules types and we always have all the schedule types when we load instances. AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoad; foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) { if (!assembly.IsDynamic && !(assembly is System.Reflection.Emit.AssemblyBuilder) && !(string.IsNullOrEmpty(assembly.Location))) { this.controllerConduit.AssemblyLoaded(this.programId, assembly.Location, assembly.GlobalAssemblyCache); } } this.serviceContainer.DefinitionDispenser.WorkflowDefinitionLoaded += ScheduleTypeLoaded; // In here we load all schedule types defined as they are - with no dynamic updates ReadOnlyCollection types; ReadOnlyCollection values; this.serviceContainer.DefinitionDispenser.GetWorkflowTypes(out types, out values); for (int i = 0; i < types.Count; i++) { Type scheduleType = types[i]; Activity rootActivity = values[i]; LoadExistingScheduleType(GetScheduleTypeId(scheduleType), scheduleType, false, rootActivity); } ReadOnlyCollection keys; this.serviceContainer.DefinitionDispenser.GetWorkflowDefinitions(out keys, out values); for (int i = 0; i < keys.Count; i++) { byte[] scheduleDefHash = keys[i]; Activity rootActivity = values[i]; Activity workflowDefinition = (Activity)rootActivity.GetValue(Activity.WorkflowDefinitionProperty); ArrayList changeActions = null; if (workflowDefinition != null) changeActions = (ArrayList)workflowDefinition.GetValue(WorkflowChanges.WorkflowChangeActionsProperty); LoadExistingScheduleType(GetScheduleTypeId(scheduleDefHash), rootActivity.GetType(), (changeActions != null && changeActions.Count != 0), rootActivity); } this.serviceContainer.WorkflowExecutorInitializing += InstanceInitializing; foreach (WorkflowInstance instance in this.serviceContainer.GetLoadedWorkflows()) { WorkflowExecutor executor = instance.GetWorkflowResourceUNSAFE(); using (executor.ExecutorLock.Enter()) { LoadExistingInstance(instance, true); } } this.eventConduitAttached.Set(); } } catch (Exception e) { // Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches // and closes the remoting channel. Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "WDE: Failure in DebugController.AttachToConduit: {0}, Call stack:{1}", e.Message, e.StackTrace)); } } #endregion #region Methods for the Runtime private void Start(object source, WorkflowRuntimeEventArgs e) { Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "WDE: DebugController.ServiceContainerStarted():")); this.isZombie = false; this.isAttached = false; this.eventConduitAttached = new ManualResetEvent(false); this.isServiceContainerStarting = true; bool published = this.programPublisher.Publish(this); // If the debugger is already attached, then the DE will invoke AttachToConduit() on a separate thread. // We need to wait for that to happen to prevent new instances being created and causing a ----. See // comments in ControllerConduit.ProgramCreated(). However, if the DE never calls AttachToConduit(), // and the detaches instead, we set a wait timeout to that of our Attach Timer. // Note that when we publish the program node, if the debugger is attached, isAttached will be set to true // when the debugger calls Attach() on the Program Node! while (published && this.isAttached && !this.eventConduitAttached.WaitOne(attachTimeout, false)); this.isServiceContainerStarting = false; } private void Stop(object source, WorkflowRuntimeEventArgs e) { Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "WDE: DebugController.ServiceContainerStopped():")); try { lock (this.syncRoot) { Detach(); this.programPublisher.Unpublish(); // See comments in Attach(). this.isZombie = true; } } catch { // Do not throw exceptions back! } } internal void Close() { //Unregister from Appdomain event to remove ourselves from GCRoot. AppDomain.CurrentDomain.ProcessExit -= OnDomainUnload; AppDomain.CurrentDomain.DomainUnload -= OnDomainUnload; if (!this.isZombie) { Stop(null, new WorkflowRuntimeEventArgs(false)); } } private void OnInstanceEvent(object sender, WorkflowExecutor.WorkflowExecutionEventArgs e) { switch (e.EventType) { case WorkflowEventInternal.Completed: InstanceCompleted(sender, new WorkflowEventArgs(((WorkflowExecutor)sender).WorkflowInstance)); break; case WorkflowEventInternal.Terminated: InstanceTerminated(sender, new WorkflowEventArgs(((WorkflowExecutor)sender).WorkflowInstance)); break; case WorkflowEventInternal.Unloaded: InstanceUnloaded(sender, new WorkflowEventArgs(((WorkflowExecutor)sender).WorkflowInstance)); break; case WorkflowEventInternal.Changed: OnWorkflowChanged(sender, e); break; case WorkflowEventInternal.HandlerInvoking: OnHandlerInvoking(sender, e); break; case WorkflowEventInternal.HandlerInvoked: OnHandlerInvoked(sender, e); break; case WorkflowEventInternal.ActivityStatusChange: OnActivityStatusChanged(sender, (WorkflowExecutor.ActivityStatusChangeEventArgs)e); break; case WorkflowEventInternal.ActivityExecuting: OnActivityExecuting(sender, (WorkflowExecutor.ActivityExecutingEventArgs)e); break; default: return; } } private void InstanceInitializing(object sender, WorkflowRuntime.WorkflowExecutorInitializingEventArgs e) { try { if (e.Loading) LoadExistingInstance(((WorkflowExecutor)sender).WorkflowInstance, true); else LoadExistingInstance(((WorkflowExecutor)sender).WorkflowInstance, false); } catch { // Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches // and closes the remoting channel. } } private void InstanceCompleted(object sender, WorkflowEventArgs args) { try { UnloadExistingInstance(args.WorkflowInstance); } catch { // Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches // and closes the remoting channel. } } private void InstanceTerminated(object sender, WorkflowEventArgs args) { try { UnloadExistingInstance(args.WorkflowInstance); } catch { // Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches // and closes the remoting channel. } } private void InstanceUnloaded(object sender, WorkflowEventArgs args) { // Treat this as if the instance completed so that it won't show up in the UI anymore. try { UnloadExistingInstance(args.WorkflowInstance); } catch { // Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches // and closes the remoting channel. } } private void ScheduleTypeLoaded(object sender, WorkflowDefinitionEventArgs args) { try { if (args.WorkflowType != null) { Activity rootActivity = ((WorkflowRuntime)sender).DefinitionDispenser.GetWorkflowDefinition(args.WorkflowType); LoadExistingScheduleType(GetScheduleTypeId(args.WorkflowType), args.WorkflowType, false, rootActivity); } else { Activity rootActivity = ((WorkflowRuntime)sender).DefinitionDispenser.GetWorkflowDefinition(args.WorkflowDefinitionHashCode); LoadExistingScheduleType(GetScheduleTypeId(args.WorkflowDefinitionHashCode), rootActivity.GetType(), false, rootActivity); } } catch { // Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches // and closes the remoting channel. } } private void OnActivityExecuting(object sender, WorkflowExecutor.ActivityExecutingEventArgs eventArgs) { if (this.isZombie || !this.isAttached) return; try { lock (this.eventLock) { IWorkflowCoreRuntime workflowCoreRuntime = (IWorkflowCoreRuntime)sender; Guid scheduleTypeId = GetScheduleTypeId(workflowCoreRuntime); // When the activity starts executing, update its handler list for stepping. EnumerateEventHandlersForActivity(scheduleTypeId, eventArgs.Activity); this.controllerConduit.BeforeActivityStatusChanged(this.programId, scheduleTypeId, workflowCoreRuntime.InstanceID, eventArgs.Activity.QualifiedName, GetHierarchicalId(eventArgs.Activity), eventArgs.Activity.ExecutionStatus, GetContextId(eventArgs.Activity)); this.controllerConduit.ActivityStatusChanged(this.programId, scheduleTypeId, workflowCoreRuntime.InstanceID, eventArgs.Activity.QualifiedName, GetHierarchicalId(eventArgs.Activity), eventArgs.Activity.ExecutionStatus, GetContextId(eventArgs.Activity)); } } catch { // Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches // and closes the remoting channel. } } private void OnActivityStatusChanged(object sender, WorkflowExecutor.ActivityStatusChangeEventArgs eventArgs) { if (this.isZombie || !this.isAttached) return; try { lock (this.eventLock) { // We will receive an event when Activity.Execute() is about to be called. if (eventArgs.Activity.ExecutionStatus == ActivityExecutionStatus.Executing) return; IWorkflowCoreRuntime workflowCoreRuntime = (IWorkflowCoreRuntime)sender; Guid scheduleTypeId = GetScheduleTypeId(workflowCoreRuntime); // When the activity starts executing, update its handler list for stepping. if (eventArgs.Activity.ExecutionStatus == ActivityExecutionStatus.Executing) EnumerateEventHandlersForActivity(scheduleTypeId, eventArgs.Activity); this.controllerConduit.BeforeActivityStatusChanged(this.programId, scheduleTypeId, workflowCoreRuntime.InstanceID, eventArgs.Activity.QualifiedName, GetHierarchicalId(eventArgs.Activity), eventArgs.Activity.ExecutionStatus, GetContextId(eventArgs.Activity)); this.controllerConduit.ActivityStatusChanged(this.programId, scheduleTypeId, workflowCoreRuntime.InstanceID, eventArgs.Activity.QualifiedName, GetHierarchicalId(eventArgs.Activity), eventArgs.Activity.ExecutionStatus, GetContextId(eventArgs.Activity)); } } catch { // Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches // and closes the remoting channel. } } private void OnHandlerInvoking(object sender, EventArgs eventArgs) { // Undone: AkashS - We need to remove EnumerateHandlersForActivity() and set the CPDE // breakpoints from here. This is handle the cases where event handlers are modified // at runtime. } private void OnHandlerInvoked(object sender, EventArgs eventArgs) { if (this.isZombie || !this.isAttached) return; try { lock (this.eventLock) { IWorkflowCoreRuntime workflowCoreRuntime = sender as IWorkflowCoreRuntime; this.controllerConduit.HandlerInvoked(this.programId, workflowCoreRuntime.InstanceID, NativeMethods.GetCurrentThreadId(), GetHierarchicalId(workflowCoreRuntime.CurrentActivity)); } } catch { // Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches // and closes the remoting channel. } } private void OnWorkflowChanged(object sender, EventArgs eventArgs) { if (this.isZombie || !this.isAttached) return; try { lock (this.eventLock) { IWorkflowCoreRuntime workflowCoreRuntime = (IWorkflowCoreRuntime)sender; // Get cached old root activity. Activity oldRootActivity = this.instanceTable.GetRootActivity(workflowCoreRuntime.InstanceID); Guid scheduleTypeId = workflowCoreRuntime.InstanceID; // From now on we will treat the instance id as a dynamic schedule type id. LoadExistingScheduleType(scheduleTypeId, oldRootActivity.GetType(), true, oldRootActivity); // And now reload the instance. this.instanceTable.UpdateRootActivity(workflowCoreRuntime.InstanceID, oldRootActivity); // The DE will update the schedule type on the thread that is running the instance. // DE should be called after the instance table entry is replaced. this.controllerConduit.InstanceDynamicallyUpdated(this.programId, workflowCoreRuntime.InstanceID, scheduleTypeId); } } catch { // Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches // and closes the remoting channel. } } #endregion #region Helper methods and properties // Callers of this method should acquire the executor lock only if they // are not being called in the runtime thread.(bug 17231). private void LoadExistingInstance(WorkflowInstance instance, bool attaching) { WorkflowExecutor executor = instance.GetWorkflowResourceUNSAFE(); if (!executor.IsInstanceValid) return; IWorkflowCoreRuntime runtimeService = (IWorkflowCoreRuntime)executor; Activity rootActivity = runtimeService.RootActivity; Guid scheduleTypeId = GetScheduleTypeId(runtimeService); // If we are just attaching, need to LoadExistingScheduleType with the dynamic definition and type // since the OnDynamicUpdateEvent has never been executed. if (attaching && runtimeService.IsDynamicallyUpdated) LoadExistingScheduleType(scheduleTypeId, rootActivity.GetType(), true, rootActivity); // Add to the InstanceTable before firing the DE event ! this.instanceTable.AddInstance(instance.InstanceId, rootActivity); this.controllerConduit.InstanceCreated(this.programId, instance.InstanceId, scheduleTypeId); // Take a lock so that SetInitialActivityStatus is always called before next status events. lock (this.eventLock) { executor.WorkflowExecutionEvent += OnInstanceEvent; foreach (Activity activity in DebugController.WalkActivityTree(rootActivity)) { #if false // ReplicatorActivity replicator = activity as ReplicatorActivity; if (replicator != null) { foreach (Activity queuedChildActivity in replicator.DynamicActivities) activityQueue.Enqueue(queuedChildActivity); } else #endif UpdateActivityStatus(scheduleTypeId, instance.InstanceId, activity); } ActivityExecutionContext rootExecutionContext = new ActivityExecutionContext(rootActivity); foreach (ActivityExecutionContext executionContext in DebugController.WalkExecutionContextTree(rootExecutionContext)) { Activity instanceActivity = executionContext.Activity; foreach (Activity childInstance in DebugController.WalkActivityTree(instanceActivity)) { UpdateActivityStatus(scheduleTypeId, instance.InstanceId, childInstance); } } } } private void UpdateActivityStatus(Guid scheduleTypeId, Guid instanceId, Activity activity) { if (activity == null) throw new ArgumentNullException("activity"); // first update its handler list if (activity.ExecutionStatus == ActivityExecutionStatus.Executing) EnumerateEventHandlersForActivity(scheduleTypeId, activity); //report only states different from the initialized if (activity.ExecutionStatus != ActivityExecutionStatus.Initialized) { Activity contextActivity = ContextActivityUtils.ContextActivity(activity); int context = ContextActivityUtils.ContextId(contextActivity); this.controllerConduit.SetInitialActivityStatus(this.programId, scheduleTypeId, instanceId, activity.QualifiedName, GetHierarchicalId(activity), activity.ExecutionStatus, context); } } private static IEnumerable WalkActivityTree(Activity rootActivity) { if (rootActivity == null || !rootActivity.Enabled) yield break; // Return self yield return rootActivity; // Go through the children as well if (rootActivity is CompositeActivity) { foreach (Activity childActivity in ((CompositeActivity)rootActivity).Activities) { foreach (Activity nestedChild in WalkActivityTree(childActivity)) yield return nestedChild; } } } private static IEnumerable WalkExecutionContextTree(ActivityExecutionContext rootContext) { if (rootContext == null) yield break; yield return rootContext; foreach (ActivityExecutionContext executionContext in rootContext.ExecutionContextManager.ExecutionContexts) { foreach (ActivityExecutionContext nestedContext in WalkExecutionContextTree(executionContext)) yield return nestedContext; } yield break; } private void UnloadExistingInstance(WorkflowInstance instance) { // Fire DE event before removing from the InstanceTable! this.controllerConduit.InstanceCompleted(this.programId, instance.InstanceId); this.instanceTable.RemoveInstance(instance.InstanceId); } private void LoadExistingScheduleType(Guid scheduleTypeId, Type scheduleType, bool isDynamic, Activity rootActivity) { if (rootActivity == null) throw new InvalidOperationException(); using (StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture)) { using (XmlWriter xmlWriter = Helpers.CreateXmlWriter(stringWriter)) { WorkflowMarkupSerializer serializer = new WorkflowMarkupSerializer(); serializer.Serialize(xmlWriter, rootActivity); string fileName = null; string md5Digest = null; Attribute[] attributes = scheduleType.GetCustomAttributes(typeof(WorkflowMarkupSourceAttribute), false) as Attribute[]; if (attributes != null && attributes.Length == 1) { fileName = ((WorkflowMarkupSourceAttribute)attributes[0]).FileName; md5Digest = ((WorkflowMarkupSourceAttribute)attributes[0]).MD5Digest; } this.controllerConduit.ScheduleTypeLoaded(this.programId, scheduleTypeId, scheduleType.Assembly.FullName, fileName, md5Digest, isDynamic, scheduleType.FullName, scheduleType.Name, stringWriter.ToString()); } } } private string GetHierarchicalId(Activity activity) { string id = string.Empty; while (activity != null) { string iterationId = string.Empty; Activity contextActivity = ContextActivityUtils.ContextActivity(activity); int context = ContextActivityUtils.ContextId(contextActivity); iterationId = activity.Name + ((context > 1 && activity == contextActivity) ? "(" + context + ")" : string.Empty); id = (id.Length > 0) ? iterationId + "." + id : iterationId; activity = activity.Parent; } return id; } private int GetContextId(Activity activity) { Activity contextActivity = ContextActivityUtils.ContextActivity(activity); return ContextActivityUtils.ContextId(contextActivity); } private Guid GetScheduleTypeId(IWorkflowCoreRuntime workflowCoreRuntime) { Activity rootActivity = workflowCoreRuntime.RootActivity; if (workflowCoreRuntime.IsDynamicallyUpdated) return workflowCoreRuntime.InstanceID; else if (string.IsNullOrEmpty(rootActivity.GetValue(Activity.WorkflowXamlMarkupProperty) as string)) return GetScheduleTypeId(rootActivity.GetType()); else return GetScheduleTypeId(rootActivity.GetValue(WorkflowDefinitionDispenser.WorkflowDefinitionHashCodeProperty) as byte[]); } private Guid GetScheduleTypeId(Type scheduleType) { // We cannot use the GUID from the type because that is not guaranteed to be unique, especially when // multiple versions are loaded and the stamps a GuidAttribute. lock (this.typeToGuid) { if (!this.typeToGuid.ContainsKey(scheduleType)) this.typeToGuid[scheduleType] = Guid.NewGuid(); return (Guid)this.typeToGuid[scheduleType]; } } private Guid GetScheduleTypeId(byte[] scheduleDefHashCode) { // We use the same hashtable to store schedule definition to Guid mapping. lock (this.xomlHashToGuid) { if (!this.xomlHashToGuid.ContainsKey(scheduleDefHashCode)) this.xomlHashToGuid[scheduleDefHashCode] = Guid.NewGuid(); return (Guid)this.xomlHashToGuid[scheduleDefHashCode]; } } private void OnAssemblyLoad(object sender, AssemblyLoadEventArgs args) { // Call assembly load on the conduit for assemblies loaded from disk. if (args.LoadedAssembly.Location != String.Empty) { try { this.controllerConduit.AssemblyLoaded(this.programId, args.LoadedAssembly.Location, args.LoadedAssembly.GlobalAssemblyCache); } catch { // Don't throw exceptions to the CLR. Ignore exceptions that may occur if the debugger detaches // and closes the remoting channel. } } } private void EnumerateEventHandlersForActivity(Guid scheduleTypeId, Activity activity) { List handlerMethods = new List(); MethodInfo getInvocationListMethod = activity.GetType().GetMethod("System.Workflow.ComponentModel.IDependencyObjectAccessor.GetInvocationList", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy); foreach (EventInfo eventInfo in activity.GetType().GetEvents(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy)) { DependencyProperty dependencyEvent = DependencyProperty.FromName(eventInfo.Name, activity.GetType()); if (dependencyEvent != null) { try { MethodInfo boundGetInvocationListMethod = getInvocationListMethod.MakeGenericMethod(new Type[] { dependencyEvent.PropertyType }); foreach (Delegate handler in (boundGetInvocationListMethod.Invoke(activity, new object[] { dependencyEvent }) as Delegate[])) { MethodInfo handlerMethodInfo = handler.Method; ActivityHandlerDescriptor handlerMethod; handlerMethod.Name = handlerMethodInfo.DeclaringType.FullName + "." + handlerMethodInfo.Name; handlerMethod.Token = handlerMethodInfo.MetadataToken; handlerMethods.Add(handlerMethod); } } catch { } } } this.controllerConduit.UpdateHandlerMethodsForActivity(this.programId, scheduleTypeId, activity.QualifiedName, handlerMethods); } #endregion } }