File: System\Activities\Debugger\DebugManager.cs
Project: ndp\cdf\src\NetFx40\System.Activities\System.Activities.csproj (System.Activities)
//-----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//-----------------------------------------------------------------------------
namespace System.Activities.Debugger
{
    using System;
    using System.Activities.Expressions;
    using System.Activities.Hosting;
    using System.Activities.Runtime;
    using System.Activities.Statements;
    using System.Activities.XamlIntegration;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Runtime;
 
    [DebuggerNonUserCode]
    class DebugManager
    {
        static StateManager.DynamicModuleManager dynamicModuleManager;
        WorkflowInstance host;
        StateManager stateManager;
        Dictionary<object, State> states;
        Dictionary<int, Stack<Activity>> runningThreads;
        InstrumentationTracker instrumentationTracker;
        List<string> temporaryFiles;
 
        public DebugManager(Activity root, string moduleNamePrefix, string typeNamePrefix, string auxiliaryThreadName, bool breakOnStartup,
            WorkflowInstance host, bool debugStartedAtRoot) :
            this(root, moduleNamePrefix, typeNamePrefix, auxiliaryThreadName, breakOnStartup, host, debugStartedAtRoot, false)
        {
        }
 
        internal DebugManager(Activity root, string moduleNamePrefix, string typeNamePrefix, string auxiliaryThreadName, bool breakOnStartup, 
            WorkflowInstance host, bool debugStartedAtRoot, bool resetDynamicModule)
        {
            if (resetDynamicModule)
            {
                dynamicModuleManager = null;
            }
 
            if (dynamicModuleManager == null)
            {
                dynamicModuleManager = new StateManager.DynamicModuleManager(moduleNamePrefix);
            }
 
            this.stateManager = new StateManager(
                new StateManager.Properties
                    {
                        ModuleNamePrefix = moduleNamePrefix,
                        TypeNamePrefix = typeNamePrefix,
                        AuxiliaryThreadName = auxiliaryThreadName,
                        BreakOnStartup = breakOnStartup
                    },
                    debugStartedAtRoot, dynamicModuleManager);
            
            this.states = new Dictionary<object, State>();
            this.runningThreads = new Dictionary<int, Stack<Activity>>();
            this.instrumentationTracker = new InstrumentationTracker(root);
            this.host = host;
        }
 
        // Whether we're priming the background thread (in Attach To Process case).
        public bool IsPriming
        {
            set { this.stateManager.IsPriming = value; }
        }
 
        // Whether debugging is done from the start of the root workflow,
        // contrast to attaching into the middle of a running workflow.
        bool DebugStartedAtRoot
        {
            get
            {
                return this.stateManager.DebugStartedAtRoot;
            }
        }
 
        internal void Instrument(Activity activity)
        {
            bool isTemporaryFile = false;
            string sourcePath = null;
            bool instrumentationFailed = false;
            Dictionary<string, byte[]> checksumCache = null;
            try
            {
                byte[] checksum;
                Dictionary<object, SourceLocation> sourceLocations = SourceLocationProvider.GetSourceLocations(activity, out sourcePath, out isTemporaryFile, out checksum);
                if (checksum != null)
                {
                    checksumCache = new Dictionary<string, byte[]>();
                    checksumCache.Add(sourcePath.ToUpperInvariant(), checksum);
                }
                Instrument(activity, sourceLocations, Path.GetFileNameWithoutExtension(sourcePath), checksumCache);
            }
            catch (Exception ex)
            {
                instrumentationFailed = true;
                Trace.WriteLine(SR.DebugInstrumentationFailed(ex.Message));
 
                if (Fx.IsFatal(ex))
                {
                    throw;
                }
            }
 
            List<Activity> sameSourceActivities = this.instrumentationTracker.GetSameSourceSubRoots(activity);
            this.instrumentationTracker.MarkInstrumented(activity);
 
            foreach (Activity sameSourceActivity in sameSourceActivities)
            {
                if (!instrumentationFailed)
                {
                    MapInstrumentationStates(activity, sameSourceActivity);
                }
                // Mark it as instrumentated, even though it fails so it won't be
                // retried.
                this.instrumentationTracker.MarkInstrumented(sameSourceActivity);
            }
 
            if (isTemporaryFile)
            {
                if (this.temporaryFiles == null)
                {
                    this.temporaryFiles = new List<string>();
                }
                Fx.Assert(!string.IsNullOrEmpty(sourcePath), "SourcePath cannot be null for temporary file");
                this.temporaryFiles.Add(sourcePath);
            }
        }
 
