File: FrameworkFork\System.ServiceModel\System\ServiceModel\Dispatcher\ImmutableDispatchRuntime.cs
Web Access
Project: src\src\dotnet-svcutil\lib\src\dotnet-svcutil-lib.csproj (dotnet-svcutil-lib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using System.Runtime;
using System.Runtime.Diagnostics;
using System.ServiceModel.Channels;
using System.ServiceModel.Diagnostics;
using System.Text;
using System.Threading.Tasks;
 
namespace System.ServiceModel.Dispatcher
{
    internal class ImmutableDispatchRuntime
    {
        readonly private int _correlationCount;
        readonly private ConcurrencyBehavior _concurrency;
        readonly private IDemuxer _demuxer;
        readonly private ErrorBehavior _error;
        private readonly bool _enableFaults;
        private InstanceBehavior _instance;
        private readonly bool _manualAddressing;
        private readonly ThreadBehavior _thread;
        private readonly bool _validateMustUnderstand;
        private readonly bool _sendAsynchronously;
 
        private readonly MessageRpcProcessor _processMessage1;
        private readonly MessageRpcProcessor _processMessage11;
        private readonly MessageRpcProcessor _processMessage2;
        private readonly MessageRpcProcessor _processMessage3;
        private readonly MessageRpcProcessor _processMessage31;
        private readonly MessageRpcProcessor _processMessage4;
        private readonly MessageRpcProcessor _processMessage41;
        private readonly MessageRpcProcessor _processMessage5;
        private readonly MessageRpcProcessor _processMessage6;
        private readonly MessageRpcProcessor _processMessage7;
        private readonly MessageRpcProcessor _processMessage8;
        private readonly MessageRpcProcessor _processMessage9;
        private readonly MessageRpcProcessor _processMessageCleanup;
        private readonly MessageRpcProcessor _processMessageCleanupError;
 
        private static AsyncCallback s_onReplyCompleted = Fx.ThunkCallback(new AsyncCallback(OnReplyCompletedCallback));
 
        internal ImmutableDispatchRuntime(DispatchRuntime dispatch)
        {
            _concurrency = new ConcurrencyBehavior(dispatch);
            _error = new ErrorBehavior(dispatch.ChannelDispatcher);
            _enableFaults = dispatch.EnableFaults;
            _instance = new InstanceBehavior(dispatch, this);
            _manualAddressing = dispatch.ManualAddressing;
            _thread = new ThreadBehavior(dispatch);
            _sendAsynchronously = dispatch.ChannelDispatcher.SendAsynchronously;
            _correlationCount = dispatch.MaxParameterInspectors;
 
            DispatchOperationRuntime unhandled = new DispatchOperationRuntime(dispatch.UnhandledDispatchOperation, this);
 
            ActionDemuxer demuxer = new ActionDemuxer();
            for (int i = 0; i < dispatch.Operations.Count; i++)
            {
                DispatchOperation operation = dispatch.Operations[i];
                DispatchOperationRuntime operationRuntime = new DispatchOperationRuntime(operation, this);
                demuxer.Add(operation.Action, operationRuntime);
            }
 
            demuxer.SetUnhandled(unhandled);
            _demuxer = demuxer;
 
            _processMessage1 = ProcessMessage1;
            _processMessage11 = ProcessMessage11;
            _processMessage2 = ProcessMessage2;
            _processMessage3 = ProcessMessage3;
            _processMessage31 = ProcessMessage31;
            _processMessage4 = ProcessMessage4;
            _processMessage41 = ProcessMessage41;
            _processMessage5 = ProcessMessage5;
            _processMessage6 = ProcessMessage6;
            _processMessage7 = ProcessMessage7;
            _processMessage8 = ProcessMessage8;
            _processMessage9 = ProcessMessage9;
            _processMessageCleanup = ProcessMessageCleanup;
            _processMessageCleanupError = ProcessMessageCleanupError;
        }
 
        internal int CorrelationCount
        {
            get { return _correlationCount; }
        }
 
        internal bool EnableFaults
        {
            get { return _enableFaults; }
        }
 
        internal bool ManualAddressing
        {
            get { return _manualAddressing; }
        }
 
        internal bool ValidateMustUnderstand
        {
            get { return _validateMustUnderstand; }
        }
 
        internal void AfterReceiveRequest(ref MessageRpc rpc)
        {
            // IDispatchMessageInspector would normally be called here. That interface isn't in contract.
        }
 
        private void Reply(ref MessageRpc rpc)
        {
            rpc.RequestContextThrewOnReply = true;
            rpc.SuccessfullySendReply = false;
 
            try
            {
                rpc.RequestContext.Reply(rpc.Reply, rpc.ReplyTimeoutHelper.RemainingTime());
                rpc.RequestContextThrewOnReply = false;
                rpc.SuccessfullySendReply = true;
 
                if (WcfEventSource.Instance.DispatchMessageStopIsEnabled())
                {
                    WcfEventSource.Instance.DispatchMessageStop(rpc.EventTraceActivity);
                }
            }
            catch (CommunicationException e)
            {
                _error.HandleError(e);
            }
            catch (TimeoutException e)
            {
                _error.HandleError(e);
            }
            catch (Exception e)
            {
                if (Fx.IsFatal(e))
                {
                    throw;
                }
 
                if (!_error.HandleError(e))
                {
                    rpc.RequestContextThrewOnReply = true;
                    rpc.CanSendReply = false;
                }
            }
        }
 
        private void BeginReply(ref MessageRpc rpc)
        {
            bool success = false;
 
            try
            {
                IResumeMessageRpc resume = rpc.Pause();
 
                rpc.AsyncResult = rpc.RequestContext.BeginReply(rpc.Reply, rpc.ReplyTimeoutHelper.RemainingTime(),
                    s_onReplyCompleted, resume);
                success = true;
 
                if (rpc.AsyncResult.CompletedSynchronously)
                {
                    rpc.UnPause();
                }
            }
            catch (CommunicationException e)
            {
                _error.HandleError(e);
            }
            catch (TimeoutException e)
            {
                _error.HandleError(e);
            }
            catch (Exception e)
            {
                if (Fx.IsFatal(e))
                {
                    throw;
                }
 
                if (!_error.HandleError(e))
                {
                    rpc.RequestContextThrewOnReply = true;
                    rpc.CanSendReply = false;
                }
            }
            finally
            {
                if (!success)
                {
                    rpc.UnPause();
                }
            }
        }
 
        internal bool Dispatch(ref MessageRpc rpc, bool isOperationContextSet)
        {
            rpc.ErrorProcessor = _processMessage8;
            rpc.NextProcessor = _processMessage1;
            return rpc.Process(isOperationContextSet);
        }
 
        private bool EndReply(ref MessageRpc rpc)
        {
            bool success = false;
 
            try
            {
                rpc.RequestContext.EndReply(rpc.AsyncResult);
                rpc.RequestContextThrewOnReply = false;
                success = true;
 
                if (WcfEventSource.Instance.DispatchMessageStopIsEnabled())
                {
                    WcfEventSource.Instance.DispatchMessageStop(rpc.EventTraceActivity);
                }
            }
            catch (Exception e)
            {
                if (Fx.IsFatal(e))
                {
                    throw;
                }
 
                _error.HandleError(e);
            }
 
            return success;
        }
 
        private void SetActivityIdOnThread(ref MessageRpc rpc)
        {
            if (FxTrace.Trace.IsEnd2EndActivityTracingEnabled && rpc.EventTraceActivity != null)
            {
                // Propogate the ActivityId to the service operation
                EventTraceActivityHelper.SetOnThread(rpc.EventTraceActivity);
            }
        }
 
        private void TransferChannelFromPendingList(ref MessageRpc rpc)
        {
            if (rpc.Channel.IsPending)
            {
                rpc.Channel.IsPending = false;
 
                ChannelDispatcher channelDispatcher = rpc.Channel.ChannelDispatcher;
                IInstanceContextProvider provider = _instance.InstanceContextProvider;
 
                if (!InstanceContextProviderBase.IsProviderSessionful(provider) &&
                    !InstanceContextProviderBase.IsProviderSingleton(provider))
                {
                    IChannel proxy = rpc.Channel.Proxy as IChannel;
                    if (!rpc.InstanceContext.IncomingChannels.Contains(proxy))
                    {
                        channelDispatcher.Channels.Add(proxy);
                    }
                }
 
                channelDispatcher.PendingChannels.Remove(rpc.Channel.Binder.Channel);
            }
        }
 
        private void AddMessageProperties(Message message, OperationContext context, ServiceChannel replyChannel)
        {
            if (context.InternalServiceChannel == replyChannel)
            {
                if (context.HasOutgoingMessageHeaders)
                {
                    message.Headers.CopyHeadersFrom(context.OutgoingMessageHeaders);
                }
 
                if (context.HasOutgoingMessageProperties)
                {
                    message.Properties.MergeProperties(context.OutgoingMessageProperties);
                }
            }
        }
 
        private static void OnReplyCompletedCallback(IAsyncResult result)
        {
            if (result.CompletedSynchronously)
            {
                return;
            }
 
            IResumeMessageRpc resume = result.AsyncState as IResumeMessageRpc;
 
            if (resume == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(SRServiceModel.SFxInvalidAsyncResultState0);
            }
 
            resume.Resume(result);
        }
 
        private void PrepareReply(ref MessageRpc rpc)
        {
            RequestContext context = rpc.OperationContext.RequestContext;
            Exception exception = null;
            bool thereIsAnUnhandledException = false;
 
            if (!rpc.Operation.IsOneWay)
            {
                if ((context != null) && (rpc.Reply != null))
                {
                    try
                    {
                        rpc.CanSendReply = PrepareAndAddressReply(ref rpc);
                    }
                    catch (Exception e)
                    {
                        if (Fx.IsFatal(e))
                        {
                            throw;
                        }
                        thereIsAnUnhandledException = !_error.HandleError(e);
                        exception = e;
                    }
                }
            }
 
            if (rpc.Operation.IsOneWay)
            {
                rpc.CanSendReply = false;
            }
 
            if (!rpc.Operation.IsOneWay && (context != null) && (rpc.Reply != null))
            {
                if (exception != null)
                {
                    // We don't call ProvideFault again, since we have already passed the
                    // point where SFx addresses the reply, and it is reasonable for
                    // ProvideFault to expect that SFx will address the reply.  Instead
                    // we always just do 'internal server error' processing.
                    rpc.Error = exception;
                    _error.ProvideOnlyFaultOfLastResort(ref rpc);
 
                    try
                    {
                        rpc.CanSendReply = PrepareAndAddressReply(ref rpc);
                    }
                    catch (Exception e)
                    {
                        if (Fx.IsFatal(e))
                        {
                            throw;
                        }
                        _error.HandleError(e);
                    }
                }
            }
            else if ((exception != null) && thereIsAnUnhandledException)
            {
                rpc.Abort();
            }
        }
 
        private bool PrepareAndAddressReply(ref MessageRpc rpc)
        {
            bool canSendReply = true;
 
            if (!_manualAddressing)
            {
                if (!object.ReferenceEquals(rpc.RequestID, null))
                {
                    System.ServiceModel.Channels.RequestReplyCorrelator.PrepareReply(rpc.Reply, rpc.RequestID);
                }
 
                if (!rpc.Channel.HasSession)
                {
                    canSendReply = System.ServiceModel.Channels.RequestReplyCorrelator.AddressReply(rpc.Reply, rpc.ReplyToInfo);
                }
            }
 
            AddMessageProperties(rpc.Reply, rpc.OperationContext, rpc.Channel);
            if (FxTrace.Trace.IsEnd2EndActivityTracingEnabled && rpc.EventTraceActivity != null)
            {
                rpc.Reply.Properties[EventTraceActivity.Name] = rpc.EventTraceActivity;
            }
 
            return canSendReply;
        }
 
        internal DispatchOperationRuntime GetOperation(ref Message message)
        {
            return _demuxer.GetOperation(ref message);
        }
 
        private interface IDemuxer
        {
            DispatchOperationRuntime GetOperation(ref Message request);
        }
 
        internal bool IsConcurrent(ref MessageRpc rpc)
        {
            return _concurrency.IsConcurrent(ref rpc);
        }
 
        internal void ProcessMessage1(ref MessageRpc rpc)
        {
            rpc.NextProcessor = _processMessage11;
 
            if (!rpc.IsPaused)
            {
                ProcessMessage11(ref rpc);
            }
        }
 
        internal void ProcessMessage11(ref MessageRpc rpc)
        {
            rpc.NextProcessor = _processMessage2;
 
            if (rpc.Operation.IsOneWay)
            {
                rpc.RequestContext.Reply(null);
                rpc.OperationContext.RequestContext = null;
            }
            else
            {
                if (!rpc.Channel.IsReplyChannel &&
                    ((object)rpc.RequestID == null) &&
                    (rpc.Operation.Action != MessageHeaders.WildcardAction))
                {
                    CommunicationException error = new CommunicationException(SRServiceModel.SFxOneWayMessageToTwoWayMethod0);
                    throw TraceUtility.ThrowHelperError(error, rpc.Request);
                }
 
                if (!_manualAddressing)
                {
                    EndpointAddress replyTo = rpc.ReplyToInfo.ReplyTo;
                    if (replyTo != null && replyTo.IsNone && rpc.Channel.IsReplyChannel)
                    {
                        CommunicationException error = new CommunicationException(SRServiceModel.SFxRequestReplyNone);
                        throw TraceUtility.ThrowHelperError(error, rpc.Request);
                    }
                }
            }
 
            if (_concurrency.IsConcurrent(ref rpc))
            {
                rpc.Channel.IncrementActivity();
                rpc.SuccessfullyIncrementedActivity = true;
            }
 
            _instance.EnsureInstanceContext(ref rpc);
            this.TransferChannelFromPendingList(ref rpc);
 
            if (!rpc.IsPaused)
            {
                this.ProcessMessage2(ref rpc);
            }
        }
 
        private void ProcessMessage2(ref MessageRpc rpc)
        {
            // Run dispatch message inspectors
            rpc.NextProcessor = _processMessage3;
 
            this.AfterReceiveRequest(ref rpc);
 
            _concurrency.LockInstance(ref rpc);
 
            if (!rpc.IsPaused)
            {
                this.ProcessMessage3(ref rpc);
            }
        }
 
 
        private void ProcessMessage3(ref MessageRpc rpc)
        {
            // Manage transactions, can likely go away
 
            rpc.NextProcessor = _processMessage31;
 
            rpc.SuccessfullyLockedInstance = true;
 
            if (!rpc.IsPaused)
            {
                this.ProcessMessage31(ref rpc);
            }
        }
 
        private void ProcessMessage31(ref MessageRpc rpc)
        {
            // More transaction stuff, can likely go away
            rpc.NextProcessor = _processMessage4;
 
            if (!rpc.IsPaused)
            {
                this.ProcessMessage4(ref rpc);
            }
        }
 
        private void ProcessMessage4(ref MessageRpc rpc)
        {
            // Bind request to synchronization context if needed
 
            rpc.NextProcessor = _processMessage41;
 
            try
            {
                _thread.BindThread(ref rpc);
            }
            catch (Exception e)
            {
                if (Fx.IsFatal(e))
                {
                    throw;
                }
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperFatal(e.Message, e);
            }
 
            if (!rpc.IsPaused)
            {
                this.ProcessMessage41(ref rpc);
            }
        }
 
 
        private void ProcessMessage41(ref MessageRpc rpc)
        {
            rpc.NextProcessor = _processMessage5;
 
            // This needs to happen after LockInstance--LockInstance guarantees
            // in-order delivery, so we can't receive the next message until we
            // have acquired the lock.
            //
            // This also needs to happen after BindThread, since
            // running on UI thread should guarantee in-order delivery if
            // the SynchronizationContext is single threaded.
            if (_concurrency.IsConcurrent(ref rpc))
            {
                rpc.EnsureReceive();
            }
 
            _instance.EnsureServiceInstance(ref rpc);
 
            if (!rpc.IsPaused)
            {
                this.ProcessMessage5(ref rpc);
            }
        }
 
        private void ProcessMessage5(ref MessageRpc rpc)
        {
            rpc.NextProcessor = _processMessage6;
 
            bool success = false;
            try
            {
                // If async call completes in sync, it tells us through the gate below
                rpc.PrepareInvokeContinueGate();
 
                SetActivityIdOnThread(ref rpc);
 
                rpc.Operation.InvokeBegin(ref rpc);
                success = true;
            }
            finally
            {
                try
                {
                    if (rpc.IsPaused)
                    {
                        // Check if the callback produced the async result and set it back on the RPC on this stack 
                        // and proceed only if the gate was signaled by the callback and completed synchronously
                        if (rpc.UnlockInvokeContinueGate(out rpc.AsyncResult))
                        {
                            rpc.UnPause();
                        }
                    }
                }
                catch (Exception e)
                {
                    if (Fx.IsFatal(e))
                    {
                        throw;
                    }
 
                    if (success && !rpc.IsPaused)
                    {
                        throw;
                    }
 
                    _error.HandleError(e);
                }
            }
 
            // Proceed if rpc is unpaused and invoke begin was successful.
            if (!rpc.IsPaused)
            {
                this.ProcessMessage6(ref rpc);
            }
        }
 
        private void ProcessMessage6(ref MessageRpc rpc)
        {
            rpc.NextProcessor = _processMessage7;
 
            try
            {
                _thread.BindEndThread(ref rpc);
            }
            catch (Exception e)
            {
                if (Fx.IsFatal(e))
                {
                    throw;
                }
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperFatal(e.Message, e);
            }
 
            if (!rpc.IsPaused)
            {
                this.ProcessMessage7(ref rpc);
            }
        }
 
        private void ProcessMessage7(ref MessageRpc rpc)
        {
            rpc.NextProcessor = null;
 
            rpc.Operation.InvokeEnd(ref rpc);
 
            // this never pauses
            this.ProcessMessage8(ref rpc);
        }
 
        private void ProcessMessage8(ref MessageRpc rpc)
        {
            rpc.NextProcessor = _processMessage9;
 
            try
            {
                _error.ProvideMessageFault(ref rpc);
            }
            catch (Exception e)
            {
                if (Fx.IsFatal(e))
                {
                    throw;
                }
 
                _error.HandleError(e);
            }
 
            this.PrepareReply(ref rpc);
 
            if (rpc.CanSendReply)
            {
                rpc.ReplyTimeoutHelper = new TimeoutHelper(rpc.Channel.OperationTimeout);
            }
 
            if (!rpc.IsPaused)
            {
                this.ProcessMessage9(ref rpc);
            }
        }
 
        private void ProcessMessage9(ref MessageRpc rpc)
        {
            rpc.NextProcessor = _processMessageCleanup;
 
            if (rpc.CanSendReply)
            {
                if (rpc.Reply != null)
                {
                    TraceUtility.MessageFlowAtMessageSent(rpc.Reply, rpc.EventTraceActivity);
                }
 
                if (_sendAsynchronously)
                {
                    this.BeginReply(ref rpc);
                }
                else
                {
                    this.Reply(ref rpc);
                }
            }
 
            if (!rpc.IsPaused)
            {
                this.ProcessMessageCleanup(ref rpc);
            }
        }
 
        private void ProcessMessageCleanup(ref MessageRpc rpc)
        {
            Fx.Assert(
                !object.ReferenceEquals(rpc.ErrorProcessor, _processMessageCleanupError),
                "ProcessMessageCleanup run twice on the same MessageRpc!");
            rpc.ErrorProcessor = _processMessageCleanupError;
 
            bool replyWasSent = false;
 
            if (rpc.CanSendReply)
            {
                if (_sendAsynchronously)
                {
                    replyWasSent = this.EndReply(ref rpc);
                }
                else
                {
                    replyWasSent = rpc.SuccessfullySendReply;
                }
            }
 
            try
            {
                try
                {
                    if (rpc.DidDeserializeRequestBody)
                    {
                        rpc.Request.Close();
                    }
                }
                catch (Exception e)
                {
                    if (Fx.IsFatal(e))
                    {
                        throw;
                    }
                    _error.HandleError(e);
                }
 
                rpc.DisposeParameters(false); //Dispose all input/output/return parameters
 
                if (rpc.FaultInfo.IsConsideredUnhandled)
                {
                    if (!replyWasSent)
                    {
                        rpc.AbortRequestContext();
                        rpc.AbortChannel();
                    }
                    else
                    {
                        rpc.CloseRequestContext();
                        rpc.CloseChannel();
                    }
                    rpc.AbortInstanceContext();
                }
                else
                {
                    if (rpc.RequestContextThrewOnReply)
                    {
                        rpc.AbortRequestContext();
                    }
                    else
                    {
                        rpc.CloseRequestContext();
                    }
                }
 
 
                if ((rpc.Reply != null) && (rpc.Reply != rpc.ReturnParameter))
                {
                    try
                    {
                        rpc.Reply.Close();
                    }
                    catch (Exception e)
                    {
                        if (Fx.IsFatal(e))
                        {
                            throw;
                        }
                        _error.HandleError(e);
                    }
                }
 
                if ((rpc.FaultInfo.Fault != null) && (rpc.FaultInfo.Fault.State != MessageState.Closed))
                {
                    // maybe ProvideFault gave a Message, but then BeforeSendReply replaced it
                    // in that case, we need to close the one from ProvideFault
                    try
                    {
                        rpc.FaultInfo.Fault.Close();
                    }
                    catch (Exception e)
                    {
                        if (Fx.IsFatal(e))
                        {
                            throw;
                        }
                        _error.HandleError(e);
                    }
                }
 
                try
                {
                    rpc.OperationContext.FireOperationCompleted();
                }
                catch (Exception e)
                {
                    if (Fx.IsFatal(e))
                    {
                        throw;
                    }
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperCallback(e);
                }
 
                _instance.AfterReply(ref rpc, _error);
 
                if (rpc.SuccessfullyLockedInstance)
                {
                    try
                    {
                        _concurrency.UnlockInstance(ref rpc);
                    }
                    catch (Exception e)
                    {
                        if (Fx.IsFatal(e))
                        {
                            throw;
                        }
 
                        Fx.Assert("Exceptions should be caught by callee");
                        rpc.InstanceContext.FaultInternal();
                        _error.HandleError(e);
                    }
                }
 
 
                if (rpc.SuccessfullyIncrementedActivity)
                {
                    try
                    {
                        rpc.Channel.DecrementActivity();
                    }
                    catch (Exception e)
                    {
                        if (Fx.IsFatal(e))
                        {
                            throw;
                        }
                        _error.HandleError(e);
                    }
                }
            }
            finally
            {
                if (rpc.Activity != null && DiagnosticUtility.ShouldUseActivity)
                {
                    rpc.Activity.Stop();
                }
            }
 
            _error.HandleError(ref rpc);
        }
 
        private void ProcessMessageCleanupError(ref MessageRpc rpc)
        {
            _error.HandleError(ref rpc);
        }
 
        private class ActionDemuxer : IDemuxer
        {
            private readonly HybridDictionary _map;
            private DispatchOperationRuntime _unhandled;
 
            internal ActionDemuxer()
            {
                _map = new HybridDictionary();
            }
 
            internal void Add(string action, DispatchOperationRuntime operation)
            {
                if (_map.Contains(action))
                {
                    DispatchOperationRuntime existingOperation = (DispatchOperationRuntime)_map[action];
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(string.Format(SRServiceModel.SFxActionDemuxerDuplicate, existingOperation.Name, operation.Name, action)));
                }
                _map.Add(action, operation);
            }
 
            internal void SetUnhandled(DispatchOperationRuntime operation)
            {
                _unhandled = operation;
            }
 
            public DispatchOperationRuntime GetOperation(ref Message request)
            {
                string action = request.Headers.Action;
                if (action == null)
                {
                    action = MessageHeaders.WildcardAction;
                }
                DispatchOperationRuntime operation = (DispatchOperationRuntime)_map[action];
                if (operation != null)
                {
                    return operation;
                }
 
                return _unhandled;
            }
        }
    }
}