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