TLinqExpression and memory management

Hello,


I'm working on resolving some memory leak issue in my application and I found something I cannot understand: madexcept memory leak report flags a leak every time I do this:

[code]
function TGMQueueSQLiteImpl.getMessageFilter: TLinqExpression;
begin
  result :=
      (
          Linq.GreaterThan('ID', FLastID) and // Any Id larger than the last seen ID
          Linq.GreaterThan('Expiration', TTimeZone.Local.ToUniversalTime(now)) and // Any non-expired message
          Linq.eq(Linq['Handeled'], False)
      );
(...)
[code]

I though there was no issue with returning a TLinqExpression from a function since it is a record and since I have several method that uses the same expression, I centralized that into a function.

Yet, when I look into the way TLinqExpression is implemented, it seems to actually contain reference to classes instances, not records. Yet, I find no trace of code that frees these instances so it looks like madexcept is correct: every time I invoke a TLinqExpression, the application appearss to leak memory.

What am I missing here? Is what I'm doing incorrect? In this case, how should I do it?

Yes, TLinqExpression holds a reference to the underlying TCustomCriterion object that is the one effectively used in an Aurelius query (TCriteria).

If you use that TLinqExpression in a TCriteria, it would be added to the list of TCustomCriterion and will be eventually destroyed when TCriteria is destroyed. 
You should not have a leak if you are using it. What exactly are you doing with the returned TLinqExpression?

Here is some sample of code that makes use of it. WithManager is a method that create a TObjectManager for the database, pass it to the lamda and the frees it (IsMessageforMe implements internal logic and can be ignored)



function TGMQueueSQLiteImpl.QueueEmpty(const MessageType: array of string): Boolean;
var
  IsEmpty: Boolean;
  Filters: TLinqExpression;
begin
  IsEmpty := true;
  Filters := getMessageFilter(MessageType);
  WithManager(
    Procedure (Manager: TObjectManager)
    var
      Cursor: ICriteriaCursor<TMessages>;
      DBMessage: TMessages;
    begin
      // Create the lookup query
      Cursor := Manager.Find<TMessages>()
        .Where(Filters)
        .Open();
      while Cursor.Next do
      begin
        DBMessage := Cursor.Get;
        if IsMessageForMe(DBMessage.Recipient) then
        begin
          IsEmpty := false;
          break;
        end;
      end;
      Cursor := nil;
    end
    );
  result := IsEmpty;
end;

That should not leak. What is the exact memory leak report?

This is what I get:



allocation number: 721989
program up time: 8.78 s
type: TAndExpression
address: $1ad39048
size: 16
access rights: read/write


thread $1a34 (TOTPWorkerThread):
671cb0f6 madExcept32.dll madExceptDbg                    1603 GetMemCallback
004074a8 wadmain.exe     System                           187 @GetMem
0040a3ee wadmain.exe     System                           187 TObject.NewInstance
0040ac0f wadmain.exe     System                           187 @ClassCreate
01d72112 wadmain.exe     Aurelius.Criteria.Base               TLogicalExpression.Create
01d854e6 wadmain.exe     Aurelius.Criteria.Expression         TExpression.And_
0204b835 wadmain.exe     Aurelius.Criteria.Linq               TLinqExpression.&op_LogicalAnd
0206ec70 wadmain.exe     GIT.MessageQueue.Storage.SQLite  276 TGMQueueSQLiteImpl.getMessageFilter
0207023e wadmain.exe     GIT.MessageQueue.Storage.SQLite  441 TGMQueueSQLiteImpl.TryDequeue
0207031f wadmain.exe     GIT.MessageQueue.Storage.SQLite  496 TGMQueueSQLiteImpl.TryDequeueAll
0211dcad wadmain.exe     GBD.MessageQueue.Container       498 TGBDMessageQueueContainer.StartTask$ActRec.$0$Body
018bf127 wadmain.exe     OtlTaskControl                  1985 TOmniTaskExecutor.Asy_Execute
018bdec3 wadmain.exe     OtlTaskControl                  1578 TOmniTask.InternalExecute
018bdbe4 wadmain.exe     OtlTaskControl                  1496 TOmniTask.Execute
018b0020 wadmain.exe     OtlThreadPool                    885 TOTPWorkerThread.ExecuteWorkItem
018afb9b wadmain.exe     OtlThreadPool                    842 TOTPWorkerThread.Execute
004b67cf wadmain.exe     madExcept                            HookedTThreadExecute
0054f735 wadmain.exe     System.Classes                       ThreadProc
0040c3f0 wadmain.exe     System                           187 ThreadWrapper
004b66b5 wadmain.exe     madExcept                            CallThreadProcSafe
004b671a wadmain.exe     madExcept                            ThreadExceptFrame
77678482 KERNEL32.DLL                                         BaseThreadInitThunk


memory dump: 
1ad39048  2c 61 d6 01 68 8e d3 1a - 60 a8 d4 1a 00 00 00 00  ,a..h...`.......

I think I got it: the code that leaks is conditional.

Basically, it looks like this:


  Filters: GetFilters;
  Withmanager(
      Procedure (Manager: TObjectManager)
      var
        Cursor: ICriteriaCursor<TMessages>;
      begin
         if QueueNotEmpty then
        Cursor := Manager.Find<TMessages>()
          .Where(Filters)
          .Open();
           (...)
      end);
    


So if QueueNotEmpty returns false, the filter is not used and the internal object will leak.

Thanks for the pointers

A wanted to add: is there a way to free these objects if the filter is not used ?

The simplest thing you can do is declare your Filters variable as TCustomCriterion instead of TLinqExpression (or maybe even the function return, can be already a TCustomCriterion).


Then you simply call Filters.Free if it's not used in any criteria.

Perfect. Thank you