Subject Re: [IBO] Memory leak
Author Helen Borrie (TeamIBO)
At 04:40 PM 09-08-02 -0500, you wrote:
>I am losing several MB of memory (Task Manager MemUsage) every time I call
>Open with a TIB_Cursor. After I call Close, the MemUsage stays near its peak.
>
>Delphi 6 Pro, IBO 4.2 (IB_Connection, IB_Cursor), W2K (local/network, makes
>no difference):
>
>For example, if I add strings to a TStringList by alternately clicking
>ButtonSetList and ButtonFreeList, the MemUsage goes like this, which is as
>it should be (the actual code is just below the MemUsage listing):
>
>Base MemUsage: 10916
>SetList: MU = 11108
>FreeList: MU = 10916
>SetList: MU = 11108
>FreeList: MU = 10916
>SetList: MU = 11108
>FreeList: MU = 10916
>
>procedure TfrmMain.ButtonSetListClick(Sender: TObject);
>var n : integer;
>begin
> aList := TStringList.create;
> for n := 0 to 4678 do aList.Add('SomeFirst SomeLastName');
>end
>
>
>procedure TfrmMain.ButtonFreeListClick(Sender: TObject);
>begin
> aList.Clear;
> aList.Free;
>end;
>--------------------------------------
>
>But if I use a TIB_Cursor to select 4678 records, and alternately click
>ButtonSelectNames and ButtonFreeList, the MemUsage goes like
>this (MemUsage goes up by 9700KB on each execution of the select, and goes
>down 300KB when the stringlist is freed). The computer becomes unusable,
>and I have to reboot the application to release the memory:
>
>Base MemUsage: 11624
>SetList: MU = 23168
>FreeList: MU = 21100
>SetList: MU = 30828
>FreeList: MU = 30524
>SetList: MU = 40292
>FreeList: MU = 40140
>SetList: MU = 49840
>FreeList: MU = 49504
>
>procedure TfrmMain.ButtonSelectNamesClick(Sender: TObject);
>begin
> aList := TStringList.create;
> n := 0;
> with TIB_Cursor.create(nil) do try
> IB_Connection:=DModOne.Connection1;
> IB_Transaction:=DModOne.Transaction1;
> SQL.Add(' SELECT LASTNAME, FIRSTNAME FROM PERSONS WHERE NAME_OID <
>120000 ORDER BY LASTNAMEUPPER');
> Open;
> if not eof then while not eof do begin
> aList.Add(Fields[0].AsString + ' ' + Fields[1].AsString );
> Next;
> end;
> finally
> Close;
> free;
> end;
>end;
>
>procedure TfrmMain.ButtonFreeListClick(Sender: TObject);
>begin
> aList.Clear;
> aList.Free;
>end;
>
>
>Is there some way to release a buffer somewhere, or what I am doing [wrong]
>to cause this memory loss problem? It is happening A LOT for me
>(everywhere I do a SELECT)....

I think there are two possible issues here. One is that you are repeatedly
creating the cursor and preparing a "new" statement each time, without any
sign that you are committing your transaction or Unpreparing the statement
before you free the instance of the cursor. You are building up unreleased
and unreachable puddles of unreleased memory in both the app and the
database. This long-running transaction is probably also playing havoc
with garbage collection on the server as well, particularly if the other
parts of your app are doing stuff to rows based on your selections.

The other is that you are doing repeated creation and destruction of an
object that you recreate repeatedly, at the user's behest, to do an
identical task. A persistent IB_Cursor, properly managed, will be much
more efficient (cause less fragmentation of memory) than this repeated
thrashing.

Create the persistent IB_Cursor object in the IDE with the following
properties:

IB_Connection: ModOne.Connection1
IB_Transaction: DModOne.Transaction1
SQL:
SELECT LASTNAME, FIRSTNAME FROM PERSONS
WHERE NAME_OID < :NAME_OID
ORDER BY LASTNAMEUPPER

Then, the flow for populating your list would be more like the following:

procedure TfrmMain.ButtonSelectNamesClick(Sender: TObject);
begin
aList := TStringList.create;
// n := 0; doesn't seem to do anything
with MyCursor do
try
if not IB_Transaction.TransactionIsActive then
IB_Transaction.StartTransaction;
if not Prepared then Prepare;
Params[0].AsInteger := SomeVariable;
First;
//
// not Open - that is for scrollable datasets;
//
if not eof then
while not eof do begin
aList.Add(Fields[0].AsString + ' ' + Fields[1].AsString );
Next;
end;
finally
Close;
Unprepare; // see note
if IB_Transaction.TransactionIsActive then
IB_Transaction.Commit;
// free; Not required for a persistent ib_cursor
end;
end;

Note: A thread about Unprepare ran through the list about a week ago, in
time actually for a note to make it into the new build of the helpfile.

In your case, because you have this procedure on a button, if you use a
persistent ib_cursor it doesn't really make sense to Unprepare this
statement, since it adds unnecessary overhead the second and subsequent
times the user refreshes the Person list. Leave it prepared; and the
ib_connection will clean it up when the user shuts down the application.

If you stick with the original plan of recreating the ib_cursor each time,
then make sure you take care of everything - committing the transaction and
unpreparing the statement as part of your pre-release cleanup.

regards,
Helen Borrie (TeamIBO Support)

** Please don't email your support questions privately **
Ask on the list and everyone benefits
Don't forget the IB Objects online FAQ - link from any page at
www.ibobjects.com