0
votes

I'm using Windows 10 1903, Delphi Rio 10.3.3 with Indy 10.6.2.5366.

I'm upgrading an old Windows Service application that used TServerSocket to Indys TIdTCPServer. The main reason is that I have to add IPv6 support to the application.

The problem I have is that the TIdTCPServer eats threads. For every connection to the TIdTCPServer a new thread is allocated (as it should) but when the client disconnects the thread doesn't close.

If I connect two clients simultaneously, IdTCPServer1.Contexts.LockList.Count = 2 and the OnExecute event are fired twice every "cycle" as it should. If I then disconnect one client so IdTCPServer1.Contexts.LockList.Count = 1, the OnExecute are fired only once every cycle (also as it should), but both threads are still running. (I use Windows taskmanager to see this)

I built a simple test-application that connects and disconnects randomly with a few seconds interval, started 5 programs and my Service crashes when reaching approximately 150 threads.

Here is my OnExecute event:

procedure T#############.IdTCPServer1Execute(AContext: TIdContext);
var
 incomestring: string;
begin
 try
   if AContext.Connection.IOHandler.InputBufferIsEmpty then
   begin
    AContext.Connection.IOHandler.CheckForDataOnSource(100);
    AContext.Connection.IOHandler.CheckForDisconnect;
    if AContext.Connection.IOHandler.InputBufferIsEmpty then
     Exit;
   end;

   incomestring:= AContext.Connection.IOHandler.InputBufferAsString(en8bit);

   if length(trim(incomestring)) = 0 then
   begin
    exit;
   end;

   DataRead(incomestring, AContext); // Send data be processed
  except
   on E: Exception do
   begin
    if E is EIdException then
     raise // <- re-raise only Indy-specific exceptions end;
    else
    begin
     inc(ErrorCount);
     Logg('Error in IdTCPServer1Execute: ' + E.Message, ltError, AContext);
    end;
   end;
 end;
end;

The reason I'm using InputBufferAsString is that I can't control the clients connecting to the server. So there isn't always CR or LF at the end (as ReadLn wants). And also the clients stay connected to the server for many hours before disconnecting.

1
You really felt the need to censor the form name? And the best you could do was T#### instead of just the default TForm1?Ken White
This can be a matter of thread pooling. There's really no reason for crashing when thread count is close to 150, unless you're out of system resources. I think your problem lies elsewhere.Peter Wolf
Hi, did you try to use the connection's ReadTimeout property?Tamas
By default, Indy DOES NOT use thread pooling. You have to enable that explicitly by assigning a TIdSchedulerOfThreadPool to TIdTCPServer.Scheduler, otherwise TIdSchedulerOfThreadDefault is used instead, which doesn't pool. But are you really calling IdTCPServer1.Contexts.LockList(), though? If so, are you calling IdTCPServer1.Contexts.UnlockList() as well? I don't see anything in this code that would cause threads to not terminate properly, unless either DataRead() or Logg() are blocking threads, or you are keeping the Contexts locked. Something outside of the code shown.Remy Lebeau
Thanks for replying. Yes I call UnlockList() in a finally clause every time I iterate through LockList(). The problem seems to be in the DataRead procedure. I'll investigate further.Rogfitz

1 Answers

0
votes

Problem solved! In my DataRead() procedure I use the TIdContext.Data member to store a pointer to a class with extra info. On IdTCPServer1Disconnect I free the extra info class but I forgot to set the TidContext.Data to nil.

So, if You ever use the TIdContext.Data member, remember to set it to nil when done with it! Otherwise the thread will keep going forever.