1

I'm developing as small diabetes program using Delphi 5 and ADO. I do a little query like this:

function GetLowestGlucoseLevel(StartDate:string;EndDate:string): Integer;
var
  Q:TADOQuery;
begin
   try
      Q:=TADOQuery.Create(Application); //Separate unit, owner set to App
      Q.Connection:=dtMod.ADOCon;
      Q.DisableControls;
      Q.Close;
      Q.SQL.Clear;
      Q.SQL.Add('SELECT Min(qGlucose.Glucose) AS MinOfGlucose from qGlucose');
      Q.Parameters[0].Value:=StartDate;
      Q.Parameters[1].Value:=EndDate;
      Q.Open;

      Result:=Q.FieldByName('MinOfGlucose').AsInteger;

      Q.Close;
    finally
      Q:=nil;
      Q.Free; 
    end; 
end;

The query runs OK and returns the result as expected. However, when I checked Windows Task Manager, memory usage keeps on rising rather than decreasing after the query.

How to fix this?

Thanks!

AFF
  • 61
  • 2
  • 4
  • Additional comment on your code: The Q object is created inside your routine, thus the call to DisableControls/Close/Clear are totally unnecessary, because the query has no associated controls, is not open and SQL property is _blank_ upon creation. The performance impact is _almost_ nothing by doing the calls, but I avoid this kind of code by nature, mainly because it makes a simple routine look a bit complicated. – jachguate Oct 27 '10 at 19:11
  • Additional 2: Your query text doesn't contain parameters, but you're asigning two of them in the next line... with no delphi at hand right now, I can't test, but I think there will occur a exception, or at least, undefined behavior. – jachguate Oct 27 '10 at 19:15
  • @Jachguate: about the disablecontrols it's just the habit of mine to ensure it always disable controls before doing sql call. Thanks for pointing it out though :) About the parameters, I'm actually calling a query inside ms access file with parameters, so without setting the parameters in my code, it would raise an exception. – AFF Oct 27 '10 at 22:56
  • 1
    @jachguate: `DisableControls` is necessary. Even without any controls calls to `.Next` are approximately 50x slower if `DisableControls` is not specified (http://stackoverflow.com/questions/369187/bde-vs-ado-in-delphi/6718130#6718130) – Ian Boyd Aug 15 '11 at 02:35

6 Answers6

13

You are leaking the TADOQuery by first setting it to nil, and then calling Free on the nil variable (which does nothing)

jasonpenny
  • 2,999
  • 1
  • 24
  • 23
  • Even when I delete the Q:=nil, the leak is still there. So I think it's not the cause of it. – AFF Oct 27 '10 at 14:00
  • 4
    Windows Task Manager does not show the actual memory usage, http://delphi.about.com/od/delphitips2007/qt/memory_usage.htm . The code in the question only has the leak I described. You should use FastMM and set `ReportMemoryLeaksOnShutdown` as described in the answer by Bharat to find any other leaks. – jasonpenny Oct 27 '10 at 14:06
  • 3
    @AFF Jason is right. Setting `Q` to `nil` before calling `Free` will not free the `TADOQuery` object. When you removed it and still witnessed a memory leak it means you had multiple leaks an only fixed one of them. (either that or you didn't recompile). – Kenneth Cochran Oct 27 '10 at 14:15
  • @Codeelegance: there is FreeandNil in D5 :) – AFF Oct 27 '10 at 14:20
4
  • Did you install Delphi 5 updates? The RTM ADO implementation is known to have issues.
  • Use FastMM4, it should work with Delphi 5 as well, and tell you more about where the leaks are.
  • I already installed the ADO Update 2, but the leak still there. I also installed FastMM4. The leak appears only when i run the query. I couldn't find other leaks which relate to dynamically created forms, etc. – AFF Oct 27 '10 at 14:38
  • Did you configured FastMM4 to report leaks? It will collect a call stack and many other informations to pinpoint the leak, but only if instructed to do so, look at the FastMM4.inc file for the $defines to enable to get detailed informations and how. –  Oct 27 '10 at 14:45
  • I finally figured it out how to use FastMM, it didn't detect any leak from my app. Does this mean it is safe to assume that my app doesn't have leaks? – AFF Oct 27 '10 at 16:08
  • FastMM does an excellent job but may not catch *every* leak, especially those who doesn't use the Delphi MM. Also you should understand how Delphi and Windows allocate and deallocate memory, they won't do it for every bite, they will perform it in larger blocks. –  Oct 28 '10 at 13:36
3

The Delphi way:

function GetLowestGlucoseLevel(const StartDate:string; const EndDate:string): Integer;
var
  Q:TADOQuery;

begin

    Q:=TADOQuery.Create(nil); //(nil) because local use only. Placed before try\finally block 
                              //because if it fails to .create then there would be no object to
                              //.Free 
    try

      Q.Connection := dtMod.ADOCon;

      //------can erase these------
      //Q.DisableControls; //No controls attached so unnecessary
      //Q.Close;           //Q is local and was never opened so no need to close
      //Q.SQL.Clear;       //Q is local and was never filled so no need to clear

      Q.SQL.Add('SELECT Min(qGlucose.Glucose) AS MinOfGlucose from qGlucose');
      Q.Parameters[0].Value:=StartDate;
      Q.Parameters[1].Value:=EndDate;
      Q.Open;

      Result := Q.FieldByName('MinOfGlucose').AsInteger;

      Q.Close;

    finally 

      Q.Free;

      //Q := nil          //not needed because Q's scope is local

    end; 
end;
Jink
  • 451
  • 4
  • 6
2

Quote:

finally
  Q:=nil;
  Q.Free; 
end; 

You're kidding, right? First nil the variable, then free it? You're a genius! :-)