        // Workflow rooted at rootActivity1 and rootActivity2 have same source file, but they
        // are two different instantiation.
        // rootActivity1 has been instrumented and its instrumentation states can be
        // re-used by rootActivity2.
        //
        // MapInstrumentationStates will walk both Workflow trees in parallel and map every 
        // state for activities in rootActivity1 to corresponding activities in rootActivity2.
        void MapInstrumentationStates(Activity rootActivity1, Activity rootActivity2)
        {
            Queue<KeyValuePair<Activity, Activity>> pairsRemaining = new Queue<KeyValuePair<Activity, Activity>>();
 
            pairsRemaining.Enqueue(new KeyValuePair<Activity, Activity>(rootActivity1, rootActivity2));
            HashSet<Activity> visited = new HashSet<Activity>();
            KeyValuePair<Activity, Activity> currentPair;
            State state;
 
            while (pairsRemaining.Count > 0)
            {
                currentPair = pairsRemaining.Dequeue();
                Activity activity1 = currentPair.Key;
                Activity activity2 = currentPair.Value;
 
                if (this.states.TryGetValue(activity1, out state))
                {
                    if (this.states.ContainsKey(activity2))
                    {
                        Trace.WriteLine("Workflow", SR.DuplicateInstrumentation(activity2.DisplayName));
                    }
                    else
                    {
                        // Map activity2 to the same state.
                        this.states.Add(activity2, state);
                    }
                }
                //Some activities may not have corresponding Xaml node, e.g. ActivityFaultedOutput.
 
                visited.Add(activity1);
 
                // This to avoid comparing any value expression with DesignTimeValueExpression (in designer case).
                IEnumerator<Activity> enumerator1 = WorkflowInspectionServices.GetActivities(activity1).GetEnumerator();
                IEnumerator<Activity> enumerator2 = WorkflowInspectionServices.GetActivities(activity2).GetEnumerator();
 
                bool hasNextItem1 = enumerator1.MoveNext();
                bool hasNextItem2 = enumerator2.MoveNext();
 
                while (hasNextItem1 && hasNextItem2)
                {
                    if (!visited.Contains(enumerator1.Current))  // avoid adding the same activity (e.g. some default implementation).
                    {
                        if (enumerator1.Current.GetType() != enumerator2.Current.GetType())
                        {
                            // Give debugger log instead of just asserting; to help user find out mismatch problem.
                            Trace.WriteLine(
                                "Unmatched type: " + enumerator1.Current.GetType().FullName +
                                " vs " + enumerator2.Current.GetType().FullName + "\n");
                        }
                        pairsRemaining.Enqueue(new KeyValuePair<Activity, Activity>(enumerator1.Current, enumerator2.Current));
                    }
 
                    hasNextItem1 = enumerator1.MoveNext();
                    hasNextItem2 = enumerator2.MoveNext();
                }
 
                // If enumerators do not finish at the same time, then they have unmatched number of activities.
                // Give debugger log instead of just asserting; to help user find out mismatch problem.
                if (hasNextItem1 || hasNextItem2)
                {
                    Trace.WriteLine("Workflow", "Unmatched number of children\n");
                }
            }
        }
        
 
        // Main instrumentation.
        // Currently the typeNamePrefix is used to notify the Designer of which file to show.
        // This will no longer necessary when the callstack API can give us the source line 
        // information.
        public void Instrument(Activity rootActivity, Dictionary<object, SourceLocation> sourceLocations, string typeNamePrefix, Dictionary<string, byte[]> checksumCache)
        {
            Queue<KeyValuePair<Activity, string>> pairsRemaining = new Queue<KeyValuePair<Activity, string>>();
 
            string name;
            Activity activity = rootActivity;
            KeyValuePair<Activity, string> pair = new KeyValuePair<Activity, string>(activity, string.Empty);
            pairsRemaining.Enqueue(pair);
            HashSet<string> existingNames = new HashSet<string>();
            HashSet<Activity> visited = new HashSet<Activity>();
            SourceLocation sourceLocation;
 
            while (pairsRemaining.Count > 0)
            {
                pair = pairsRemaining.Dequeue();
                activity = pair.Key;
                string parentName = pair.Value;
                string displayName = activity.DisplayName;
                
                // If no DisplayName, then use the type name.
                if (string.IsNullOrEmpty(displayName))
                {
                    displayName = activity.GetType().Name;
                }
 
                if (parentName == string.Empty)
                {   // the root
                    name = displayName;
                }
                else
                {
                    name = string.Format(CultureInfo.InvariantCulture, "{0}.{1}", parentName, displayName);
                }
 
                int i = 0;
                while (existingNames.Contains(name))
                {
                    ++i;
                    name = string.Format(CultureInfo.InvariantCulture, "{0}.{1}{2}", parentName, displayName, i.ToString(CultureInfo.InvariantCulture));
                }
 
                existingNames.Add(name);
 
                visited.Add(activity);
 
                if (sourceLocations.TryGetValue(activity, out sourceLocation))
                {
                    object[] objects = activity.GetType().GetCustomAttributes(typeof(DebuggerStepThroughAttribute), false);
                    if ((objects == null || objects.Length == 0))
                    {
                        Instrument(activity, sourceLocation, name);
                    }
                }
 
                foreach (Activity childActivity in WorkflowInspectionServices.GetActivities(activity))
                {
                    if (!visited.Contains(childActivity))
                    {
                        pairsRemaining.Enqueue(new KeyValuePair<Activity, string>(childActivity, name));
                    }
                }
            }
            this.stateManager.Bake(typeNamePrefix, checksumCache);
        }
 
