Tell me more ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

I tried to ask this on Stack Overflow, but it got closed. Someone recommended I ask her, so ...

This is not a code review about style or maintainability - what you will see is less than 5% of my app, but the part where the memory leak is - this is a code review to find a single bug. Can someone please help? I have been looking for leaks.

Here's the previous question ... (I offered a 500 point bounty there, but I don't have 500 points here; time to get active, I guess)


I have been looking and looking and looking and I just cannot find it!

By process of elimination I have thrown out 95% of my code and I still can't find it in the remaining lines. Obviously I have a blind-spot or knowledge gap, can you please help?

I have a very simple client/server setup using Indy 10 (and XE2 starter) and have narrowed it down to the handling of a TCP “heartbeat” (every second).

After six or seven hundred iterations, I get an exception. I have seen a few different ones, just by randomness, but 95% of the time it is “out of memory” (as an aside, I just got thread creation error, with this stack dump, but that is just the symptom, not the cause)

:773eb9bc KERNELBASE.RaiseException + 0x58
System.Classes.TThread.Create(???)
IdThread.TIdThread.Create(True,True,'IdCmdTCPServer1Scheduler User')
IdThread.TIdThreadWithTask.Create(nil,'IdCmdTCPServer1Scheduler User')
IdSchedulerOfThread.TIdSchedulerOfThread.NewThread
IdSchedulerOfThreadDefault.TIdSchedulerOfThreadDefault.NewThread
IdSchedulerOfThreadDefault.TIdSchedulerOfThreadDefault.AcquireYarn
IdCustomTCPServer.TIdListenerThread.Run
IdThread.TIdThread.Execute
:004bee25 HookedTThreadExecute + $2D
System.Classes.ThreadProc($92D8F7C)
System.ThreadWrapper($92D4FF8)
:004bed07 CallThreadProcSafe + $F
:004bed74 ThreadExceptFrame + $3C
:76a1339a kernel32.BaseThreadInitThunk + 0x12
:77d79ef2 ntdll.RtlInitializeExceptionChain + 0x63
:77d79ec5 ntdll.RtlInitializeExceptionChain + 0x36

I will post the server code below, but both projects can be downloaded from https://www.opendrive.com by logging in as user out_of_memory with password 12345.

A few points:

  • I have no calls to .Create() in my code. Indy does, of course
  • I have no threads
  • The code will look bad mostly because I threw out so much of it that what remains might not make sense
  • I am using GExperts debug tracing, but you can rip that out if you want to
  • The timer in real life is 30 seconds, but I jacked it up to 1 second, just to get it to crash faster
  • I am using real world IP addresses of 10.21.18.206 for client and 10.18.21.211 for server
  • Feel free to change any of the above and generally remove code until it works. That is what I will be doing until someone beats me to it

Final thought, maybe there is something I don’t know about the Indy TCP command Handler? Maybe I am supposed to free something? Or maybe it’s not in the code, but in a setting of the object inspector?

Here is the horrible server code for any code readers, or d/l both projects. Please help; I am at my wit’s end

unit fmainForm;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls, IdContext,
  IdAntiFreezeBase, Vcl.IdAntiFreeze, IdBaseComponent, IdComponent,
  IdCustomTCPServer, IdTCPServer, IdCmdTCPServer, IdCommandHandlers, Vcl.Grids;

const UM_HEART_BEAT = WM_USER + 1;

type
  TForm2 = class(TForm)
    Edit1: TEdit;
    Label1: TLabel;
    Label2: TLabel;
    Edit3: TEdit;
    Edit4: TEdit;
    Label3: TLabel;
    IdCmdTCPServer1: TIdCmdTCPServer;
    IdAntiFreeze1: TIdAntiFreeze;
    ToolsStringGrid: TStringGrid;
    Timer1: TTimer;
    procedure FormCreate(Sender: TObject);
    procedure IdCmdTCPServer1CommandHandlers0Command(ASender: TIdCommand);
    procedure Timer1Timer(Sender: TObject);
    procedure IdCmdTCPServer1AfterBind(Sender: TObject);
  private
    function  GetToolNumberFromContext(const Context : TIdContext) : Integer;
    procedure ToolOnLine(toolNumber : Integer);

  public
    { Public declarations }
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

uses debug_utils;

const
  // Sets UNIX_START_DATE to TDateTime of 01/01/1970
  UNIX_START_DATE: TDateTime = 25569.0;

// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
function DateTimeToUnix(ConvDate: TDateTime): Longint;
begin
  Result := Round((ConvDate - UNIX_START_DATE) * 86400);
end;

// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
function GetCurrentUnixTimeStamp() : LongInt;
begin
   Result := DateTimeToUnix( Now() );
end;

// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
procedure TForm2.FormCreate(Sender: TObject);
   var i : Integer;
begin
   IntializeDebugTrace(3);
   TraceShowMilliSeconds(True);
   TraceClear();
   TracePause();

   ReportMemoryLeaksOnShutdown := True;
   Edit3.Text := FormatDateTime('hh:mm:ss' ,Now());

   for i := 0 to 3 do
      ToolsStringGrid.Cells[0, i] := '10.21.18.206';
end;

// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
function  TForm2.GetToolNumberFromContext(const Context : TIdContext) : Integer;
   var IP: String;
       i : Integer;
begin
   IP := Context.Binding.PeerIP;

   // 3 : skip header row & two store rooms, start at first tool
   for i := 3 to Pred(ToolsStringGrid.RowCount) do
   begin
      if IP = ToolsStringGrid.Cells[0, i] then
      begin
         Result := i - 3;  // We number tools zer0 internally in the code, so subtract the start index (3)
         Exit;
      end;
   end;

   TraceError('Communication received from unknown PC (' + IP + ')');

   MessageDlg('Fatal error. '+#13+#10+''+#13+#10+'Communication received from unknown PC (' + IP + ')'+#13+#10+''+#13+#10+'Please remove the machine from the network', mtError, [mbOK], 0);
   Result := 0;
   Halt;
end;  // GetToolNumberFromContext()


// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
procedure TForm2.IdCmdTCPServer1AfterBind(Sender: TObject);
begin

end;

// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
procedure TForm2.IdCmdTCPServer1CommandHandlers0Command(ASender: TIdCommand);
   var toolNumber : Integer;
begin
   toolNumber := GetToolNumberFromContext(ASender.Context);

   TraceInfo('Server: ' + ASender.Context.Binding.PeerIP + ']' + 'Received "HEART_BEAT from tool #' + IntToStr(toolNumber + 1) + '"');
   ToolOnLine(toolNumber);

   Edit1.Text := IntToStr(StrToInt(Edit1.Text) + 1);
   Edit4.Text := FormatDateTime('hh:mm:ss' ,Now());

   ASender.Reply.SetReply(200, IntToStr(42));
end;


// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
procedure TForm2.Timer1Timer(Sender: TObject);
begin
   Edit4.Text := FormatDateTime('hh:mm:ss' ,Now());
end;

// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
procedure TForm2.ToolOnLine(toolNumber : Integer);
begin

TraceInfo('Server:  tool online at ' + IntToStr(GetCurrentUnixTimeStamp()));
end;   // ToolOnLine();


end.
share|improve this question
2  
Sometimes memory leaks are not actually where you expect them. Hence for someone on this site to help find it is pretty hard. TBH, hate to move you back but I think at the moment this question is really best suited to SO, but you might get some help if your lucky from some Delphi experts... – dreza Oct 7 '12 at 5:11
+! @dreza Yes, I totally agree - where the leak shows up is the symptom, not the cause. I have narrowed it down now & it looks like a problem in my TCP comamnd handler. A little more work will either solve it or see it back on SO. If you care to post your comment as an answer, I will award it. – Mawg Oct 8 '12 at 2:13
1  
looking at the code you are doing things in the context of the IdCmdTCPServer1CommandHandlers0Command that are not thread safe. This in my experience can lead to random exceptions of type invalid pointers, access violations and other seemingly random exceptions. you must synchronise to access the vcl components. – MikeT Oct 9 '12 at 20:48
+1 @MikeT it is apity that they closed it as you have indeed identified the bug and I would have very much liked to award you the answer. Thank you very much for your help. – Mawg Oct 10 '12 at 5:18

closed as off topic by Brian Reichle, Glenn Rogers, Jeff Vanzella, Corbin, svick Oct 9 '12 at 10:03

Questions on Code Review Stack Exchange are expected to relate to code review request within the scope defined in the FAQ. Consider editing the question or leaving comments for improvement if you believe the question can be reworded to fit within the scope. Read more about closed questions here.

Browse other questions tagged or ask your own question.