Detecting Client Death in WCF Duplex Contracts
Asked Answered
C

2

31

I'm trying to build a SOA where clients can perform long running queries on the server and the server responds using a callback.

I'd like to be able to detect if the client disconnects (through user initiated shutdown, unhandled exception or loss of network connectivity) so that the server can choose to cancel the expensive request.

I'm testing a variety of failure cases but I can't seem to get certain event handlers to fire.

Tested Failure Cases: Killing the Client Process After the request. Using a program like CurrPorts to close the TCP Connection.

Test Code:

using System;
using System.ServiceModel;
using System.Threading;

namespace WCFICommunicationObjectExperiments
{
    class Program
    {
        static void Main(string[] args)
        {
            var binding = new NetTcpBinding(SecurityMode.None);

            var serviceHost = new ServiceHost(typeof (Server));
            serviceHost.AddServiceEndpoint(typeof (IServer), binding, "net.tcp://localhost:5000/Server");
            serviceHost.Open();
            Console.WriteLine("Host is running, press <ENTER> to exit.");
            Console.ReadLine();
        }

    }

    [ServiceContract(CallbackContract = typeof(IClient))]
    public interface IServer
    {
        [OperationContract]
        void StartProcessing(string Query);
    }

    public interface IClient
    {
        [OperationContract]
        void RecieveResults(string Results);
    }

    [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
    public class Server : IServer
    {

        public void StartProcessing(string Query)
        {
            Thread.Sleep(5000);

            //Callback Channel
            var clientCallback = OperationContext.Current.GetCallbackChannel<IClient>();
            var clientCallbackCommunicationObject = ((ICommunicationObject) clientCallback);
            EventHandler faultedHandlerCallback = (o, s) => Console.WriteLine("Client Channel Faulted.");
            EventHandler closedHandlerCallback = (o, s) => Console.WriteLine("Client Channel Closed.");
            clientCallbackCommunicationObject.Faulted += faultedHandlerCallback;
            clientCallbackCommunicationObject.Closed += closedHandlerCallback;

            //Request Channel
            var requestChannel = OperationContext.Current.Channel;
            EventHandler faultedHandlerRequest = (o, s) => Console.WriteLine("Request Channel Faulted.");
            EventHandler closedHandlerRequest = (o, s) => Console.WriteLine("Request Channel Closed.");
            requestChannel.Faulted += faultedHandlerRequest;
            requestChannel.Closed += closedHandlerRequest;

            try
            {
                clientCallback.RecieveResults("42.");
            }
            catch (CommunicationObjectAbortedException ex)
            {
                Console.WriteLine("Client Aborted the connection");
            }
            catch (CommunicationObjectFaultedException ex)
            {
                Console.WriteLine("Client Died.");
            }
            clientCallbackCommunicationObject.Faulted -= faultedHandlerCallback;
            clientCallbackCommunicationObject.Faulted -= closedHandlerCallback;
            requestChannel.Faulted -= faultedHandlerRequest;
            requestChannel.Closed -= closedHandlerRequest;
        }
    }

    public class ClientToTestStates : IClient
    {
        private IServer m_Server;

        private readonly ManualResetEvent m_ReceivedEvent = new ManualResetEvent(false);
        private readonly ManualResetEvent m_ChannelFaulted = new ManualResetEvent(false);
        private readonly ManualResetEvent m_ChannelClosed = new ManualResetEvent(false);

        public ClientToTestStates()
        {
            var binding = new NetTcpBinding(SecurityMode.None);
            var channelFactory = new DuplexChannelFactory<IServer>(this, binding, new EndpointAddress("net.tcp://localhost:5000/Server"));
            m_Server = channelFactory.CreateChannel();
            ((ICommunicationObject)m_Server).Open();
            ((ICommunicationObject)m_Server).Faulted += ChannelFaulted;
            ((ICommunicationObject)m_Server).Closed += ChannelClosed;

            m_Server.StartProcessing("What is the answer?");

            WaitHandle.WaitAny(new WaitHandle[] {m_ReceivedEvent, m_ChannelFaulted, m_ChannelClosed});
        }

        void ChannelFaulted(object sender, EventArgs e)
        {
            m_ChannelFaulted.Set();
            Console.WriteLine("Channel Faulted.");
        }

        void ChannelClosed(object sender, EventArgs e)
        {
            m_ChannelClosed.Set();
            Console.WriteLine("Channel Closed.");
        }


        public void RecieveResults(string results)
        {
            m_ReceivedEvent.Set();
            Console.WriteLine("Recieved Results {0}", results);
        }
    }
}

What's the best practice to handle these sorts of failure cases? I'd like to be able to use the underlying tcp connection to detect some of these things.

Cheri answered 15/9, 2009 at 15:35 Comment(1)
Have you tried turning on Reliability? TCP provides point-to-point reliability. <br> Message reliability (via WS-Reliability) provides end-to-end reliability. <br> And in turn I believe will notify you when either side ‘goes away’ unceremoniously. For transports that support it, it is best practice to always turn on reliability, though some network ‘goo’ may not support itPenn
A
17

In his 'Programming WCF Services' book, Juval Lowy explains that WCF does not provide a mechansim for managing service callbacks, and this must be managed by the service and client explicitly. If the service attempts to invoke a callback which has been closed on the client, an ObjectDisposedException will be thrown on the service channel.

He recommends adding a Connect and Disconnect method to the service contract - since the callback must be provided to the service when these are called, the service can manage client callbacks. It is then up to the client to ensure that it calls Disconnect when it no longer wishes to recieve callbacks from the service, and the service must handle any exceptions when invoking callbacks to the client.

Anisometric answered 15/9, 2009 at 16:32 Comment(3)
Thanks for the information. In the case of unexpected client failure where one could not expect Disconnect() to get called what can be done to detect that failure early on the server side to free up precious resources?Cheri
given that the OS knows that the TCP connection has not got the keep alive packets. It should be possible for a WCF server to know the client has gone away. So there should be a better answer then this. I just don't know what it is!Anthropogenesis
The OS can use the TCP connection. In my tests I was using the Net.tcp binding and I was receiving channel faulted events. Unfortunately if the tcp connection is to a machine that is serving as a relay (using SSH's remote forwarding with global binding) the OS doesn't see the TCP connection close (because the connection to the relay machine hasn't actually closed). This was causing the strange behavior I observed in my test.Cheri
E
12

try this to check if the callback object is still valid:

(((ICommunicationObject)myCallbackObject).State == CommunicationState.Opened)

myCallbackObject in this case is the object through which you can perform the callback, i.e. the one implementing the callback contract

Electrophotography answered 26/10, 2012 at 8:53 Comment(2)
this solution is not recommended because the callback channel can be faulted in between the time you check for the state and the time you do anything with the channel.Joselynjoseph
Yeah you should still handle any exceptions since there isn't really a way to check for sure.Electrophotography

© 2022 - 2024 — McMap. All rights reserved.