        // Exiting the DebugManager.
        // Delete all temporary files 
        public void Exit()
        {
            if (this.temporaryFiles != null)
            {
                foreach (string temporaryFile in this.temporaryFiles)
                {
                    // Clean up published source.
                    try
                    {
                        File.Delete(temporaryFile);
                    }
                    catch (IOException)
                    {
                        // ---- IOException silently.
                    }
                    this.temporaryFiles = null;
                }
            }
            this.stateManager.ExitThreads(); // State manager is still keep for the session in SessionStateManager
            this.stateManager = null;
        }
 
        void Instrument(Activity activity, SourceLocation sourceLocation, string name)
        {
            Fx.Assert(activity != null, "activity can't be null");
            Fx.Assert(sourceLocation != null, "sourceLocation can't be null");
            if (this.states.ContainsKey(activity))
            {
                Trace.WriteLine(SR.DuplicateInstrumentation(activity.DisplayName));
            }
            else
            {
                State activityState = this.stateManager.DefineStateWithDebugInfo(sourceLocation, name);
                this.states.Add(activity, activityState);
            }
        }
 
        // Test whether activity has been instrumented.
        // If not, try to instrument it.
        // It will return true if instrumentation is already done or
        // instrumentation is succesful.  False otherwise.
        bool EnsureInstrumented(Activity activity)
        {
            // This is the most common case, we will find the instrumentation.
            if (this.states.ContainsKey(activity))
            {
                return true;
            }
            
            // No states correspond to this yet.
            if (this.instrumentationTracker.IsUninstrumentedSubRoot(activity))
            {
                Instrument(activity);
                return this.states.ContainsKey(activity);
            }
            else
            {
                return false;
            }
        }
 
        // Primitive EnterState
        void EnterState(int threadId, Activity activity, Dictionary<string, object> locals)
        {
            Fx.Assert(activity != null, "activity cannot be null");
            this.Push(threadId, activity);
 
            State activityState;
            if (this.states.TryGetValue(activity, out activityState))
            {
                this.stateManager.EnterState(threadId, activityState, locals);
            }
            else
            {
                Fx.Assert(false, "Uninstrumented activity is disallowed: " + activity.DisplayName);
            }
        }
        public void OnEnterState(ActivityInstance instance)
        {
            Fx.Assert(instance != null, "ActivityInstance cannot be null");
            Activity activity = instance.Activity;
 
            if (this.EnsureInstrumented(activity))
            {
                this.EnterState(GetOrCreateThreadId(activity, instance), activity, GenerateLocals(instance));
            }
        }
 
