Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reuse asynchronous socket: subsequent connect attempts fail

I'm trying to reuse a socket in an asynchronous HTTP client, but I'm not able to connect to the host the second time around. I basically treat my asynchronous HTTP client as a state machine with the following states:

  • Available: the socket is available for use
  • Connecting: the socket is connecting to the endpoint
  • Sending: the socket is sending data to the endpoint
  • Receiving: the socket is receiving data from the endpoint
  • Failed: there was a socket failure
  • Clean Up: cleaning up the socket state

In the connecting state I call BeginConnect:

private void BeginConnect()
{
    lock (_sync) // re-entrant lock
    {
        IPAddress[] addersses = Dns.GetHostEntry(_asyncTask.Host).AddressList;

        // Connect to any available address
        IAsyncResult result = _reusableSocket.BeginConnect(addersses, _asyncTask.Port, new AsyncCallback(ConnectCallback), null);
    }
}

The callback method changes the state to Sending once a successful connection has been established:

private void ConnectCallback(IAsyncResult result)
{
    lock (_sync) // re-entrant lock
    {
        try
        {
            _reusableSocket.EndConnect(result);

            ChangeState(EClientState.Sending);
        }
        catch (SocketException e)
        {
            Console.WriteLine("Can't connect to: " + _asyncTask.Host);
            Console.WriteLine("SocketException: {0} Error Code: {1}", e.Message, e.NativeErrorCode);
            ThreadPool.QueueUserWorkItem(o =>
            {
                // An attempt was made to get the page so perform a callback
                ChangeState(EClientState.Failed);
            });
        }
    }
}

In the cleanup I Shutdown the socket and Disconnect with a reuse flag:

private void CleanUp()
{
    lock (_sync) // re-entrant lock
    {
        // Perform cleanup 
        if (_reusableSocket.Connected)
        {
            _reusableSocket.Shutdown(SocketShutdown.Both);
            _reusableSocket.Disconnect(true);
        }
        ChangeState(EClientState.Available);
    }
}

Subsequent calls to BeginConnect result in a timeout and an exception:

SocketException: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond XX.XXX.XX.XX:80

Error Code: 10060

Here is the state trace:

Initializing...
Change State: Connecting
Change State: Sending
Change State: Receiving
Change State: CleanUp
Callback:     Received data from client 0 // <--- Received the first data 
Change State: Available
Change State: Connecting // <--- Timeout when I try to reuse the socket to connect to a different endpoint

What do I have to do to be able to reuse the socket to connect to a different host?

Note: I have not tried to re-connect to the same host, but I assume the same thing happens (i.e. fails to connect).

Update
I found the following note in the documentation of BeginConnect:

If this socket has previously been disconnected, then BeginConnect must be called on a thread that will not exit until the operation is complete. This is a limitation of the underlying provider. Also the EndPoint that is used must be different.

I'm starting to wonder if my issue has something to do with that... I am connecting to a different EndPoint, but what do they mean that the thread from which we call BeginConnect must not exit until the operation is complete?

Update 2.0:
I asked a related question and I tried using the "Async family" calls instead of the "Begin family" calls, but I get the same problem!!!

like image 517
Kiril Avatar asked Apr 23 '11 04:04

Kiril


1 Answers

I commented on this question: what is benefit from socket reuse in C# about socket reuse using Disconnect(true)/DisconnectEx() and this may help you.

Personally I think it's an optimisation too far in client code.

Re update 1 to your question; no, you'd get an AbortedOperation exception if that were the case (see here: VB.NET 3.5 SocketException on deployment but not on development machine) and the docs are wrong if you're running on Vista or later as it doesn't enforce the "thread must exist until after overlapped I/O completes" rule that previous operating systems enforce.

As I've already said in the reply to the linked question; there's very little point in using this functionality for outbound connection establishment. It's likely that it was originally added to the Winsock API to support socket reuse for AcceptEx() on inbound connections, where, on a very busy web server that was using TransmitFile() to send files to clients (which is where disconnect for reused seems to have originated). The docs state that it doesn't play well with TIME_WAIT and so using it for connections where you initiate the active close (and thus put the socket into TIME_WAIT, see here) doesn't really make sense.

Can you explain why you think this micro optimisation is actually necessary in your case?

like image 84
Len Holgate Avatar answered Nov 12 '22 08:11

Len Holgate