SocketException: “a non-blocking socket operation could not be completed immediately” with .Net Remoting

If you are using .Net Remoting with the TCP channel, you may get the error SocketException: “a non-blocking socket operation could not be completed immediately” under some specific conditions.

The underlying socket error code is SocketError.WouldBlock(10035). This is defined as WSAWOULDBLOCK in he Platform SDK header files:

 #define WSABASEERR              10000
...
#define WSAEWOULDBLOCK          (WSABASEERR+35)

The managed call stack at exception time can be something like this:

 System.Net.Sockets.SocketException: A non-blocking socket operation could not be completed immediately
    at System.Net.Sockets.Socket.Receive(Byte[] buffer, Int32 offset, Int32 size, SocketFlags socketFlags)
    at System.Runtime.Remoting.Channels.SocketStream.Read(Byte[] buffer, Int32 offset, Int32 size)
    at System.Runtime.Remoting.Channels.SocketHandler.ReadFromSocket(Byte[] buffer, Int32 offset, Int32 count)
    at System.Runtime.Remoting.Channels.SocketHandler.BufferMoreData()
    at System.Runtime.Remoting.Channels.SocketHandler.Read(Byte[] buffer, Int32 offset, Int32 count)
    at System.Runtime.Remoting.Channels.Tcp.TcpFixedLengthReadingStream.Read(Byte[] buffer, Int32 offset, Int32 count)
    at System.IO.BinaryReader.ReadString()
    at System.Runtime.Serialization.Formatters.Binary.IOUtil.ReadArgs(__BinaryParser input)
    at System.Runtime.Serialization.Formatters.Binary.BinaryMethodCall.Read(__BinaryParser input)
    at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadMethodObject(BinaryHeaderEnum binaryHeaderEnum)
    at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.Run()
    at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
    at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
    at System.Runtime.Remoting.Channels.CoreChannel.DeserializeBinaryRequestMessage(String objectUri, Stream inputStream, Boolean bStrictBinding, TypeFilterLevel securityLevel)
    at System.Runtime.Remoting.Channels.BinaryServerFormatterSink.ProcessMessage(IServerChannelSinkStack sinkStack, IMessage requestMsg, ITransportHeaders requestHeaders, Stream requestStream, IMessage& responseMsg, ITransportHeaders& responseHeaders, Stream& responseStream)

WSAEWOULDBLOCK is returned by some socket APIs when the call may need to block the caller (in other words, it cannot return immediately), but the underlying socket is in non-blocking mode. For instance, the recv socket api needs to block, waiting for enough bytes to be in the input buffer, if they are not already there.

With .Net Remoting using the TCP channel, a socket should not be in non-blocking mode. However, this situation may arise on the server side of .Net Remoting if you call StartListening() on the channel when the channel is already listening.

The typical scenario is if you create the channel and call StartListening on it:

 private static string StartServer()
{
     TcpChannel serverChannel = new TcpChannel(0);
     ChannelServices.RegisterChannel(serverChannel, false);
     RemotingConfiguration.RegisterWellKnownServiceType(typeof(MyHost), "MyHost",
                                                        WellKnownObjectMode.Singleton);
     serverChannel.StartListening(null);            
     return ((ChannelDataStore)serverChannel.ChannelData).ChannelUris[0];
}

The TcpChannel constructor puts the channel in listening mode, so calling StartListening at this stage creates the conditions for this behavior. Without going into too much detail on the underlying cause, suffice it to say that in this case a listening socket will experience an error while accepting the incoming connection, so it will be put into non-blocking mode. This will cause the next accept to succeed but the new connection at this point will be in non-blocking mode also.

Fortunately, if you run into this condition, the fix is easy: just remove the additional StartListening call:

 private static string StartServer()
{
    TcpChannel serverChannel = new TcpChannel(0);
     ChannelServices.RegisterChannel(serverChannel, false);
     RemotingConfiguration.RegisterWellKnownServiceType(typeof(MyHost), "MyHost",
                                                        WellKnownObjectMode.Singleton);
     return ((ChannelDataStore)serverChannel.ChannelData).ChannelUris[0];
}

Notes

  • StartListening is a method of the IChannelReceiver interface and is also implemented by the other channels in .Net Remoting: IpcChannel and HttpChannel. However, these channels do not suffer from this behavior: calling StartListening on an already listening IpcChannel or HttpChannel doesn’t do anything.
  • StartListening has its own place as a way to resume listening on a channel that is not listening. In other words, it is safe to call StartListening on a channel on which StopListening had been called previously.