        [SuppressMessage(FxCop.Category.Usage, FxCop.Rule.ReviewUnusedParameters)]
        public void OnEnterState(Activity expression, ActivityInstance instance, LocationEnvironment environment)
        {
            if (this.EnsureInstrumented(expression))
            {
                this.EnterState(GetOrCreateThreadId(expression, instance), expression, GenerateLocals(instance));
            }
        }
 
        void LeaveState(Activity activity)
        {
            Fx.Assert(activity != null, "Activity cannot be null");
            int threadId = GetExecutingThreadId(activity, true);
 
            // If debugging was not started from the root, then threadId should not be < 0.
            Fx.Assert(!this.DebugStartedAtRoot || threadId >= 0, "Leaving from an unknown state");
 
            if (threadId >= 0) 
            {
                State activityState;
                if (this.states.TryGetValue(activity, out activityState))
                {
                    this.stateManager.LeaveState(threadId, activityState);
                }
                else
                {
                    Fx.Assert(false, "Uninstrumented activity is disallowed: " + activity.DisplayName);
                }
                this.Pop(threadId);
            }
        }
 
        public void OnLeaveState(ActivityInstance activityInstance)
        {
            Fx.Assert(activityInstance != null, "ActivityInstance cannot be null");
            if (this.EnsureInstrumented(activityInstance.Activity))
            {
                this.LeaveState(activityInstance.Activity);
            }
        }
 
        static Dictionary<string, object> GenerateLocals(ActivityInstance instance)
        {
            Dictionary<string, object> locals = new Dictionary<string, object>();
            locals.Add("debugInfo", new DebugInfo(instance));
            return locals;
        }
 
        void Push(int threadId, Activity activity)
        {
            ((Stack<Activity>)this.runningThreads[threadId]).Push(activity);
        }
 
        void Pop(int threadId)
        {
            Stack<Activity> stack = this.runningThreads[threadId];
            stack.Pop();
            if (stack.Count == 0)
            {
                this.stateManager.Exit(threadId);
                this.runningThreads.Remove(threadId);
            }
        }
 
        // Given an activity, return the thread id where it is currently
        // executed (on the top of the callstack).
        // Boolean "strict" parameter determine whether the activity itself should
        // be on top of the stack.
        // Strict checking is needed in the case of "Leave"-ing a state.
        // Non-strict checking is needed for "Enter"-ing a state, since the direct parent of 
        // the activity may not yet be executed (e.g. the activity is an argument of another activity,
        // the activity is "enter"-ed even though the direct parent is not yet "enter"-ed.
        int GetExecutingThreadId(Activity activity, bool strict)
        {
            int threadId = -1;
 
            foreach (KeyValuePair<int, Stack<Activity>> entry in this.runningThreads)
            {
                Stack<Activity> threadStack = entry.Value;
                if (threadStack.Peek() == activity)
                {
                    threadId = entry.Key;
                    break;
                }
            }
 
            if (threadId < 0 && !strict)
            {
                foreach (KeyValuePair<int, Stack<Activity>> entry in this.runningThreads)
                {
                    Stack<Activity> threadStack = entry.Value;
                    Activity topActivity = threadStack.Peek();
                    if (!IsParallelActivity(topActivity) && IsAncestorOf(threadStack.Peek(), activity))
                    {
                        threadId = entry.Key;
                        break;
                    }
                }
            }
            return threadId;
        }
 
        static bool IsAncestorOf(Activity ancestorActivity, Activity activity)
        {
            Fx.Assert(activity != null, "IsAncestorOf: Cannot pass null as activity");
            Fx.Assert(ancestorActivity != null, "IsAncestorOf: Cannot pass null as ancestorActivity");
            activity = activity.Parent;
            while (activity != null && activity != ancestorActivity && !IsParallelActivity(activity))
            {
                activity = activity.Parent;
            }
            return (activity == ancestorActivity);
        }
 
