Josh Sutterfield
Josh Sutterfield

Reputation: 1988

Diagnosing WCF channel fault where service closes socket after method return

I am receiving an exception on a WCF client in a very specific case where the contract method completes on the service - i.e. the exception occurs after the return on the service, and before the return on the client proxy.

I have confirmed that the provoking issue is related to the fact that return type is an array of a given type, while the array includes instances of a derived type. For non-WCF usage there is no exception at all in returning this array (as the code demonstrates), and when the method is invoked from the client, it returns on the service side.

Now, exceptions thrown from within the method I can catch, or receive client-side if I fail to catch, but this kind of issue apparently is quietly swallowed & the only info that can be gathered from the client-side exception is the socket error after the connection is closed!

If we hook Closing/Faulted events we do see a Fault & Close on the service-side, then a Fault on the client side, before the exception.

Follow the minimal case below, and note that I am trying multiple ways to get additional information. NOTE: I have omitted the actual ServiceContract since it is clear enough from the implementation (only one method).

public class BaseItem {}
public class DerivedItem : BaseItem {}

// Service implementation plus fault & error handling
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, IncludeExceptionDetailInFaults = true, UseSynchronizationContext = false)]
public class BasicService : IMyService, IErrorHandler {

    BaseItem[] IMyService.GetItems(bool useDerived) {
        return new BaseItem[] { useDerived ? new DerivedItem() : new BaseItem() };
    }

    public void ProvideFault(Exception error, MessageVersion version, ref Message fault) {}

    public bool HandleError(Exception error) { return false; }
}


public class ClientLauncher {
    public static void LaunchClient() {
        IMyService service = new BasicService();

        BaseItem[] items = service.GetItems(false); // Works fine
        items = service.GetItems(true); // Works fine

        ServiceHost host = new ServiceHost(service); // Singleton instance
        host.AddServiceEndpoint(typeof(IMyService), new NetTcpBinding(),
            "net.tcp://localhost:8000");
        host.Open();

        // Adding service as the implementation of IErrorHandler
        ((ChannelDispatcher)host.ChannelDispatchers[0]).ErrorHandlers.Add((IErrorHandler)service);

        var myFactory = new ChannelFactory<IMyService>(new NetTcpBinding(), new EndpointAddress("net.tcp://localhost:8000"));
        IMyService client = myFactory.CreateChannel();

        try {
            items = client.GetItems(false); // Works fine
            items = client.GetItems(true); // Results in channel faulted, then exception
        }
        catch (Exception ex) {
            string str = ex.ToString();
        }
    }
}

Direct calls to GetItems work fine, and client-invoked GetItems works but only if the array is free of instances of the derived type. The array is necessary - no problem returning a DerivedItem if the method returned single BaseItem.

But again the real problem is more general - regardless of the specific provoking issue, how can we find anything out about it when 1. The exception only seems to indicate the fact that the socket got closed by the service and 2. The IErrorHandler, though added, is not called? I would really like to use WCF but my confidence is pretty shaken if this is all the exception is going to tell me:

************** Exception Text **************
System.ServiceModel.CommunicationException: The socket connection was aborted. This could be caused by an error processing your message or a receive timeout being exceeded by the remote host, or an underlying network resource issue. Local socket timeout was '00:00:59.9090000'. ---> System.IO.IOException: The read operation failed, see inner exception. ---> System.ServiceModel.CommunicationException: The socket connection was aborted. This could be caused by an error processing your message or a receive timeout being exceeded by the remote host, or an underlying network resource issue. Local socket timeout was '00:00:59.9090000'. ---> System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host
   at System.Net.Sockets.Socket.Receive(Byte[] buffer, Int32 offset, Int32 size, SocketFlags socketFlags)
   at System.ServiceModel.Channels.SocketConnection.ReadCore(Byte[] buffer, Int32 offset, Int32 size, TimeSpan timeout, Boolean closing)
   --- End of inner exception stack trace ---
   at System.ServiceModel.Channels.SocketConnection.ReadCore(Byte[] buffer, Int32 offset, Int32 size, TimeSpan timeout, Boolean closing)
   at System.ServiceModel.Channels.SocketConnection.Read(Byte[] buffer, Int32 offset, Int32 size, TimeSpan timeout)
   at System.ServiceModel.Channels.DelegatingConnection.Read(Byte[] buffer, Int32 offset, Int32 size, TimeSpan timeout)
   at System.ServiceModel.Channels.ConnectionStream.Read(Byte[] buffer, Int32 offset, Int32 count)
   at System.Net.FixedSizeReader.ReadPacket(Byte[] buffer, Int32 offset, Int32 count)
   at System.Net.Security.NegotiateStream.StartFrameHeader(Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.NegotiateStream.ProcessRead(Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
   --- End of inner exception stack trace ---
   at System.Net.Security.NegotiateStream.ProcessRead(Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.NegotiateStream.Read(Byte[] buffer, Int32 offset, Int32 count)
   at System.ServiceModel.Channels.StreamConnection.Read(Byte[] buffer, Int32 offset, Int32 size, TimeSpan timeout)
   --- End of inner exception stack trace ---

Server stack trace: 
   at System.ServiceModel.Channels.StreamConnection.Read(Byte[] buffer, Int32 offset, Int32 size, TimeSpan timeout)
   at System.ServiceModel.Channels.SessionConnectionReader.Receive(TimeSpan timeout)
   at System.ServiceModel.Channels.SynchronizedMessageSource.Receive(TimeSpan timeout)
   at System.ServiceModel.Channels.FramingDuplexSessionChannel.Receive(TimeSpan timeout)
   at System.ServiceModel.Channels.FramingDuplexSessionChannel.TryReceive(TimeSpan timeout, Message& message)
   at System.ServiceModel.Dispatcher.DuplexChannelBinder.Request(Message message, TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
   at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)

Exception rethrown at [0]: 
   at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
   at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
   at KapLogic.Aegis.Drivers.P2000.Common.WCF.IMyService.GetItems(Boolean useDerived)
   at KapLogic.Aegis.Drivers.P2000.Common.WCF.ClientLauncher.LaunchClient()

Upvotes: 1

Views: 2687

Answers (1)

siddharth
siddharth

Reputation: 111

Decorating the BaseItem with a KnownType attribute and specifying the DerivedItem as the known type in the attribute should take care of the problem.

e.g - Code:

[DataContract]
[KnownType(typeof(DerivedItem))]
public class BaseItem { }

Excerpt from MSDN: "The KnownTypeAttribute class allows you to specify, in advance, the types that should be included for consideration during deserialization. For a working example, see the Known Types example."

More information can be found at : http://msdn.microsoft.com/en-us/library/ms730167.aspx

Upvotes: 1

Related Questions