none
WCF Named Pipe, Per Session, handles persists RRS feed

  • Question

  • Hello

    I have two service application on a computer, that communicates using named pipes.

    Server side application uses PerSession context mode and multiple concurrency mode.

    client application is also a wcf service exposed using websocket with context mode per session and multiple concurrency.

    When a client disconnect from the websocket service, connection is properly closed on both services.

    But, when i try to close the connection of a session on the server side application, the connection is not closed on the websocket application. On the server side app, i call the dispose method of the service implementation, which closes the callback to the websocket service. But the session on websocket server does not received a connection closed of faulted event.

    I assume that the connection persists, because using process explorer and watching handles, i see that named pipes connection are still up. Another interesting point, if a connect an other client after a server side disconnection, no handle are created, does wcf uses the "not really released" named pipe ?

    Any idea about this issue ?

    Edit :

    Server Side :

    [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple,
        InstanceContextMode = InstanceContextMode.PerSession,
        AddressFilterMode = AddressFilterMode.Any)]
    public class WSProtobufService1 : IWSProtobuf, IDisposable
    {
        private IWSProtobufCallback websocketProtobufCallback;
    
        // IWSProtobuf implentation
        public void Message(byte[] message)
        {
            if(websocketProtobufCallback != null && OperationContext.Current != null)
            {
                websocketProtobufCallback = OperationContext.Current.GetCallbackChannel<IWSProtobufCallback>();
            }            
        }
    
        public void CloseConnection()
        {
            if (websocketProtobufCallback != null)
            {
                ICommunicationObject x = (ICommunicationObject)websocketProtobufCallback;
                if (x != null && x.State == CommunicationState.Opened)
                {
                    try
                    {
                        x.Close();
                    }
                    catch (Exception ex)
                    {
                        Log.WriteLine("Error closing named pipe connection => {0}{1}{2}", ex.Message, Environment.NewLine, ex.StackTrace);
                        if (x != null)
                        {
                            try
                            {
                                x.Abort();
                            }
                            catch (Exception ex2)
                            {
                                Log.WriteLine("Error aborting named pipe connection => {0}{1}{2}", ex2.Message, Environment.NewLine, ex2.StackTrace);
                            }
                        }
                    }
                }
            }
        }
    
        public void Dispose()
        {
            //Disposing objects, no exceptions thrown here.
        }
    }
    
    [ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IWSProtobufCallback))]
    public interface IWSProtobuf
    {
        [OperationContract(IsOneWay = true)]
        void Message(byte[] message);
    }
    
    public interface IWSProtobufCallback
    {
        [OperationContract(IsOneWay = true)]
        void OnMessage(byte[] result);
    }

    Client Side :

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Multiple)]
    public class WebSocketRoutingService : IWebSocketConnector, IDisposable
    {
    
        /// <summary>
        /// The websocket protobuf callback, to send messages to the client
        /// </summary>
        private IWebSocketConnectorCallback websocketProtobufCallback;
    
        private Connectors.WebSocket.IWSProtobuf duplexClient = null;
    
        /// <summary>
        /// Main entry for web socket messages.
        /// All client messages get through this method.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <returns>Task, async await pattern</returns>
        /// <exception cref="System.ArgumentNullException">message</exception>
        public async System.Threading.Tasks.Task Message(System.ServiceModel.Channels.Message message)
        {
            if (message == null)
            {
                throw new ArgumentNullException("message");
            }
    
            try
            {
                if (this.websocketProtobufCallback == null)
                {
                    this.websocketProtobufCallback = OperationContext.Current.GetCallbackChannel<IWebSocketConnectorCallback>();
                    ((ICommunicationObject)this.websocketProtobufCallback).Faulted += WebSocketProtoConnectorService_Faulted;
                }                
    
            }
            catch (Exception ex)
            {
                Logging.ExceptionManager.HandleException(Logging.Constants.ServiceManager, ex, false);
            }
    
            return;
        }
    
        /// <summary>
        /// Sends the message using the websocket callback.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <returns></returns>
        private async System.Threading.Tasks.Task SendMessageThroughWS(System.ServiceModel.Channels.Message message)
        {
            ICommunicationObject x = (ICommunicationObject)this.websocketProtobufCallback;
            if (x.State == CommunicationState.Opened)
            {
                try
                {
                    //work
                }
                catch (Exception ex)
                {
                    Logging.ExceptionManager.HandleException(Logging.Constants.ServiceManager, ex, true);
                    Logging.Logger.Write("Error when sending message " + this.CurrentConfigurationToString());                    
                }
            }
        }
    
        private void CloseDuplexCallback()
        {
            try
            {
                Logger.Write("Disposing callback");
                if (websocketProtobufCallback != null && ((ICommunicationObject)websocketProtobufCallback).State == CommunicationState.Opened)
                {
                    ((ICommunicationObject)websocketProtobufCallback).Close();
                    websocketProtobufCallback = null;
                    Logger.Write("callback disposed");
                }
                else
                {
                    Logger.Write("callback already disposed");
                }
            }
            catch (Exception ex)
            {
                ExceptionManager.HandleException(Constants.ServiceManager, ex, true);
            }
        }
    
        private void CloseDuplexConnection()
        {
            try
            {
                Logger.Write("Disposing duplex");
                if (this.duplexClient != null && ((System.ServiceModel.ICommunicationObject)this.duplexClient).State == CommunicationState.Opened)
                {
                    ((System.ServiceModel.ICommunicationObject)this.duplexClient).Close();
                    duplexClient = null;
                    Logger.Write("duplex disposed");
                }
                else
                {
                    Logger.Write("duplex already disposed");
                }
            }
            catch (Exception ex)
            {
                ExceptionManager.HandleException(Constants.ServiceManager, ex, true);
            }
        }
    
        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            CloseDuplexCallback();
            CloseDuplexConnection();
        }
    
        #region Duplex Client
    
        private void AgentConnection_Faulted(object sender, EventArgs e)
        {
            Dispose();
        }
    
        private void WebSocketRoutingService_Closed(object sender, EventArgs e)
        {
            Dispose();
        }
    
        /// <summary>
        /// Handles the Faulted event of the WebSocketProtoConnectorService control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        private void WebSocketProtoConnectorService_Faulted(object sender, EventArgs e)
        {
            Dispose();
        }
    
        private Connectors.WebSocket.IWSProtobuf CreateOpenDuplexConnection(string instance)
        {
            var client = this.CreateDuplexConnection(instance);
            if (client != null)
            {
                ((ICommunicationObject)client).Faulted += AgentConnection_Faulted;
                ((ICommunicationObject)client).Closed += WebSocketRoutingService_Closed;
                ((ICommunicationObject)client).Open();
            }
    
            return client;
        }
    
        private Connectors.WebSocket.IWSProtobuf CreateDuplexConnection(string instance)
        {
            if (!string.IsNullOrEmpty(instance))
            {
                WSProtoCallback dcc = new WSProtoCallback();
                dcc.OnMessageReceived += OnMessageReceived;
                NetNamedPipeBinding pipeBinding = new NetNamedPipeBinding();
                pipeBinding.Security.Mode = NetNamedPipeSecurityMode.None;
                pipeBinding.SendTimeout = new TimeSpan(0, 0, 10);
                pipeBinding.OpenTimeout = TimeSpan.FromMilliseconds(OpenTimeout);
                pipeBinding.ReceiveTimeout = TimeSpan.FromMilliseconds(ReceiveTimeout);
                pipeBinding.MaxBufferPoolSize = 2147483647;
                pipeBinding.MaxBufferSize = 2147483647;
                pipeBinding.MaxConnections = 32767;
                pipeBinding.MaxReceivedMessageSize = 2147483647;
                pipeBinding.ReaderQuotas.MaxArrayLength = 2147483647;
                pipeBinding.ReaderQuotas.MaxBytesPerRead = 2147483647;
                pipeBinding.ReaderQuotas.MaxDepth = 2147483647;
                pipeBinding.ReaderQuotas.MaxStringContentLength = 2147483647;
                pipeBinding.ReaderQuotas.MaxNameTableCharCount = 2147483647;
    
                string endpoint = "proto.svc";
                Uri localUri = ServiceManager.GetLocalUri(instance, endpoint);
                return DuplexChannelFactory<Connectors.WebSocket.IWSProtobuf>.CreateChannel(dcc, pipeBinding, new EndpointAddress(localUri));
            }
            else
            {
                return null;
            }
        }
    
        private void OnMessageReceived(byte[] message)
        {
            // some work
        }
    
        private void PrepareDuplexConnection(string instance, out Proto.WsError error)
        {
            error = null;
            if (ServiceManager.Current.Instances.Any(i => i.InstanceSafeName.Equals(instance, StringComparison.InvariantCultureIgnoreCase)))
            {
                if (duplexClient == null || ((System.ServiceModel.ICommunicationObject)this.duplexClient).State != CommunicationState.Opened)
                {
                    try
                    {
                        var client = CreateOpenDuplexConnection(instance);
                        if (client != null)
                        {
                            duplexClient = client;
                        }
                    }
                    catch (Exception ex)
                    {
                        error = Utils.BuildError(ErrorCodes.CONNECTION_FAILED, "error with instance connection : "+instance);
                        error.Details = ex.Message;
                    }
                }
            }
            else
            {
                error = Utils.BuildError(ErrorCodes.CONNECTION_FAILED_INSTANCE_NOT_FOUND, string.Format("Instance not found ! ({0} does not exists)", instance));
            }
        }
        #endregion
    }
    
    public class WSProtoCallback : Connectors.WebSocket.IWSProtobufCallback
    {
        public delegate void OnMessageReceivedEventHanlder(byte[] message);
        public event OnMessageReceivedEventHanlder OnMessageReceived;
    
        public void OnMessage(byte[] result)
        {
            this.OnMessageReceived(result);
        }
    }

    Edit 2 :

    After serveral attempts, i finally found a possible workaround, but i'm not confident with it.

    I keep an instance of the OperationContext.Current in my server side class. And call OperationContext.Current.InstanceContext.Abort() to close both server and client session connection.

    If i call Close method, this throws a timeout exception :

    The ServiceHost close operation timed out after 00:00:10.  This could be because a client failed to close a sessionful channel within the required time.  The time allotted to this operation may have been a portion of a longer timeout.

    Thanks


    • Modifié Mathias Herbaux vendredi 18 mars 2016 10:49 update, more details
    jeudi 17 mars 2016 22:09