        static bool IsParallelActivity(Activity activity)
        {
            Fx.Assert(activity != null, "IsParallel: Cannot pass null as activity");
            return activity is Parallel ||
                    (activity.GetType().IsGenericType && activity.GetType().GetGenericTypeDefinition() == typeof(ParallelForEach<>));
        }
 
        // Get threads currently executing the parent of the given activity, 
        // if none then create a new one and prep the call stack to current state.
        int GetOrCreateThreadId(Activity activity, ActivityInstance instance)
        {
            int threadId = -1;
            if (activity.Parent != null && !IsParallelActivity(activity.Parent))
            {
                threadId = GetExecutingThreadId(activity.Parent, false);
            }
            if (threadId < 0)
            {
                threadId = CreateLogicalThread(activity, instance, false);
            }
            return threadId;
        }
 
        // Create logical thread and bring its call stack to reflect call from
        // the root up to (but not including) the instance.
        // If the activity is an expression though, then the call stack will also include the instance
        // (since it is the parent of the expression).
        int CreateLogicalThread(Activity activity, ActivityInstance instance, bool primeCurrentInstance)
        {
            Stack<ActivityInstance> ancestors = null;
 
            if (!this.DebugStartedAtRoot)
            {
                ancestors = new Stack<ActivityInstance>();
 
                if (activity != instance.Activity || primeCurrentInstance)
                {   // This mean that activity is an expression and 
                    // instance is the parent of this expression.
                   
                    Fx.Assert(primeCurrentInstance || (activity is ActivityWithResult), "Expect an ActivityWithResult");
                    Fx.Assert(primeCurrentInstance || (activity.Parent == instance.Activity), "Argument Expression is not given correct parent instance");
                    if (primeCurrentInstance || !IsParallelActivity(instance.Activity))
                    {
                        ancestors.Push(instance);
                    }
                }
 
                ActivityInstance instanceParent = instance.Parent;
                while (instanceParent != null && !IsParallelActivity(instanceParent.Activity))
                {
                    ancestors.Push(instanceParent);
                    instanceParent = instanceParent.Parent;
                }
 
                if (instanceParent != null && IsParallelActivity(instanceParent.Activity))
                {
                    // Ensure thread is created for the parent (a Parallel activity).
                    int parentThreadId = GetExecutingThreadId(instanceParent.Activity, false);
                    if (parentThreadId < 0)
                    {
                        parentThreadId = CreateLogicalThread(instanceParent.Activity, instanceParent, true);
                        Fx.Assert(parentThreadId > 0, "Parallel main thread can't be created");
                    }
                }
            }
 
            string threadName = "DebuggerThread:";
            if (activity.Parent != null)
            {
                threadName += activity.Parent.DisplayName;
            }
            else // Special case for the root of WorklowService that does not have a parent.
            {   
                threadName += activity.DisplayName;
            }
 
            int newThreadId = this.stateManager.CreateLogicalThread(threadName);
            Stack<Activity> newStack = new Stack<Activity>();
            this.runningThreads.Add(newThreadId, newStack);
 
            if (!this.DebugStartedAtRoot && ancestors != null)
            { // Need to create callstack to current activity.                        
                PrimeCallStack(newThreadId, ancestors);
            }
            
            return newThreadId;
        }
 
        // Prime the call stack to contains all the ancestors of this instance.
        // Note: the call stack will not include the current instance.
        void PrimeCallStack(int threadId, Stack<ActivityInstance> ancestors)
        {
            Fx.Assert(!this.DebugStartedAtRoot, "Priming should not be called if the debugging is attached from the start of the workflow");
            bool currentIsPrimingValue = this.stateManager.IsPriming;
            this.stateManager.IsPriming = true;
            while (ancestors.Count > 0)
            {
                ActivityInstance currentInstance = ancestors.Pop();
                if (EnsureInstrumented(currentInstance.Activity))
                {
                    this.EnterState(threadId, currentInstance.Activity, GenerateLocals(currentInstance));
                }
            }
            this.stateManager.IsPriming = currentIsPrimingValue;
        }
    }
}