File: System\Runtime\ActionItem.cs
Project: ndp\cdf\src\System.ServiceModel.Internals\System.ServiceModel.Internals.csproj (System.ServiceModel.Internals)
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------
 
namespace System.Runtime
{
    using System.Diagnostics;
    using System.Runtime.Diagnostics;
    using System.Security;
    using System.Threading;
 
    abstract class ActionItem
    {
        [Fx.Tag.SecurityNote(Critical = "Stores the security context, used later in binding back into")]
        [SecurityCritical]
        SecurityContext context;
        bool isScheduled;
 
        bool lowPriority;
 
        protected ActionItem()
        {
        }
 
        public bool LowPriority
        {
            get
            {
                return this.lowPriority;
            }
            protected set
            {
                this.lowPriority = value;
            }
        }
 
        public static void Schedule(Action<object> callback, object state)
        {
            Schedule(callback, state, false);
        }
        [Fx.Tag.SecurityNote(Critical = "Calls into critical method ScheduleCallback",
            Safe = "Schedule invoke of the given delegate under the current context")]
        [SecuritySafeCritical]
        public static void Schedule(Action<object> callback, object state, bool lowPriority)
        {
            Fx.Assert(callback != null, "A null callback was passed for Schedule!");
 
            if (PartialTrustHelpers.ShouldFlowSecurityContext ||
                WaitCallbackActionItem.ShouldUseActivity ||
                Fx.Trace.IsEnd2EndActivityTracingEnabled)
            {
                new DefaultActionItem(callback, state, lowPriority).Schedule();
            }
            else
            {
                ScheduleCallback(callback, state, lowPriority);
            }
        }
 
        [Fx.Tag.SecurityNote(Critical = "Called after applying the user context on the stack or (potentially) " +
            "without any user context on the stack")]
        [SecurityCritical]
        protected abstract void Invoke();
 
        [Fx.Tag.SecurityNote(Critical = "Access critical field context and critical property " +
            "CallbackHelper.InvokeWithContextCallback, calls into critical method " +
            "PartialTrustHelpers.CaptureSecurityContextNoIdentityFlow, calls into critical method ScheduleCallback; " +
            "since the invoked method and the capturing of the security contex are de-coupled, can't " +
            "be treated as safe")]
        [SecurityCritical]
        protected void Schedule()
        {
            if (isScheduled)
            {
                throw Fx.Exception.AsError(new InvalidOperationException(InternalSR.ActionItemIsAlreadyScheduled));
            }
 
            this.isScheduled = true;
            if (PartialTrustHelpers.ShouldFlowSecurityContext)
            {
                this.context = PartialTrustHelpers.CaptureSecurityContextNoIdentityFlow();
            }
            if (this.context != null)
            {
                ScheduleCallback(CallbackHelper.InvokeWithContextCallback);
            }
            else
            {
                ScheduleCallback(CallbackHelper.InvokeWithoutContextCallback);
            }
        }
        [Fx.Tag.SecurityNote(Critical = "Access critical field context and critical property " +
            "CallbackHelper.InvokeWithContextCallback, calls into critical method ScheduleCallback; " +
            "since nothing is known about the given context, can't be treated as safe")]
        [SecurityCritical]
        protected void ScheduleWithContext(SecurityContext context)
        {
            if (context == null)
            {
                throw Fx.Exception.ArgumentNull("context");
            }
            if (isScheduled)
            {
                throw Fx.Exception.AsError(new InvalidOperationException(InternalSR.ActionItemIsAlreadyScheduled));
            }
 
            this.isScheduled = true;
            this.context = context.CreateCopy();
            ScheduleCallback(CallbackHelper.InvokeWithContextCallback);
        }
        [Fx.Tag.SecurityNote(Critical = "Access critical property CallbackHelper.InvokeWithoutContextCallback, " +
            "Calls into critical method ScheduleCallback; not bound to a security context")]
        [SecurityCritical]
        protected void ScheduleWithoutContext()
        {
            if (isScheduled)
            {
                throw Fx.Exception.AsError(new InvalidOperationException(InternalSR.ActionItemIsAlreadyScheduled));
            }
 
            this.isScheduled = true;
            ScheduleCallback(CallbackHelper.InvokeWithoutContextCallback);
        }
 
        [Fx.Tag.SecurityNote(Critical = "Calls into critical methods IOThreadScheduler.ScheduleCallbackNoFlow, " +
            "IOThreadScheduler.ScheduleCallbackLowPriNoFlow")]
        [SecurityCritical]
        static void ScheduleCallback(Action<object> callback, object state, bool lowPriority)
        {
            Fx.Assert(callback != null, "Cannot schedule a null callback");
            if (lowPriority)
            {
                IOThreadScheduler.ScheduleCallbackLowPriNoFlow(callback, state);
            }
            else
            {
                IOThreadScheduler.ScheduleCallbackNoFlow(callback, state);
            }
        }
 
        [Fx.Tag.SecurityNote(Critical = "Extract the security context stored and reset the critical field")]
        [SecurityCritical]
        SecurityContext ExtractContext()
        {
            Fx.Assert(this.context != null, "Cannot bind to a null context; context should have been set by now");
            Fx.Assert(this.isScheduled, "Context is extracted only while the object is scheduled");
            SecurityContext result = this.context;
            this.context = null;
            return result;
        }
 
        [Fx.Tag.SecurityNote(Critical = "Calls into critical static method ScheduleCallback")]
        [SecurityCritical]
        void ScheduleCallback(Action<object> callback)
        {
            ScheduleCallback(callback, this, this.lowPriority);
        }
 
