// 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.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Runtime;
using System.Runtime.Diagnostics;
using System.ServiceModel.Description;
using System.ServiceModel.Diagnostics;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Security;
using System.Threading;
using System.Threading.Tasks;

namespace System.ServiceModel.Channels
{
    // This class is sealed because the constructor could call Abort, which is virtual
    internal sealed class ServiceChannel : CommunicationObject, IChannel, IClientChannel, IDuplexContextChannel, IOutputChannel, IRequestChannel, IServiceChannel
    {
        private int _activityCount = 0;
        private bool _allowInitializationUI = true;
        private bool _allowOutputBatching = false;
        private bool _autoClose = true;
        private CallOnceManager _autoDisplayUIManager;
        private CallOnceManager _autoOpenManager;
        private readonly IChannelBinder _binder;
        private readonly ChannelDispatcher _channelDispatcher;
        private ClientRuntime _clientRuntime;
        private readonly bool _closeBinder = true;
        private bool _closeFactory;
        private bool _didInteractiveInitialization;
        private bool _doneReceiving;
        private EndpointDispatcher _endpointDispatcher;
        private bool _explicitlyOpened;
        private ExtensionCollection<IContextChannel> _extensions;
        private readonly ServiceChannelFactory _factory;
        private readonly bool _hasSession;
        private readonly SessionIdleManager _idleManager;
        private InstanceContext _instanceContext;
        private bool _isPending;
        private readonly bool _isReplyChannel;
        private EndpointAddress _localAddress;
        private readonly MessageVersion _messageVersion;
        private readonly bool _openBinder = false;
        private TimeSpan _operationTimeout;
        private object _proxy;
        private bool _hasChannelStartedAutoClosing;
        private bool _hasCleanedUpChannelCollections;
        private EventTraceActivity _eventActivity;

        private EventHandler<UnknownMessageReceivedEventArgs> _unknownMessageReceived;

        private ServiceChannel(IChannelBinder binder, MessageVersion messageVersion, IDefaultCommunicationTimeouts timeouts)
        {
            if (binder == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("binder");
            }

            _messageVersion = messageVersion;
            _binder = binder;
            _isReplyChannel = _binder.Channel is IReplyChannel;

            IChannel innerChannel = binder.Channel;
            _hasSession = (innerChannel is ISessionChannel<IDuplexSession>) ||
                        (innerChannel is ISessionChannel<IInputSession>) ||
                        (innerChannel is ISessionChannel<IOutputSession>);

            IncrementActivity();
            _openBinder = (binder.Channel.State == CommunicationState.Created);

            _operationTimeout = timeouts.SendTimeout;
        }

        internal ServiceChannel(ServiceChannelFactory factory, IChannelBinder binder)
            : this(binder, factory.MessageVersion, factory)
        {
            _factory = factory;
            _clientRuntime = factory.ClientRuntime;

            SetupInnerChannelFaultHandler();

            DispatchRuntime dispatch = factory.ClientRuntime.DispatchRuntime;
            if (dispatch != null)
            {
                _autoClose = dispatch.AutomaticInputSessionShutdown;
            }

            factory.ChannelCreated(this);
        }

        internal ServiceChannel(IChannelBinder binder,
                                EndpointDispatcher endpointDispatcher,
                                ChannelDispatcher channelDispatcher,
                                SessionIdleManager idleManager)
            : this(binder, channelDispatcher.MessageVersion, channelDispatcher.DefaultCommunicationTimeouts)
        {
            if (endpointDispatcher == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("endpointDispatcher");
            }

            _channelDispatcher = channelDispatcher;
            _endpointDispatcher = endpointDispatcher;
            _clientRuntime = endpointDispatcher.DispatchRuntime.CallbackClientRuntime;

            SetupInnerChannelFaultHandler();

            _autoClose = endpointDispatcher.DispatchRuntime.AutomaticInputSessionShutdown;
            _isPending = true;

            IDefaultCommunicationTimeouts timeouts = channelDispatcher.DefaultCommunicationTimeouts;
            _idleManager = idleManager;

            if (!binder.HasSession)
                _closeBinder = false;

            if (_idleManager != null)
            {
                bool didIdleAbort;
                _idleManager.RegisterChannel(this, out didIdleAbort);
                if (didIdleAbort)
                {
                    Abort();
                }
            }
        }

        private CallOnceManager AutoOpenManager
        {
            get
            {
                if (!_explicitlyOpened && (_autoOpenManager == null))
                {
                    EnsureAutoOpenManagers();
                }
                return _autoOpenManager;
            }
        }

        private CallOnceManager AutoDisplayUIManager
        {
            get
            {
                if (!_explicitlyOpened && (_autoDisplayUIManager == null))
                {
                    EnsureAutoOpenManagers();
                }
                return _autoDisplayUIManager;
            }
        }


        internal EventTraceActivity EventActivity
        {
            get
            {
                if (_eventActivity == null)
                {
                    //Take the id on the thread so that we know the initiating operation.
                    _eventActivity = EventTraceActivity.GetFromThreadOrCreate();
                }
                return _eventActivity;
            }
        }

        internal bool CloseFactory
        {
            get { return _closeFactory; }
            set { _closeFactory = value; }
        }

        protected override TimeSpan DefaultCloseTimeout
        {
            get { return CloseTimeout; }
        }

        protected override TimeSpan DefaultOpenTimeout
        {
            get { return OpenTimeout; }
        }

        internal DispatchRuntime DispatchRuntime
        {
            get
            {
                if (_endpointDispatcher != null)
                {
                    return _endpointDispatcher.DispatchRuntime;
                }
                if (_clientRuntime != null)
                {
                    return _clientRuntime.DispatchRuntime;
                }
                return null;
            }
        }

        internal MessageVersion MessageVersion
        {
            get { return _messageVersion; }
        }

        internal IChannelBinder Binder
        {
            get { return _binder; }
        }

        internal TimeSpan CloseTimeout
        {
            get
            {
                if (IsClient)
                {
                    return _factory.InternalCloseTimeout;
                }
                else
                {
                    return ChannelDispatcher.InternalCloseTimeout;
                }
            }
        }

        internal ChannelDispatcher ChannelDispatcher
        {
            get { return _channelDispatcher; }
        }

        internal EndpointDispatcher EndpointDispatcher
        {
            get { return _endpointDispatcher; }
            set
            {
                lock (ThisLock)
                {
                    _endpointDispatcher = value;
                    _clientRuntime = value.DispatchRuntime.CallbackClientRuntime;
                }
            }
        }

        internal ServiceChannelFactory Factory
        {
            get { return _factory; }
        }

        internal IChannel InnerChannel
        {
            get { return _binder.Channel; }
        }

        internal bool IsPending
        {
            get { return _isPending; }
            set { _isPending = value; }
        }

        internal bool HasSession
        {
            get { return _hasSession; }
        }

        internal bool IsClient
        {
            get { return _factory != null; }
        }

        internal bool IsReplyChannel
        {
            get { return _isReplyChannel; }
        }

        public Uri ListenUri
        {
            get
            {
                return _binder.ListenUri;
            }
        }

        public EndpointAddress LocalAddress
        {
            get
            {
                if (_localAddress == null)
                {
                    if (_endpointDispatcher != null)
                    {
                        _localAddress = _endpointDispatcher.EndpointAddress;
                    }
                    else
                    {
                        _localAddress = _binder.LocalAddress;
                    }
                }
                return _localAddress;
            }
        }

        internal TimeSpan OpenTimeout
        {
            get
            {
                if (IsClient)
                {
                    return _factory.InternalOpenTimeout;
                }
                else
                {
                    return ChannelDispatcher.InternalOpenTimeout;
                }
            }
        }

        public TimeSpan OperationTimeout
        {
            get { return _operationTimeout; }
            set
            {
                if (value < TimeSpan.Zero)
                {
                    string message = SR.SFxTimeoutOutOfRange0;
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("value", value, message));
                }
                if (TimeoutHelper.IsTooLarge(value))
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("value", value, SR.SFxTimeoutOutOfRangeTooBig));
                }


