0
votes

I have a simple TCP file server program developed in Delphi RIO + Indy TCP Server. When 2 or more clients asks for the files, the CPU runs very high in 90s. This spooks off the server team and during this time, they have hard time login into the server on which the program is running.

Based on other threads on the subject, when I put IndySleep(x), it does bring the CPU down and the avg stays in 50-60s. I understand that putting IndySleep() may throttle a bit, but it works!

The files it serves are already compressed and vary in size from 1KB to <10MB.

Is there anything else I can do to improve overall CPU usage, without or with little IndySleep()?

Here is the code snippet:

procedure TMainForm.IdTCPSyncServerExecute(AContext: TIdContext);
begin
  if (not AContext.Connection.IOHandler.InputBufferIsEmpty)
    and (AContext.Connection.Connected) then
  begin
      SendFile(AContext, AContext.Connection.IOHandler.ReadLn);

    //IndySleep(1000 div IdTCPSyncServer.Contexts.Count); // For high CPU
    IndySleep(500); // For high CPU
  end;
end;

procedure TMainForm.SendFile(AContext: TIdContext; AFileName: string);
var
  lStream: TFileStream;
begin
    lStream := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyWrite);
    if Assigned(lStream) then
    begin
      try
        WriteRespHeader(AContext, 1, lStream.Size); //Custom fn() writes back to client file size and other useful info
        AContext.Connection.IOHandler.LargeStream := False; // 32-bit
        lStream.Position := 0;
        AContext.Connection.IOHandler.Write(lStream, lStream.Size);
      finally
        lStream.Free;
      end;
      AddLogMsg(AContext.Binding.PeerIP + ' : Sent File: ' + AFileName); // Thread.Queue() based logging
    end;
end;
1

1 Answers

2
votes

You have the call to IndySleep() in the wrong place. If there is nothing available from the client to read yet, you are exiting OnExecute immediately and coming right back in, creating a tight loop. That is where your high CPU usage is likely occurring. Sleep only when there is nothing available yet, eg:

procedure TMainForm.IdTCPSyncServerExecute(AContext: TIdContext);
begin
  if (not AContext.Connection.IOHandler.InputBufferIsEmpty)
    and (AContext.Connection.Connected) then
  begin
    SendFile(AContext, AContext.Connection.IOHandler.ReadLn);
  end else begin
    //IndySleep(1000 div IdTCPSyncServer.Contexts.Count); // For high CPU
    IndySleep(500); // For high CPU
    // or, use AContext.Connection.IOHandler.Readable() instead...
    // or, use AContext.Connection.IOHandler.CheckForDataOnSoure() instead...
  end;
end;

Alternatively, I usually suggest this kind of manual check instead:

procedure TMainForm.IdTCPSyncServerExecute(AContext: TIdContext);
begin
  if AContext.Connection.IOHandler.InputBufferIsEmpty then
  begin
    AContext.Connection.IOHandler.CheckForDataOnSource(500{1000 div IdTCPSyncServer.Contexts.Count}); // For high CPU
    AContext.Connection.IOHandler.CheckForDisconnect;    
    if AContext.Connection.IOHandler.InputBufferIsEmpty then Exit;
  end;
  SendFile(AContext, AContext.Connection.IOHandler.ReadLn);
end;

But really, in this case, a better solution is to simply not manually check for the presence of client data at all. If there is nothing available to read yet, just let IOHandler.ReadLn() block until something actually arrives, eg:

procedure TMainForm.IdTCPSyncServerExecute(AContext: TIdContext);
begin
  SendFile(AContext, AContext.Connection.IOHandler.ReadLn);
end;