        [SecurityCritical]
        static class CallbackHelper
        {
            [Fx.Tag.SecurityNote(Critical = "Stores a delegate to a critical method")]
            static Action<object> invokeWithContextCallback;
            [Fx.Tag.SecurityNote(Critical = "Stores a delegate to a critical method")]
            static Action<object> invokeWithoutContextCallback;
            [Fx.Tag.SecurityNote(Critical = "Stores a delegate to a critical method")]
            static ContextCallback onContextAppliedCallback;
            [Fx.Tag.SecurityNote(Critical = "Provides access to a critical field; Initialize it with " +
                "a delegate to a critical method")]
            public static Action<object> InvokeWithContextCallback
            {
                get
                {
                    if (invokeWithContextCallback == null)
                    {
                        invokeWithContextCallback = new Action<object>(InvokeWithContext);
                    }
                    return invokeWithContextCallback;
                }
            }
 
            [Fx.Tag.SecurityNote(Critical = "Provides access to a critical field; Initialize it with " +
                "a delegate to a critical method")]
            public static Action<object> InvokeWithoutContextCallback
            {
                get
                {
                    if (invokeWithoutContextCallback == null)
                    {
                        invokeWithoutContextCallback = new Action<object>(InvokeWithoutContext);
                    }
                    return invokeWithoutContextCallback;
                }
            }
            [Fx.Tag.SecurityNote(Critical = "Provides access to a critical field; Initialize it with " +
                "a delegate to a critical method")]
            public static ContextCallback OnContextAppliedCallback
            {
                get
                {
                    if (onContextAppliedCallback == null)
                    {
                        onContextAppliedCallback = new ContextCallback(OnContextApplied);
                    }
                    return onContextAppliedCallback;
                }
            }
            [Fx.Tag.SecurityNote(Critical = "Called by the scheduler without any user context on the stack")]
            static void InvokeWithContext(object state)
            {
                SecurityContext context = ((ActionItem)state).ExtractContext();
                SecurityContext.Run(context, OnContextAppliedCallback, state);
            }
 
            [Fx.Tag.SecurityNote(Critical = "Called by the scheduler without any user context on the stack")]
            static void InvokeWithoutContext(object state)
            {
                ((ActionItem)state).Invoke();
                ((ActionItem)state).isScheduled = false;
            }
            [Fx.Tag.SecurityNote(Critical = "Called after applying the user context on the stack")]
            static void OnContextApplied(object o)
            {
                ((ActionItem)o).Invoke();
                ((ActionItem)o).isScheduled = false;
            }
        }
 
        class DefaultActionItem : ActionItem
        {
            [Fx.Tag.SecurityNote(Critical = "Stores a delegate that will be called later, at a particular context")]
            [SecurityCritical]
            Action<object> callback;
            [Fx.Tag.SecurityNote(Critical = "Stores an object that will be passed to the delegate that will be " +
                "called later, at a particular context")]
            [SecurityCritical]
            object state;
 
            bool flowLegacyActivityId;
            Guid activityId;
            EventTraceActivity eventTraceActivity;
 
            [Fx.Tag.SecurityNote(Critical = "Access critical fields callback and state",
                Safe = "Doesn't leak information or resources")]
            [SecuritySafeCritical]
            public DefaultActionItem(Action<object> callback, object state, bool isLowPriority)
            {
                Fx.Assert(callback != null, "Shouldn't instantiate an object to wrap a null callback");
                base.LowPriority = isLowPriority;
                this.callback = callback;
                this.state = state;
                if (WaitCallbackActionItem.ShouldUseActivity)
                {
                    this.flowLegacyActivityId = true;
                    this.activityId = EtwDiagnosticTrace.ActivityId;
                }
                if (Fx.Trace.IsEnd2EndActivityTracingEnabled)
                {
                    this.eventTraceActivity = EventTraceActivity.GetFromThreadOrCreate();
                    if (TraceCore.ActionItemScheduledIsEnabled(Fx.Trace))
                    {
                        TraceCore.ActionItemScheduled(Fx.Trace, this.eventTraceActivity);
                    }
                }
 
            }
 
            [Fx.Tag.SecurityNote(Critical = "Implements a the critical abstract ActionItem.Invoke method, " +
                "Access critical fields callback and state")]
            [SecurityCritical]
            protected override void Invoke()
            {
                if (this.flowLegacyActivityId || Fx.Trace.IsEnd2EndActivityTracingEnabled)
                {
                    TraceAndInvoke();
                }
                else
                {
                    this.callback(this.state);
                }
            }
 
            [Fx.Tag.SecurityNote(Critical = "Implements a the critical abstract Trace method, " +
                    "Access critical fields callback and state")]
            [SecurityCritical]
            void TraceAndInvoke()
            {
                //
                if (this.flowLegacyActivityId)
                {
                    Guid currentActivityId = EtwDiagnosticTrace.ActivityId;
                    try
                    {
                        EtwDiagnosticTrace.ActivityId = this.activityId;
                        this.callback(this.state);
                    }
                    finally
                    {
                        EtwDiagnosticTrace.ActivityId = currentActivityId;
                    }
                }
                else
                {
                    Guid previous = Guid.Empty;
                    bool restoreActivityId = false;
                    try
                    {
                        if (this.eventTraceActivity != null)
                        {
                            previous = Trace.CorrelationManager.ActivityId;
                            restoreActivityId = true;
                            Trace.CorrelationManager.ActivityId = this.eventTraceActivity.ActivityId;
                            if (TraceCore.ActionItemCallbackInvokedIsEnabled(Fx.Trace))
                            {
                                TraceCore.ActionItemCallbackInvoked(Fx.Trace, this.eventTraceActivity);
                            }
                        }
                        this.callback(this.state);
                    }
                    finally
                    {
                        if (restoreActivityId)
                        {
                            Trace.CorrelationManager.ActivityId = previous;
                        }
                    }
                }
            }
        }
    }
}