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;
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;