Use:

finally
  Q.Free; 
  Q:=nil;
end; 

Or don't even bother assigning nil to it, since Q is a local variable...


But rereading your code, I notice you use Application as owner. As a result, it will not really be a leak, since it will be freed when the application is freed. If you use a form, it would be freed when the owner form gets freed.
What you should try is to call this query about 100.000 times to check if it keeps reserving memory or if it's just increasing memory until a certain size has been reached. The latter is more likely, since the memory is reserved for future ADO calls.

Wim ten Brink
  • 25,901
  • 20
  • 83
  • 149
  • Hahaha, yes, one stupid genius i am! :-D ... anyway, when i call the query again and again and again and again... , the memory use keeps on raising. I suspect something fishy about the ADO itself. – AFF Oct 27 '10 at 14:41
  • 1
    And it's actually not an error. :-) You're just delaying freeing the memory until the application is freed. The Delphi ADO components are known to have some minor memory leaks, though. – Wim ten Brink Oct 27 '10 at 15:15
  • @Workshop Alex: It depends on how you define "memory leak." You say it's a variable that never gets freed, so this is fine. I think a better description is a variable that remains allocated after you're done using it. – Mason Wheeler Oct 27 '10 at 15:51
  • 1
    +1; for explaining `the Application is the owner, so all memory will be released when the application ends`. So during the application run, memory usage will increase for every call of this method. `Reversing the := nil and Free will release memory at the end of the method`. – Jeroen Wiert Pluimers Oct 28 '10 at 07:50
  • @Mason Wheeler, if a variable gets freed in the end, it's not a leak, just a waste of resources for the duration of the application. Normally, you would use a form as owner, especially when you create a TQuery component in design-time as a form control. – Wim ten Brink Oct 28 '10 at 14:42
  • @Workshop Alex: See, there's where your argument starts not making any sense. All variables are part of the heap, afterall, and the heap gets reclaimed by Windows when the program ends, so technically, you could argue that nothing ever gets leaked, right? You have a definition that may be technically correct but is functionally useless. The only *useful* definition of a memory leak is continuing to hold on to memory after you no longer need it. – Mason Wheeler Oct 28 '10 at 16:08
  • @Mason Wheeler, Yes and no. Technically, a good program should have freed all used memory at the moment it terminates. That's just good practice, even though Windows will clean it up afterwards. If the owner had been a form, the component would live as long as the form. Now it's owned by the application, thus it lives as long as the application component. Memory that needs to be cleaned by Windows just means your application hasn't handled all it's data, which could mean data loss. (But in 99.99% of all cases, the data that's leaked was useful at termination.) – Wim ten Brink Oct 29 '10 at 08:47
0

As others pointed out, the finally section should have the 2 statements reversed, like so:

finally
  Q.Free; 
  Q:=nil;  // <- not even necessary since this is a local var
end; 

Or you can call SysUtils.FreeAndNil(Q) (if that is available in Delphi 5, not sure).

Beside that, the TaskManager is an awful instrument to determine memory usage anyway. You might release the memory for Q, but that does not automatically mean the Delphi memory manager releases the memory to the OS.

Arjan de Haan
  • 120
  • 1
  • 5
0

Aside inversing the lines as Arjan, jasonpenny and WorkShop Alex said, you can use Process Explorer to see the real consumption of memory (Private Bytes) of the process. Task Manager is not really suited to this task, as it only shows the working set of the process.

Community
  • 1
  • 1
Fabricio Araujo
  • 3,810
  • 3
  • 28
  • 43