Search code examples
delphiindywinsock2

How can I avoid freezing on Write to socket in Indy if the other end is not reading data from the Socket


I have a application with client and server sockets using Indy, compiled with Delphi 10.2.

The application have a working thread that processes requests from the different ports and write responses within the same thread using a call to something like this:

procedure TMyCommManager.WriteResponse(AHandler: TIdIOHandler; SomeData: SomeType);
var 
  idBytes: TidBytes;
  PacketSize: Integer;
begin
  SomeData.GetBytes(idBytes, PacketSize);
  AHandler.Write(DataBuffer, PacketSize);
end;

Almost all the time everything is working as expected, but we noticed the worker thread is freezing from time to time in production. After various iterations, we finally got to the point that all this is occurring in the call to the TidIOHandler.Write() and I'm quite sure it is happening because the other end for a single port is not reading the responses from the socket.

After a reset to the port from within the other end the worker thread unfreezes and keeps working as expected.

I found this answer from Remy Lebeau to the question Delphi (Indy) Server Freezing on Write, where he mentions in comments (emphasis mine):

Indy uses blocking sockets, so if the client is not reading inbound data on its end, eventually the socket's internal send buffer will fill up and the socket will become blocked on the server side waiting for the client to empty the buffer. The only way to avoid a deadlock in that scenario would be to set a socket-level send timeout using the socket API directly. Indy does not implement send timeouts in its logic.

I'm looking for the correct way to set that timeout, via INDY or via a direct API call in Windows, but I'm stuck in that, so I come here looking for help.

If that's not possible, I could implement a Timeout mechanism on a secondary thread, but I'm not sure what's the correct way to reset the connection from this secondary thread on my end to let the worker thread continue it's work.


Solution

  • I'm looking for the correct way to set that timeout, via INDY or via a direct API call in Windows

    In my previous comment, when I said "set a socket-level send timeout using the socket API directly", I was refering to the SO_SNDTIMEO socket option via the setsockopt() function.

    In terms of Indy, you can call setsockopt() by using the TIdSocketHandle.SetSockOpt() method, eg:

    // Windows
    
    SomeConnection.Socket.Binding.SetSockOpt(Id_SOL_SOCKET, Id_SO_SNDTIMEO, TimeoutInMS);
    

    // 'Nix
    
    var tv: timeval;
    ...
    SomeConnection.Socket.Binding.SetSockOpt(Id_SOL_SOCKET, Id_SO_SNDTIMEO, Integer(@timeval));
    
    or:
    
    GBSDStack.SetSocketOption(SomeConnection.Socket.Binding.Handle, Id_SOL_SOCKET, Id_SO_SNDTIMEO, timeval, sizeof(timeval));
    

    (the TIdTCPConnection.Socket property is a shorthand for accessing TIdIOHandlerSocket when the TIdTCPConnection.IOHandler has been assigned a TIdIOHandlerSocket or descendant).

    Just know that if a timeout does occur, you can't know exactly which data was actually transmitted to the peer, so recovery is usually impossible in most protocols. All you can really do is just close the connection and reconnect.