                _operationTimeout = value;
            }
        }

        internal object Proxy
        {
            get
            {
                object proxy = _proxy;
                if (proxy != null)
                    return proxy;
                else
                    return this;
            }
            set
            {
                _proxy = value;
                EventSender = value;   // need to use "proxy" as open/close event source
            }
        }

        internal ClientRuntime ClientRuntime
        {
            get { return _clientRuntime; }
        }

        public EndpointAddress RemoteAddress
        {
            get
            {
                IOutputChannel outputChannel = InnerChannel as IOutputChannel;
                if (outputChannel != null)
                    return outputChannel.RemoteAddress;

                IRequestChannel requestChannel = InnerChannel as IRequestChannel;
                if (requestChannel != null)
                    return requestChannel.RemoteAddress;

                return null;
            }
        }

        private ProxyOperationRuntime UnhandledProxyOperation
        {
            get { return ClientRuntime.GetRuntime().UnhandledProxyOperation; }
        }

        public Uri Via
        {
            get
            {
                IOutputChannel outputChannel = InnerChannel as IOutputChannel;
                if (outputChannel != null)
                    return outputChannel.Via;

                IRequestChannel requestChannel = InnerChannel as IRequestChannel;
                if (requestChannel != null)
                    return requestChannel.Via;

                return null;
            }
        }

        internal InstanceContext InstanceContext
        {
            get { return _instanceContext; }
            set { _instanceContext = value; }
        }

        private void SetupInnerChannelFaultHandler()
        {
            // need to call this method after this.binder and this.clientRuntime are set to prevent a potential 
            // NullReferenceException in this method or in the OnInnerChannelFaulted method; 
            // because this method accesses this.binder and OnInnerChannelFaulted accesses this.clientRuntime.
            _binder.Channel.Faulted += OnInnerChannelFaulted;
        }

        private void BindDuplexCallbacks()
        {
            IDuplexChannel duplexChannel = InnerChannel as IDuplexChannel;
            if ((duplexChannel != null) && (_factory != null) && (_instanceContext != null))
            {
                if (_binder is DuplexChannelBinder)
                    ((DuplexChannelBinder)_binder).EnsurePumping();
            }
        }

        internal bool CanCastTo(Type t)
        {
            if (t.IsAssignableFrom(typeof(IClientChannel)))
                return true;

            if (t.IsAssignableFrom(typeof(IDuplexContextChannel)))
                return InnerChannel is IDuplexChannel;

            if (t.IsAssignableFrom(typeof(IServiceChannel)))
                return true;

            return false;
        }

        internal void CompletedIOOperation()
        {
            if (_idleManager != null)
            {
                _idleManager.CompletedActivity();
            }
        }

        private void EnsureAutoOpenManagers()
        {
            lock (ThisLock)
            {
                if (!_explicitlyOpened)
                {
                    if (_autoOpenManager == null)
                    {
                        _autoOpenManager = new CallOnceManager(this, CallOpenOnce.Instance);
                    }
                    if (_autoDisplayUIManager == null)
                    {
                        _autoDisplayUIManager = new CallOnceManager(this, CallDisplayUIOnce.Instance);
                    }
                }
            }
        }

        private void EnsureDisplayUI()
        {
            CallOnceManager manager = AutoDisplayUIManager;
            if (manager != null)
            {
                manager.CallOnce(TimeSpan.MaxValue, null);
            }
            ThrowIfInitializationUINotCalled();
        }

        private IAsyncResult BeginEnsureDisplayUI(AsyncCallback callback, object state)
        {
            CallOnceManager manager = AutoDisplayUIManager;
            if (manager != null)
            {
                return manager.BeginCallOnce(TimeSpan.MaxValue, null, callback, state);
            }
            else
            {
                return new CallOnceCompletedAsyncResult(callback, state);
            }
        }

        private void EndEnsureDisplayUI(IAsyncResult result)
        {
            CallOnceManager manager = AutoDisplayUIManager;
            if (manager != null)
            {
                manager.EndCallOnce(result);
            }
            else
            {
                CallOnceCompletedAsyncResult.End(result);
            }
            ThrowIfInitializationUINotCalled();
        }

        private void EnsureOpened(TimeSpan timeout)
        {
            CallOnceManager manager = AutoOpenManager;
            if (manager != null)
            {
                manager.CallOnce(timeout, _autoDisplayUIManager);
            }

            ThrowIfOpening();
            ThrowIfDisposedOrNotOpen();
        }

        private IAsyncResult BeginEnsureOpened(TimeSpan timeout, AsyncCallback callback, object state)
        {
            CallOnceManager manager = AutoOpenManager;
            if (manager != null)
            {
                return manager.BeginCallOnce(timeout, _autoDisplayUIManager, callback, state);
            }
            else
            {
                ThrowIfOpening();
                ThrowIfDisposedOrNotOpen();

                return new CallOnceCompletedAsyncResult(callback, state);
            }
        }

        private void EndEnsureOpened(IAsyncResult result)
        {
            CallOnceManager manager = AutoOpenManager;
            if (manager != null)
            {
                manager.EndCallOnce(result);
            }
            else
            {
                CallOnceCompletedAsyncResult.End(result);
            }
        }

        public T GetProperty<T>() where T : class
        {
            IChannel innerChannel = InnerChannel;
            if (innerChannel != null)
                return innerChannel.GetProperty<T>();
            return null;
        }

        private void PrepareCall(ProxyOperationRuntime operation, bool oneway, ref ProxyRpc rpc)
        {
            OperationContext context = OperationContext.Current;
            // Doing a request reply callback when dispatching in-order deadlocks.
            // We never receive the reply until we finish processing the current message.
            if (!oneway)
            {
                DispatchRuntime dispatchBehavior = ClientRuntime.DispatchRuntime;
                if ((dispatchBehavior != null) && (dispatchBehavior.ConcurrencyMode == ConcurrencyMode.Single))
                {
                    if ((context != null) && (!context.IsUserContext) && (context.InternalServiceChannel == this))
                    {
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.Format(SR.SFxCallbackRequestReplyInOrder1, typeof(CallbackBehaviorAttribute).Name)));
                    }
                }
            }

            if ((State == CommunicationState.Created) && !operation.IsInitiating)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.Format(SR.SFxNonInitiatingOperation1, operation.Name)));
            }

            if (_hasChannelStartedAutoClosing)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ProtocolException(SR.SFxClientOutputSessionAutoClosed));
            }

            operation.BeforeRequest(ref rpc);
            AddMessageProperties(rpc.Request, context);
            if (!oneway && !ClientRuntime.ManualAddressing && rpc.Request.Version.Addressing != AddressingVersion.None)
            {
                RequestReplyCorrelator.PrepareRequest(rpc.Request);

                MessageHeaders headers = rpc.Request.Headers;
                EndpointAddress localAddress = LocalAddress;
                EndpointAddress replyTo = headers.ReplyTo;

                if (replyTo == null)
                {
                    headers.ReplyTo = localAddress ?? EndpointAddress.AnonymousAddress;
                }

                if (IsClient && (localAddress != null) && !localAddress.IsAnonymous)
                {
                    Uri localUri = localAddress.Uri;

                    if ((replyTo != null) && !replyTo.IsAnonymous && (localUri != replyTo.Uri))
                    {
                        string text = SR.Format(SR.SFxRequestHasInvalidReplyToOnClient, replyTo.Uri, localUri);
                        Exception error = new InvalidOperationException(text);
                        throw TraceUtility.ThrowHelperError(error, rpc.Request);
                    }

                    EndpointAddress faultTo = headers.FaultTo;
                    if ((faultTo != null) && !faultTo.IsAnonymous && (localUri != faultTo.Uri))
                    {
                        string text = SR.Format(SR.SFxRequestHasInvalidFaultToOnClient, faultTo.Uri, localUri);
                        Exception error = new InvalidOperationException(text);
                        throw TraceUtility.ThrowHelperError(error, rpc.Request);
                    }
                }
            }

            if (TraceUtility.MessageFlowTracingOnly)
            {
                //always set a new ID if none provided
            }

            if (rpc.Activity != null)
            {
                TraceUtility.SetActivity(rpc.Request, rpc.Activity);
                if (TraceUtility.ShouldPropagateActivity)
                {
                    TraceUtility.AddActivityHeader(rpc.Request);
                }
            }
            else if (TraceUtility.PropagateUserActivity || TraceUtility.ShouldPropagateActivity)
            {
                TraceUtility.AddAmbientActivityToMessage(rpc.Request);
            }
            operation.Parent.BeforeSendRequest(ref rpc);


            //Attach and transfer Activity
            if (FxTrace.Trace.IsEnd2EndActivityTracingEnabled)
            {
                TraceClientOperationPrepared(ref rpc);
            }

            TraceUtility.MessageFlowAtMessageSent(rpc.Request, rpc.EventTraceActivity);

            if (MessageLogger.LogMessagesAtServiceLevel)
            {
                MessageLogger.LogMessage(ref rpc.Request, (oneway ? MessageLoggingSource.ServiceLevelSendDatagram : MessageLoggingSource.ServiceLevelSendRequest) | MessageLoggingSource.LastChance);
            }
        }

        private void TraceClientOperationPrepared(ref ProxyRpc rpc)
        {
            //Retrieve the old id on the RPC and attach the id on the message since we have a message id now.
            Guid previousId = rpc.EventTraceActivity != null ? rpc.EventTraceActivity.ActivityId : Guid.Empty;
            EventTraceActivity requestActivity = EventTraceActivityHelper.TryExtractActivity(rpc.Request);
            if (requestActivity == null)
            {
                requestActivity = EventTraceActivity.GetFromThreadOrCreate();
                EventTraceActivityHelper.TryAttachActivity(rpc.Request, requestActivity);
            }
            rpc.EventTraceActivity = requestActivity;

            if (WcfEventSource.Instance.ClientOperationPreparedIsEnabled())
            {
                string remoteAddress = string.Empty;
                if (RemoteAddress != null && RemoteAddress.Uri != null)
                {
                    remoteAddress = RemoteAddress.Uri.AbsoluteUri;
                }
                WcfEventSource.Instance.ClientOperationPrepared(rpc.EventTraceActivity,
                                            rpc.Action,
                                            _clientRuntime.ContractName,
                                            remoteAddress,
                                            previousId);
            }
        }

        internal static IAsyncResult BeginCall(ServiceChannel channel, ProxyOperationRuntime operation, object[] ins, AsyncCallback callback, object asyncState)
        {
            Fx.Assert(channel != null, "'channel' MUST NOT be NULL.");
            Fx.Assert(operation != null, "'operation' MUST NOT be NULL.");
            return channel.BeginCall(operation.Action, operation.IsOneWay, operation, ins, channel._operationTimeout, callback, asyncState);
        }

        internal IAsyncResult BeginCall(string action, bool oneway, ProxyOperationRuntime operation, object[] ins, AsyncCallback callback, object asyncState)
        {
            return BeginCall(action, oneway, operation, ins, _operationTimeout, callback, asyncState);
        }

        internal IAsyncResult BeginCall(string action, bool oneway, ProxyOperationRuntime operation, object[] ins, TimeSpan timeout, AsyncCallback callback, object asyncState)
        {
            ThrowIfDisallowedInitializationUI();
            ThrowIfIdleAborted(operation);
            ThrowIfIsConnectionOpened(operation);

            ServiceModelActivity serviceModelActivity = null;

            if (DiagnosticUtility.ShouldUseActivity)
            {
                serviceModelActivity = ServiceModelActivity.CreateActivity(true);
                callback = TraceUtility.WrapExecuteUserCodeAsyncCallback(callback);
            }

            SendAsyncResult result;

            using (Activity boundOperation = ServiceModelActivity.BoundOperation(serviceModelActivity, true))
            {
                if (DiagnosticUtility.ShouldUseActivity)
                {
                    ServiceModelActivity.Start(serviceModelActivity, SR.Format(SR.ActivityProcessAction, action), ActivityType.ProcessAction);
                }

                result = new SendAsyncResult(this, operation, action, ins, oneway, timeout, callback, asyncState);
                if (DiagnosticUtility.ShouldUseActivity)
                {
                    result.Rpc.Activity = serviceModelActivity;
                }

                TraceServiceChannelCallStart(result.Rpc.EventTraceActivity, false);

                result.Begin();
            }

            return result;
        }

        internal object Call(string action, bool oneway, ProxyOperationRuntime operation, object[] ins, object[] outs)
        {
            return Call(action, oneway, operation, ins, outs, _operationTimeout);
        }

        internal object Call(string action, bool oneway, ProxyOperationRuntime operation, object[] ins, object[] outs, TimeSpan timeout)
        {
            ThrowIfDisallowedInitializationUI();
            ThrowIfIdleAborted(operation);
            ThrowIfIsConnectionOpened(operation);

            ProxyRpc rpc = new ProxyRpc(this, operation, action, ins, timeout);

            TraceServiceChannelCallStart(rpc.EventTraceActivity, true);

            using (rpc.Activity = DiagnosticUtility.ShouldUseActivity ? ServiceModelActivity.CreateBoundedActivity() : null)
            {
                if (DiagnosticUtility.ShouldUseActivity)
                {
                    ServiceModelActivity.Start(rpc.Activity, SR.Format(SR.ActivityProcessAction, action), ActivityType.ProcessAction);
                }

                PrepareCall(operation, oneway, ref rpc);

                if (!_explicitlyOpened)
                {
                    EnsureDisplayUI();
                    EnsureOpened(rpc.TimeoutHelper.RemainingTime());
                }
                else
                {
                    ThrowIfOpening();
                    ThrowIfDisposedOrNotOpen();
                }

                try
                {
                    ConcurrencyBehavior.UnlockInstanceBeforeCallout(OperationContext.Current);

                    if (oneway)
                    {
                        _binder.Send(rpc.Request, rpc.TimeoutHelper.RemainingTime());
                    }
                    else
                    {
                        rpc.Reply = _binder.Request(rpc.Request, rpc.TimeoutHelper.RemainingTime());

                        if (rpc.Reply == null)
                        {
                            ThrowIfFaulted();
                            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CommunicationException(SR.SFxServerDidNotReply));
                        }
                    }
                }
                finally
                {
                    CompletedIOOperation();
                    CallOnceManager.SignalNextIfNonNull(_autoOpenManager);
                    ConcurrencyBehavior.LockInstanceAfterCallout(OperationContext.Current);
                }

                rpc.OutputParameters = outs;
                HandleReply(operation, ref rpc);
            }
            return rpc.ReturnValue;
        }

        internal object EndCall(string action, object[] outs, IAsyncResult result)
        {
            SendAsyncResult sendResult = result as SendAsyncResult;
            if (sendResult == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentException(SR.SFxInvalidCallbackIAsyncResult));

            using (ServiceModelActivity rpcActivity = sendResult.Rpc.Activity)
            {
                using (ServiceModelActivity.BoundOperation(rpcActivity, true))
                {
                    if (sendResult.Rpc.Activity != null && DiagnosticUtility.ShouldUseActivity)
                    {
                        sendResult.Rpc.Activity.Resume();
                    }
                    if (sendResult.Rpc.Channel != this)
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("result", SR.AsyncEndCalledOnWrongChannel);

                    if (action != MessageHeaders.WildcardAction && action != sendResult.Rpc.Action)
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("result", SR.AsyncEndCalledWithAnIAsyncResult);

                    SendAsyncResult.End(sendResult);

                    sendResult.Rpc.OutputParameters = outs;
                    HandleReply(sendResult.Rpc.Operation, ref sendResult.Rpc);

                    if (sendResult.Rpc.Activity != null)
                    {
                        sendResult.Rpc.Activity = null;
                    }
                    return sendResult.Rpc.ReturnValue;
                }
            }
        }

        internal void DecrementActivity()
        {
            int updatedActivityCount = Interlocked.Decrement(ref _activityCount);

            if (!((updatedActivityCount >= 0)))
            {
                throw Fx.AssertAndThrowFatal("ServiceChannel.DecrementActivity: (updatedActivityCount >= 0)");
            }

            if (updatedActivityCount == 0 && _autoClose)
            {
                try
                {
                    if (State == CommunicationState.Opened)
                    {
                        if (IsClient)
                        {
                            ISessionChannel<IDuplexSession> duplexSessionChannel = InnerChannel as ISessionChannel<IDuplexSession>;
                            if (duplexSessionChannel != null)
                            {
                                _hasChannelStartedAutoClosing = true;
                                duplexSessionChannel.Session.CloseOutputSession(CloseTimeout);
                            }
                        }
                        else
                        {
                            Close(CloseTimeout);
                        }
                    }
                }
                catch (CommunicationException)
                {
                }
                catch (TimeoutException e)
                {
                    if (WcfEventSource.Instance.CloseTimeoutIsEnabled())
                    {
                        WcfEventSource.Instance.CloseTimeout(e.Message);
                    }
                }
                catch (ObjectDisposedException)
                {
                }
                catch (InvalidOperationException)
                {
                }
            }
        }

        internal void FireUnknownMessageReceived(Message message)
        {
            EventHandler<UnknownMessageReceivedEventArgs> handler = _unknownMessageReceived;
            if (handler != null)
                handler(_proxy, new UnknownMessageReceivedEventArgs(message));
        }

        private TimeoutException GetOpenTimeoutException(TimeSpan timeout)
        {
            EndpointAddress address = RemoteAddress ?? LocalAddress;
            if (address != null)
            {
                return new TimeoutException(SR.Format(SR.TimeoutServiceChannelConcurrentOpen2, address, timeout));
            }
            else
            {
                return new TimeoutException(SR.Format(SR.TimeoutServiceChannelConcurrentOpen1, timeout));
            }
        }

        internal void HandleReceiveComplete(RequestContext context)
        {
            if (context == null && HasSession)
            {
                bool first;
                lock (ThisLock)
                {
                    first = !_doneReceiving;
                    _doneReceiving = true;
                }

                if (first)
                {
                    DispatchRuntime dispatchBehavior = ClientRuntime.DispatchRuntime;
                    DecrementActivity();
                }
            }
        }

        private void HandleReply(ProxyOperationRuntime operation, ref ProxyRpc rpc)
        {
            try
            {
                //set the ID after response
                if (TraceUtility.MessageFlowTracingOnly && rpc.ActivityId != Guid.Empty)
                {
                    DiagnosticTraceBase.ActivityId = rpc.ActivityId;
                }

                if (rpc.Reply != null)
                {
                    TraceUtility.MessageFlowAtMessageReceived(rpc.Reply, null, rpc.EventTraceActivity, false);

                    if (MessageLogger.LogMessagesAtServiceLevel)
                    {
                        MessageLogger.LogMessage(ref rpc.Reply, MessageLoggingSource.ServiceLevelReceiveReply | MessageLoggingSource.LastChance);
                    }
                    operation.Parent.AfterReceiveReply(ref rpc);

                    if ((operation.ReplyAction != MessageHeaders.WildcardAction) && !rpc.Reply.IsFault && rpc.Reply.Headers.Action != null)
                    {
                        if (String.CompareOrdinal(operation.ReplyAction, rpc.Reply.Headers.Action) != 0)
                        {
                            Exception error = new ProtocolException(SR.Format(SR.SFxReplyActionMismatch3,
                                                                                  operation.Name,
                                                                                  rpc.Reply.Headers.Action,
                                                                                  operation.ReplyAction));
                            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(error);
                        }
                    }
                    if (operation.DeserializeReply && _clientRuntime.IsFault(ref rpc.Reply))
                    {
                        MessageFault fault = MessageFault.CreateFault(rpc.Reply, _clientRuntime.MaxFaultSize);
                        string action = rpc.Reply.Headers.Action;
                        if (action == rpc.Reply.Version.Addressing.DefaultFaultAction)
                        {
                            action = null;
                        }
                        ThrowIfFaultUnderstood(rpc.Reply, fault, action, rpc.Reply.Version, rpc.Channel.GetProperty<FaultConverter>());
                        FaultException fe = rpc.Operation.FaultFormatter.Deserialize(fault, action);
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperWarning(fe);
                    }

                    operation.AfterReply(ref rpc);
                }
            }
            finally
            {
                if (operation.SerializeRequest)
                {
                    rpc.Request.Close();
                }

                OperationContext operationContext = OperationContext.Current;
                bool consumed = ((rpc.Reply != null) && (rpc.Reply.State != MessageState.Created));

                if ((operationContext != null) && operationContext.IsUserContext)
                {
                    operationContext.SetClientReply(rpc.Reply, consumed);
                }
                else if (consumed)
                {
                    rpc.Reply.Close();
                }

                if (TraceUtility.MessageFlowTracingOnly)
                {
                    if (rpc.ActivityId != Guid.Empty)
                    {
                        //reset the ID as it was created internally - ensures each call is uniquely correlatable
                        DiagnosticTraceBase.ActivityId = Guid.Empty;
                        rpc.ActivityId = Guid.Empty;
                    }
                }
            }

            if (WcfEventSource.Instance.ServiceChannelCallStopIsEnabled())
            {
                string remoteAddress = string.Empty;
                if (RemoteAddress != null && RemoteAddress.Uri != null)
                {
                    remoteAddress = RemoteAddress.Uri.AbsoluteUri;
                }
                WcfEventSource.Instance.ServiceChannelCallStop(rpc.EventTraceActivity, rpc.Action,
                                            _clientRuntime.ContractName,
                                            remoteAddress);
            }
        }

        private void ThrowIfFaultUnderstood(Message reply, MessageFault fault, string action, MessageVersion version, FaultConverter faultConverter)
        {
            Exception exception;
            if (faultConverter != null && faultConverter.TryCreateException(reply, fault, out exception))
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperWarning(exception);
            }

            bool checkSender;
            bool checkReceiver;
            FaultCode code;

            if (version.Envelope == EnvelopeVersion.Soap11)
            {
                checkSender = true;
                checkReceiver = true;
                code = fault.Code;
            }
            else
            {
                checkSender = fault.Code.IsSenderFault;
                checkReceiver = fault.Code.IsReceiverFault;
                code = fault.Code.SubCode;
            }

            if (code == null)
            {
                return;
            }

            if (code.Namespace == null)
            {
                return;
            }

            if (checkSender)
            {
                if (string.Compare(code.Namespace, FaultCodeConstants.Namespaces.NetDispatch, StringComparison.Ordinal) == 0)
                {
                    if (string.Compare(code.Name, FaultCodeConstants.Codes.SessionTerminated, StringComparison.Ordinal) == 0)
                    {
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperWarning(new ChannelTerminatedException(fault.Reason.GetMatchingTranslation(CultureInfo.CurrentCulture).Text));
                    }
                }

                // throw SecurityAccessDeniedException explicitly
                if (string.Compare(code.Namespace, SecurityVersion.Default.HeaderNamespace.Value, StringComparison.Ordinal) == 0)
                {
                    if (string.Compare(code.Name, SecurityVersion.Default.FailedAuthenticationFaultCode.Value, StringComparison.Ordinal) == 0)
                    {
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperWarning(new SecurityAccessDeniedException(fault.Reason.GetMatchingTranslation(CultureInfo.CurrentCulture).Text));
                    }
                }
            }

            if (checkReceiver)
            {
                if (string.Compare(code.Namespace, FaultCodeConstants.Namespaces.NetDispatch, StringComparison.Ordinal) == 0)
                {
                    if (string.Compare(code.Name, FaultCodeConstants.Codes.InternalServiceFault, StringComparison.Ordinal) == 0)
                    {
                        if (HasSession)
                        {
                            Fault();
                        }
                        if (fault.HasDetail)
                        {
                            ExceptionDetail detail = fault.GetDetail<ExceptionDetail>();
                            throw DiagnosticUtility.ExceptionUtility.ThrowHelperWarning(new FaultException<ExceptionDetail>(detail, fault.Reason, fault.Code, action));
                        }
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperWarning(new FaultException(fault, action));
                    }
                    if (string.Compare(code.Name, FaultCodeConstants.Codes.DeserializationFailed, StringComparison.Ordinal) == 0)
                    {
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperWarning(new ProtocolException(
                            fault.Reason.GetMatchingTranslation(CultureInfo.CurrentCulture).Text));
                    }
                }
            }
        }

        private void ThrowIfIdleAborted(ProxyOperationRuntime operation)
        {
            if (_idleManager != null && _idleManager.DidIdleAbort)
            {
                string text = SR.Format(SR.SFxServiceChannelIdleAborted, operation.Name);
                Exception error = new CommunicationObjectAbortedException(text);
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(error);
            }
        }

        private void ThrowIfIsConnectionOpened(ProxyOperationRuntime operation)
        {
            if (operation.IsSessionOpenNotificationEnabled)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                    SR.Format(SR.SFxServiceChannelCannotBeCalledBecauseIsSessionOpenNotificationEnabled, operation.Name, "Action", OperationDescription.SessionOpenedAction, "Open")));
            }
        }

        private void ThrowIfInitializationUINotCalled()
        {
            if (!_didInteractiveInitialization && (ClientRuntime.InteractiveChannelInitializers.Count > 0))
            {
                IInteractiveChannelInitializer example = ClientRuntime.InteractiveChannelInitializers[0];
                string text = SR.Format(SR.SFxInitializationUINotCalled, example.GetType().ToString());
                Exception error = new InvalidOperationException(text);
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(error);
            }
        }

        private void ThrowIfDisallowedInitializationUI()
        {
            if (!_allowInitializationUI)
            {
                ThrowIfDisallowedInitializationUICore();
            }
        }

        private void ThrowIfDisallowedInitializationUICore()
        {
            if (ClientRuntime.InteractiveChannelInitializers.Count > 0)
            {
                IInteractiveChannelInitializer example = ClientRuntime.InteractiveChannelInitializers[0];
                string text = SR.Format(SR.SFxInitializationUIDisallowed, example.GetType().ToString());
                Exception error = new InvalidOperationException(text);
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(error);
            }
        }

        private void ThrowIfOpening()
        {
            if (State == CommunicationState.Opening)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.SFxCannotCallAutoOpenWhenExplicitOpenCalled));
            }
        }

        internal void IncrementActivity()
        {
            Interlocked.Increment(ref _activityCount);
        }

        private void OnInnerChannelFaulted(object sender, EventArgs e)
        {
            Fault();

            if (HasSession)
            {
                DispatchRuntime dispatchRuntime = ClientRuntime.DispatchRuntime;
            }

            if (_autoClose && !IsClient)
            {
                Abort();
            }
        }

        private void AddMessageProperties(Message message, OperationContext context)
        {
            if (_allowOutputBatching)
            {
                message.Properties.AllowOutputBatching = true;
            }

            if (context != null && context.InternalServiceChannel == this)
            {
                if (!context.OutgoingMessageVersion.IsMatch(message.Headers.MessageVersion))
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                        SR.Format(SR.SFxVersionMismatchInOperationContextAndMessage2, context.OutgoingMessageVersion, message.Headers.MessageVersion)
                        ));
                }

                if (context.HasOutgoingMessageHeaders)
                {
                    message.Headers.CopyHeadersFrom(context.OutgoingMessageHeaders);
                }

                if (context.HasOutgoingMessageProperties)
                {
                    message.Properties.CopyProperties(context.OutgoingMessageProperties);
                }
            }
        }

        #region IChannel Members
        public void Send(Message message)
        {
            Send(message, OperationTimeout);
        }

        public void Send(Message message, TimeSpan timeout)
        {
            ProxyOperationRuntime operation = UnhandledProxyOperation;
            Call(message.Headers.Action, true, operation, new object[] { message }, Array.Empty<object>(), timeout);
        }

        public IAsyncResult BeginSend(Message message, AsyncCallback callback, object state)
        {
            return BeginSend(message, OperationTimeout, callback, state);
        }

        public IAsyncResult BeginSend(Message message, TimeSpan timeout, AsyncCallback callback, object state)
        {
            ProxyOperationRuntime operation = UnhandledProxyOperation;
            return BeginCall(message.Headers.Action, true, operation, new object[] { message }, timeout, callback, state);
        }

        public void EndSend(IAsyncResult result)
        {
            EndCall(MessageHeaders.WildcardAction, Array.Empty<object>(), result);
        }

        public Message Request(Message message)
        {
            return Request(message, OperationTimeout);
        }

        public Message Request(Message message, TimeSpan timeout)
        {
            ProxyOperationRuntime operation = UnhandledProxyOperation;
            return (Message)Call(message.Headers.Action, false, operation, new object[] { message }, Array.Empty<object>(), timeout);
        }

        public IAsyncResult BeginRequest(Message message, AsyncCallback callback, object state)
        {
            return BeginRequest(message, OperationTimeout, callback, state);
        }

        public IAsyncResult BeginRequest(Message message, TimeSpan timeout, AsyncCallback callback, object state)
        {
            ProxyOperationRuntime operation = UnhandledProxyOperation;
            return BeginCall(message.Headers.Action, false, operation, new object[] { message }, timeout, callback, state);
        }

        public Message EndRequest(IAsyncResult result)
        {
            return (Message)EndCall(MessageHeaders.WildcardAction, Array.Empty<object>(), result);
        }

        protected override void OnAbort()
        {
            if (_idleManager != null)
            {
                _idleManager.CancelTimer();
            }

            _binder.Abort();

            if (_factory != null)
                _factory.ChannelDisposed(this);

            if (_closeFactory)
            {
                if (_factory != null)
                    _factory.Abort();
            }

            CleanupChannelCollections();
        }

        protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state)
        {
            return CommunicationObjectInternal.OnBeginClose(this, timeout, callback, state);
        }

        protected override void OnEndClose(IAsyncResult result)
        {
            CommunicationObjectInternal.OnEnd(result);
        }

        protected override void OnClose(TimeSpan timeout)
        {
            CommunicationObjectInternal.OnClose(this, timeout);
        }

        protected internal override async Task OnCloseAsync(TimeSpan timeout)
        {
            TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);

            if (_idleManager != null)
            {
                _idleManager.CancelTimer();
            }

            if (_factory != null)
            {
                _factory.ChannelDisposed(this);
            }

            if (_closeBinder)
            {
                var asyncInnerChannel = InnerChannel as IAsyncCommunicationObject;
                if (asyncInnerChannel != null)
                {
                    await asyncInnerChannel.CloseAsync(timeoutHelper.RemainingTime());
                }
                else
                {
                    InnerChannel.Close(timeoutHelper.RemainingTime());
                }
            }

            if (_closeFactory)
            {
                var asyncFactory = _factory as IAsyncCommunicationObject;
                if (asyncFactory != null)
                {
                    await asyncFactory.CloseAsync(timeoutHelper.RemainingTime());
                }
                else
                {
                    _factory.Close(timeoutHelper.RemainingTime());
                }
            }

            CleanupChannelCollections();
        }

        protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
        {
            return CommunicationObjectInternal.OnBeginOpen(this, timeout, callback, state);
        }

        protected override void OnEndOpen(IAsyncResult result)
        {
            CommunicationObjectInternal.OnEnd(result);
        }

        protected override void OnOpen(TimeSpan timeout)
        {
            CommunicationObjectInternal.OnOpen(this, timeout);
        }

        protected internal override async Task OnOpenAsync(TimeSpan timeout)
        {
            ThrowIfDisallowedInitializationUI();
            ThrowIfInitializationUINotCalled();

            if (_autoOpenManager == null)
            {
                _explicitlyOpened = true;
            }

            TraceChannelOpenStarted();

            if (_openBinder)
            {
                var asyncInnerChannel = InnerChannel as IAsyncCommunicationObject;
                if (asyncInnerChannel != null)
                {
                    await asyncInnerChannel.OpenAsync(timeout);
                }
                else
                {
                    InnerChannel.Open(timeout);
                }
            }

            BindDuplexCallbacks();
            CompletedIOOperation();

            TraceChannelOpenCompleted();
        }

        private void CleanupChannelCollections()
        {
            if (!_hasCleanedUpChannelCollections)
            {
                lock (ThisLock)
                {
                    if (!_hasCleanedUpChannelCollections)
                    {
                        if (InstanceContext != null)
                        {
                            InstanceContext.OutgoingChannels.Remove((IChannel)_proxy);
                        }

                        _hasCleanedUpChannelCollections = true;
                    }
                }
            }
        }

        #endregion

        #region IClientChannel Members

        bool IDuplexContextChannel.AutomaticInputSessionShutdown
        {
            get { return _autoClose; }
            set { _autoClose = value; }
        }

        bool IClientChannel.AllowInitializationUI
        {
            get { return _allowInitializationUI; }
            set
            {
                ThrowIfDisposedOrImmutable();
                _allowInitializationUI = value;
            }
        }

        bool IContextChannel.AllowOutputBatching
        {
            get { return _allowOutputBatching; }
            set { _allowOutputBatching = value; }
        }

        bool IClientChannel.DidInteractiveInitialization
        {
            get { return _didInteractiveInitialization; }
        }

        IAsyncResult IDuplexContextChannel.BeginCloseOutputSession(TimeSpan timeout, AsyncCallback callback, object state)
        {
            return GetDuplexSessionOrThrow().BeginCloseOutputSession(timeout, callback, state);
        }

        void IDuplexContextChannel.EndCloseOutputSession(IAsyncResult result)
        {
            GetDuplexSessionOrThrow().EndCloseOutputSession(result);
        }

        void IDuplexContextChannel.CloseOutputSession(TimeSpan timeout)
        {
            GetDuplexSessionOrThrow().CloseOutputSession(timeout);
        }

        private IDuplexSession GetDuplexSessionOrThrow()
        {
            if (InnerChannel == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.channelIsNotAvailable0));
            }

            ISessionChannel<IDuplexSession> duplexSessionChannel = InnerChannel as ISessionChannel<IDuplexSession>;
            if (duplexSessionChannel == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.channelDoesNotHaveADuplexSession0));
            }

            return duplexSessionChannel.Session;
        }

        IExtensionCollection<IContextChannel> IExtensibleObject<IContextChannel>.Extensions
        {
            get
            {
                lock (ThisLock)
                {
                    if (_extensions == null)
                        _extensions = new ExtensionCollection<IContextChannel>((IContextChannel)Proxy, ThisLock);
                    return _extensions;
                }
            }
        }

        InstanceContext IDuplexContextChannel.CallbackInstance
        {
            get { return _instanceContext; }
            set
            {
                lock (ThisLock)
                {
                    if (_instanceContext != null)
                    {
                        _instanceContext.OutgoingChannels.Remove((IChannel)_proxy);
                    }

                    _instanceContext = value;

                    if (_instanceContext != null)
                    {
                        _instanceContext.OutgoingChannels.Add((IChannel)_proxy);
                    }
                }
            }
        }

        IInputSession IContextChannel.InputSession
        {
            get
            {
                if (InnerChannel != null)
                {
                    ISessionChannel<IInputSession> inputSession = InnerChannel as ISessionChannel<IInputSession>;
                    if (inputSession != null)
                        return inputSession.Session;

                    ISessionChannel<IDuplexSession> duplexSession = InnerChannel as ISessionChannel<IDuplexSession>;
                    if (duplexSession != null)
                        return duplexSession.Session;
                }

                return null;
            }
        }

        IOutputSession IContextChannel.OutputSession
        {
            get
            {
                if (InnerChannel != null)
                {
                    ISessionChannel<IOutputSession> outputSession = InnerChannel as ISessionChannel<IOutputSession>;
                    if (outputSession != null)
                        return outputSession.Session;

                    ISessionChannel<IDuplexSession> duplexSession = InnerChannel as ISessionChannel<IDuplexSession>;
                    if (duplexSession != null)
                        return duplexSession.Session;
                }

                return null;
            }
        }

        string IContextChannel.SessionId
        {
            get
            {
                if (InnerChannel != null)
                {
                    ISessionChannel<IInputSession> inputSession = InnerChannel as ISessionChannel<IInputSession>;
                    if (inputSession != null)
                        return inputSession.Session.Id;

                    ISessionChannel<IOutputSession> outputSession = InnerChannel as ISessionChannel<IOutputSession>;
                    if (outputSession != null)
                        return outputSession.Session.Id;

                    ISessionChannel<IDuplexSession> duplexSession = InnerChannel as ISessionChannel<IDuplexSession>;
                    if (duplexSession != null)
                        return duplexSession.Session.Id;
                }

                return null;
            }
        }

        event EventHandler<UnknownMessageReceivedEventArgs> IClientChannel.UnknownMessageReceived
        {
            add
            {
                lock (ThisLock)
                {
                    _unknownMessageReceived += value;
                }
            }
            remove
            {
                lock (ThisLock)
                {
                    _unknownMessageReceived -= value;
                }
            }
        }

        public void DisplayInitializationUI()
        {
            ThrowIfDisallowedInitializationUI();

            if (_autoDisplayUIManager == null)
            {
                _explicitlyOpened = true;
            }

            ClientRuntime.GetRuntime().DisplayInitializationUI(this);
            _didInteractiveInitialization = true;
        }

        public IAsyncResult BeginDisplayInitializationUI(AsyncCallback callback, object state)
        {
            ThrowIfDisallowedInitializationUI();

            if (_autoDisplayUIManager == null)
            {
                _explicitlyOpened = true;
            }

            return ClientRuntime.GetRuntime().BeginDisplayInitializationUI(this, callback, state);
        }

        public void EndDisplayInitializationUI(IAsyncResult result)
        {
            ClientRuntime.GetRuntime().EndDisplayInitializationUI(result);
            _didInteractiveInitialization = true;
        }

        void IDisposable.Dispose()
        {
            Close();
        }

        #endregion

        private void TraceChannelOpenStarted()
        {
            if (WcfEventSource.Instance.ClientChannelOpenStartIsEnabled() && _endpointDispatcher == null)
            {
                WcfEventSource.Instance.ClientChannelOpenStart(EventActivity);
            }
            else if (WcfEventSource.Instance.ServiceChannelOpenStartIsEnabled())
            {
                WcfEventSource.Instance.ServiceChannelOpenStart(EventActivity);
            }
        }

        private void TraceChannelOpenCompleted()
        {
            if (_endpointDispatcher == null && WcfEventSource.Instance.ClientChannelOpenStopIsEnabled())
            {
                WcfEventSource.Instance.ClientChannelOpenStop(EventActivity);
            }
            else if (WcfEventSource.Instance.ServiceChannelOpenStopIsEnabled())
            {
                WcfEventSource.Instance.ServiceChannelOpenStop(EventActivity);
            }
        }

        private static void TraceServiceChannelCallStart(EventTraceActivity eventTraceActivity, bool isSynchronous)
        {
            if (WcfEventSource.Instance.ServiceChannelCallStartIsEnabled())
            {
                if (isSynchronous)
                {
                    WcfEventSource.Instance.ServiceChannelCallStart(eventTraceActivity);
                }
                else
                {
                    WcfEventSource.Instance.ServiceChannelBeginCallStart(eventTraceActivity);
                }
            }
        }

        // Invariants for signalling the CallOnce manager.
        //
        // 1) If a Call, BeginCall, or EndCall on the channel throws,
        //    the manager will SignalNext itself.
        // 2) If a Waiter times out, it will SignalNext its manager
        //    once it is both timed out and signalled.
        // 3) Once Call or EndCall returns successfully, it guarantees
        //    that SignalNext will be called once the // next stage
        //    has sufficiently completed.

        internal class SendAsyncResult : TraceAsyncResult
        {
            private readonly bool _isOneWay;
            private readonly ProxyOperationRuntime _operation;
            internal ProxyRpc Rpc;
            private OperationContext _operationContext;

            private static AsyncCallback s_ensureInteractiveInitCallback = Fx.ThunkCallback(EnsureInteractiveInitCallback);
            private static AsyncCallback s_ensureOpenCallback = Fx.ThunkCallback(EnsureOpenCallback);
            private static AsyncCallback s_sendCallback = Fx.ThunkCallback(SendCallback);

            internal SendAsyncResult(ServiceChannel channel, ProxyOperationRuntime operation,
                                     string action, object[] inputParameters, bool isOneWay, TimeSpan timeout,
                                     AsyncCallback userCallback, object userState)
                : base(userCallback, userState)
            {
                Rpc = new ProxyRpc(channel, operation, action, inputParameters, timeout);
                _isOneWay = isOneWay;
                _operation = operation;
                _operationContext = OperationContext.Current;
            }

            internal void Begin()
            {
                Rpc.Channel.PrepareCall(_operation, _isOneWay, ref Rpc);

                if (Rpc.Channel._explicitlyOpened)
                {
                    Rpc.Channel.ThrowIfOpening();
                    Rpc.Channel.ThrowIfDisposedOrNotOpen();
                    StartSend(true);
                }
                else
                {
                    StartEnsureInteractiveInit();
                }
            }

            private void StartEnsureInteractiveInit()
            {
                IAsyncResult result = Rpc.Channel.BeginEnsureDisplayUI(s_ensureInteractiveInitCallback, this);

                if (result.CompletedSynchronously)
                {
                    FinishEnsureInteractiveInit(result, true);
                }
            }

            private static void EnsureInteractiveInitCallback(IAsyncResult result)
            {
                if (!result.CompletedSynchronously)
                {
                    ((SendAsyncResult)result.AsyncState).FinishEnsureInteractiveInit(result, false);
                }
            }

            private void FinishEnsureInteractiveInit(IAsyncResult result, bool completedSynchronously)
            {
                Exception exception = null;

                try
                {
                    Rpc.Channel.EndEnsureDisplayUI(result);
                }
                catch (Exception e)
                {
                    if (Fx.IsFatal(e) || completedSynchronously)
                    {
                        throw;
                    }
                    exception = e;
                }

                if (exception != null)
                {
                    CallComplete(completedSynchronously, exception);
                }
                else
                {
                    StartEnsureOpen(completedSynchronously);
                }
            }

            private void StartEnsureOpen(bool completedSynchronously)
            {
                TimeSpan timeout = Rpc.TimeoutHelper.RemainingTime();
                IAsyncResult result = null;
                Exception exception = null;

                try
                {
                    result = Rpc.Channel.BeginEnsureOpened(timeout, s_ensureOpenCallback, this);
                }
                catch (Exception e)
                {
                    if (Fx.IsFatal(e) || completedSynchronously)
                    {
                        throw;
                    }
                    exception = e;
                }

                if (exception != null)
                {
                    CallComplete(completedSynchronously, exception);
                }
                else if (result.CompletedSynchronously)
                {
                    FinishEnsureOpen(result, completedSynchronously);
                }
            }

            private static void EnsureOpenCallback(IAsyncResult result)
            {
                if (!result.CompletedSynchronously)
                {
                    ((SendAsyncResult)result.AsyncState).FinishEnsureOpen(result, false);
                }
            }

            private void FinishEnsureOpen(IAsyncResult result, bool completedSynchronously)
            {
                Exception exception = null;
                using (ServiceModelActivity.BoundOperation(Rpc.Activity))
                {
                    try
                    {
                        Rpc.Channel.EndEnsureOpened(result);
                    }
                    catch (Exception e)
                    {
                        if (Fx.IsFatal(e) || completedSynchronously)
                        {
                            throw;
                        }
                        exception = e;
                    }

                    if (exception != null)
                    {
                        CallComplete(completedSynchronously, exception);
                    }
                    else
                    {
                        StartSend(completedSynchronously);
                    }
                }
            }

            private void StartSend(bool completedSynchronously)
            {
                TimeSpan timeout = Rpc.TimeoutHelper.RemainingTime();
                IAsyncResult result = null;
                Exception exception = null;

                try
                {
                    ConcurrencyBehavior.UnlockInstanceBeforeCallout(_operationContext);

                    if (_isOneWay)
                    {
                        result = Rpc.Channel._binder.BeginSend(Rpc.Request, timeout, s_sendCallback, this);
                    }
                    else
                    {
                        result = Rpc.Channel._binder.BeginRequest(Rpc.Request, timeout, s_sendCallback, this);
                    }
                }
                catch (Exception e)
                {
                    if (Fx.IsFatal(e))
                    {
                        throw;
                    }
                    if (completedSynchronously)
                    {
                        ConcurrencyBehavior.LockInstanceAfterCallout(_operationContext);
                        throw;
                    }
                    exception = e;
                }
                finally
                {
                    CallOnceManager.SignalNextIfNonNull(Rpc.Channel._autoOpenManager);
                }

                if (exception != null)
                {
                    CallComplete(completedSynchronously, exception);
                }
                else if (result.CompletedSynchronously)
                {
                    FinishSend(result, completedSynchronously);
                }
            }

            private static void SendCallback(IAsyncResult result)
            {
                if (!result.CompletedSynchronously)
                {
                    ((SendAsyncResult)result.AsyncState).FinishSend(result, false);
                }
            }

            private void FinishSend(IAsyncResult result, bool completedSynchronously)
            {
                Exception exception = null;

                try
                {
                    if (_isOneWay)
                    {
                        Rpc.Channel._binder.EndSend(result);
                    }
                    else
                    {
                        Rpc.Reply = Rpc.Channel._binder.EndRequest(result);

                        if (Rpc.Reply == null)
                        {
                            Rpc.Channel.ThrowIfFaulted();
                            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CommunicationException(SR.SFxServerDidNotReply));
                        }
                    }
                }
                catch (Exception e)
                {
                    if (Fx.IsFatal(e))
                    {
                        throw;
                    }
                    if (completedSynchronously)
                    {
                        ConcurrencyBehavior.LockInstanceAfterCallout(_operationContext);
                        throw;
                    }
                    exception = e;
                }

                CallComplete(completedSynchronously, exception);
            }

            private void CallComplete(bool completedSynchronously, Exception exception)
            {
                Rpc.Channel.CompletedIOOperation();
                Complete(completedSynchronously, exception);
            }

            public static void End(SendAsyncResult result)
            {
                try
                {
                    AsyncResult.End<SendAsyncResult>(result);
                }
                finally
                {
                    ConcurrencyBehavior.LockInstanceAfterCallout(result._operationContext);
                }
            }
        }

        internal interface ICallOnce
        {
            void Call(ServiceChannel channel, TimeSpan timeout);
            IAsyncResult BeginCall(ServiceChannel channel, TimeSpan timeout, AsyncCallback callback, object state);
            void EndCall(ServiceChannel channel, IAsyncResult result);
        }

        internal class CallDisplayUIOnce : ICallOnce
        {
            private static CallDisplayUIOnce s_instance;

            internal static CallDisplayUIOnce Instance
            {
                get
                {
                    if (s_instance == null)
                    {
                        s_instance = new CallDisplayUIOnce();
                    }
                    return s_instance;
                }
            }
            [Conditional("DEBUG")]

            private void ValidateTimeoutIsMaxValue(TimeSpan timeout)
            {
                if (timeout != TimeSpan.MaxValue)
                {
                    Fx.Assert("non-MaxValue timeout for displaying interactive initialization UI");
                }
            }

            void ICallOnce.Call(ServiceChannel channel, TimeSpan timeout)
            {
                ValidateTimeoutIsMaxValue(timeout);
                channel.DisplayInitializationUI();
            }

            IAsyncResult ICallOnce.BeginCall(ServiceChannel channel, TimeSpan timeout, AsyncCallback callback, object state)
            {
                ValidateTimeoutIsMaxValue(timeout);
                return channel.BeginDisplayInitializationUI(callback, state);
            }

            void ICallOnce.EndCall(ServiceChannel channel, IAsyncResult result)
            {
                channel.EndDisplayInitializationUI(result);
            }
        }

        internal class CallOpenOnce : ICallOnce
        {
            private static CallOpenOnce s_instance;

            internal static CallOpenOnce Instance
            {
                get
                {
                    if (s_instance == null)
                    {
                        s_instance = new CallOpenOnce();
                    }
                    return s_instance;
                }
            }

            void ICallOnce.Call(ServiceChannel channel, TimeSpan timeout)
            {
                channel.Open(timeout);
            }

            IAsyncResult ICallOnce.BeginCall(ServiceChannel channel, TimeSpan timeout, AsyncCallback callback, object state)
            {
                return channel.BeginOpen(timeout, callback, state);
            }

            void ICallOnce.EndCall(ServiceChannel channel, IAsyncResult result)
            {
                channel.EndOpen(result);
            }
        }

        internal class CallOnceManager
        {
            private readonly ICallOnce _callOnce;
            private readonly ServiceChannel _channel;
            private bool _isFirst = true;
            private Queue<IWaiter> _queue;

            private static Action<object> s_signalWaiter = new Action<object>(SignalWaiter);

            internal CallOnceManager(ServiceChannel channel, ICallOnce callOnce)
            {
                _callOnce = callOnce;
                _channel = channel;
                _queue = new Queue<IWaiter>();

                // Detect when the channel becomes unusable
                ChannelIsAvailable = true;
                _channel.Closing += (s, e) =>
                {
                    SetChannelUnavailable();
                };

                _channel.Faulted += (s, e) =>
                {
                    SetChannelUnavailable();
                };
            }

            private object ThisLock
            {
                get { return this; }
            }

            internal bool ChannelIsAvailable { get; private set; }

            // Called when the channel becomes unusable during the open
            // or waiting for queued waiters to complete.  This method
            // ensures any waiters' WaitHandles are set so they complete
            // rather than timing out.
            private void SetChannelUnavailable()
            {
                ChannelIsAvailable = false;
                SignalNext();
            }

            internal void CallOnce(TimeSpan timeout, CallOnceManager cascade)
            {
                SyncWaiter waiter = null;
                bool first = false;

                if (_queue != null)
                {
                    lock (ThisLock)
                    {
                        if (_queue != null)
                        {
                            if (_isFirst)
                            {
                                first = true;
                                _isFirst = false;
                            }
                            else
                            {
                                waiter = new SyncWaiter(this);
                                _queue.Enqueue(waiter);
                            }
                        }
                    }
                }

                SignalNextIfNonNull(cascade);

                if (first)
                {
                    bool throwing = true;
                    try
                    {
                        _callOnce.Call(_channel, timeout);
                        throwing = false;
                    }
                    finally
                    {
                        if (throwing)
                        {
                            SignalNext();
                        }
                    }
                }
                else if (waiter != null)
                {
                    waiter.Wait(timeout);
                }
            }

            internal IAsyncResult BeginCallOnce(TimeSpan timeout, CallOnceManager cascade,
                                                AsyncCallback callback, object state)
            {
                AsyncWaiter waiter = null;
                bool first = false;

                if (_queue != null)
                {
                    lock (ThisLock)
                    {
                        if (_queue != null)
                        {
                            if (_isFirst)
                            {
                                first = true;
                                _isFirst = false;
                            }
                            else
                            {
                                waiter = new AsyncWaiter(this, timeout, callback, state);
                                _queue.Enqueue(waiter);
                            }
                        }
                    }
                }

                SignalNextIfNonNull(cascade);

                if (first)
                {
                    bool throwing = true;
                    try
                    {
                        IAsyncResult result = _callOnce.BeginCall(_channel, timeout, callback, state);
                        throwing = false;
                        return result;
                    }
                    finally
                    {
                        if (throwing)
                        {
                            SignalNext();
                        }
                    }
                }
                else if (waiter != null)
                {
                    return waiter;
                }
                else
                {
                    return new CallOnceCompletedAsyncResult(callback, state);
                }
            }

            internal void EndCallOnce(IAsyncResult result)
            {
                if (result is CallOnceCompletedAsyncResult)
                {
                    CallOnceCompletedAsyncResult.End(result);
                }
                else if (result is AsyncWaiter)
                {
                    AsyncWaiter.End(result);
                }
                else
                {
                    bool throwing = true;
                    try
                    {
                        _callOnce.EndCall(_channel, result);
                        throwing = false;
                    }
                    finally
                    {
                        if (throwing)
                        {
                            SignalNext();
                        }
                    }
                }
            }

            static internal void SignalNextIfNonNull(CallOnceManager manager)
            {
                if (manager != null)
                {
                    manager.SignalNext();
                }
            }

            internal void SignalNext()
            {
                if (_queue == null)
                {
                    return;
                }

                IWaiter waiter = null;

                lock (ThisLock)
                {
                    if (_queue != null)
                    {
                        if (_queue.Count > 0)
                        {
                            waiter = _queue.Dequeue();
                        }
                        else
                        {
                            _queue = null;
                        }
                    }
                }

                if (waiter != null)
                {
                    ActionItem.Schedule(s_signalWaiter, waiter);
                }
            }

            private static void SignalWaiter(object state)
            {
                ((IWaiter)state).Signal();
            }

            private interface IWaiter
            {
                void Signal();
            }

            internal class SyncWaiter : IWaiter
            {
                private ManualResetEvent _wait = new ManualResetEvent(false);
                private CallOnceManager _manager;
                private bool _isTimedOut = false;
                private bool _isSignaled = false;
                private int _waitCount = 0;

                internal SyncWaiter(CallOnceManager manager)
                {
                    _manager = manager;
                }

                private bool ShouldSignalNext
                {
                    get { return (_isTimedOut || !_manager.ChannelIsAvailable) && _isSignaled; }
                }

                void IWaiter.Signal()
                {
                    _wait.Set();
                    CloseWaitHandle();

                    bool signalNext;
                    lock (_manager.ThisLock)
                    {
                        _isSignaled = true;
                        signalNext = ShouldSignalNext;
                    }
                    if (signalNext)
                    {
                        _manager.SignalNext();
                    }
                }

                internal bool Wait(TimeSpan timeout)
                {
                    try
                    {
                        // The wait can be ended by success, a timeout, or the
                        // channel becoming unavailable for further use
                        bool timedOut = !TimeoutHelper.WaitOne(_wait, timeout);
                        if (timedOut || !_manager.ChannelIsAvailable)
                        {
                            bool signalNext;
                            lock (_manager.ThisLock)
                            {
                                _isTimedOut = timedOut;
                                signalNext = ShouldSignalNext;
                            }
                            if (signalNext)
                            {
                                _manager.SignalNext();
                            }
                        }
                    }
                    finally
                    {
                        CloseWaitHandle();
                    }

                    return !_isTimedOut;
                }

                private void CloseWaitHandle()
                {
                    if (Interlocked.Increment(ref _waitCount) == 2)
                    {
                        _wait.Dispose();
                    }
                }
            }

            internal class AsyncWaiter : AsyncResult, IWaiter
            {
                private static Action<object> s_timerCallback = new Action<object>(TimerCallback);

                private CallOnceManager _manager;
                private TimeSpan _timeout;
                private Timer _timer;

                internal AsyncWaiter(CallOnceManager manager, TimeSpan timeout,
                                     AsyncCallback callback, object state)
                    : base(callback, state)
                {
                    _manager = manager;
                    _timeout = timeout;

                    if (timeout != TimeSpan.MaxValue)
                    {
                        _timer = new Timer(new TimerCallback(s_timerCallback), this, timeout, TimeSpan.FromMilliseconds(-1));
                    }
                }

                internal static void End(IAsyncResult result)
                {
                    AsyncResult.End<AsyncWaiter>(result);
                }

                void IWaiter.Signal()
                {
                    if ((_timer == null) || _timer.Change(TimeSpan.FromMilliseconds(-1), TimeSpan.FromMilliseconds(-1)))
                    {
                        Complete(false);
                        _manager._channel.Closed -= OnClosed;
                    }
                    else
                    {
                        _manager.SignalNext();
                    }
                }

                private void OnClosed(object sender, EventArgs e)
                {
                    if ((_timer == null) || _timer.Change(TimeSpan.FromMilliseconds(-1), TimeSpan.FromMilliseconds(-1)))
                    {
                        Complete(false, _manager._channel.CreateClosedException());
                    }
                }

                private static void TimerCallback(object state)
                {
                    AsyncWaiter _this = (AsyncWaiter)state;
                    _this.Complete(false, _this._manager._channel.GetOpenTimeoutException(_this._timeout));
                }
            }
        }

        private class CallOnceCompletedAsyncResult : AsyncResult
        {
            internal CallOnceCompletedAsyncResult(AsyncCallback callback, object state)
                : base(callback, state)
            {
                Complete(true);
            }

            static internal void End(IAsyncResult result)
            {
                AsyncResult.End<CallOnceCompletedAsyncResult>(result);
            }
        }

        internal class SessionIdleManager
        {
            private readonly IChannelBinder _binder;
            private ServiceChannel _channel;
            private readonly long _idleTicks;
            private long _lastActivity;
            private readonly Timer _timer;
            private static Action<object> s_timerCallback;
            private bool _didIdleAbort;
            private bool _isTimerCancelled;
            private object _thisLock;

            private SessionIdleManager(IChannelBinder binder, TimeSpan idle)
            {
                _binder = binder;
                _timer = new Timer(new TimerCallback(GetTimerCallback()), this, idle, TimeSpan.FromMilliseconds(-1));
                _idleTicks = Ticks.FromTimeSpan(idle);
                _thisLock = new Object();
            }

            internal static SessionIdleManager CreateIfNeeded(IChannelBinder binder, TimeSpan idle)
            {
                if (binder.HasSession && (idle != TimeSpan.MaxValue))
                {
                    return new SessionIdleManager(binder, idle);
                }
                else
                {
                    return null;
                }
            }

            internal bool DidIdleAbort
            {
                get
                {
                    lock (_thisLock)
                    {
                        return _didIdleAbort;
                    }
                }
            }

            internal void CancelTimer()
            {
                lock (_thisLock)
                {
                    _isTimerCancelled = true;
                    _timer.Change(TimeSpan.FromMilliseconds(-1), TimeSpan.FromMilliseconds(-1));
                }
            }

            internal void CompletedActivity()
            {
                Interlocked.Exchange(ref _lastActivity, Ticks.Now);
            }

            internal void RegisterChannel(ServiceChannel channel, out bool didIdleAbort)
            {
                lock (_thisLock)
                {
                    _channel = channel;
                    didIdleAbort = _didIdleAbort;
                }
            }

            private static Action<object> GetTimerCallback()
            {
                if (s_timerCallback == null)
                {
                    s_timerCallback = TimerCallback;
                }
                return s_timerCallback;
            }

            private static void TimerCallback(object state)
            {
                ((SessionIdleManager)state).TimerCallback();
            }

            private void TimerCallback()
            {
                // This reads lastActivity atomically without changing its value.
                // (it only sets if it is zero, and then it sets it to zero).
                long last = Interlocked.CompareExchange(ref _lastActivity, 0, 0);
                long abortTime = last + _idleTicks;

                lock (_thisLock)
                {
                    long ticksNow = Ticks.Now;
                    if (ticksNow > abortTime)
                    {
                        if (WcfEventSource.Instance.SessionIdleTimeoutIsEnabled())
                        {
                            string listenUri = string.Empty;
                            if (_binder.ListenUri != null)
                            {
                                listenUri = _binder.ListenUri.AbsoluteUri;
                            }

                            WcfEventSource.Instance.SessionIdleTimeout(listenUri);
                        }

                        _didIdleAbort = true;
                        if (_channel != null)
                        {
                            _channel.Abort();
                        }
                        else
                        {
                            _binder.Abort();
                        }
                    }
                    else
                    {
                        if (!_isTimerCancelled && _binder.Channel.State != CommunicationState.Faulted && _binder.Channel.State != CommunicationState.Closed)
                        {
                            _timer.Change(Ticks.ToTimeSpan(abortTime - ticksNow), TimeSpan.FromMilliseconds(-1));
                        }
                    }
                }
            }
        }
    }
}
