I am working on datasnap client server software. When executed, server reads data from my database and keeps it in memory. Every client when connecting to the server, requests the data calling a procedure on the server. But I am having problems with huge memory consumption on server side.
Server lifeCycle set to invocation or session does not affect the huge memory used by server.
In this test, when launching server, it uses about 5MB. Connection a client and getting data from the server, makes the server use another 58MB. For every next client! And this is just with 6 objects. In my real software server gets over 200MB for a client. My old version of the software has 50+ clients running.
I tested this with Delphi XE6, XE7, XE8, 10, 10 with Update 1. ReportmemoryLeakAtShutdown reports nothing at client, and a very small amount at server, but it is reported as a known bug.
Am I doing something very wrong, or it is a Delphi problem?
Here is my test source:
common unit for server and client:
TOwnedFlag = (ofOwned);
TOwnedFlags = set of TOwnedFlag;
TMarshalList<T: class> = class
private
FList: TArray<T>;
FFlags: TOwnedFlags; // use set for internal flags because sets are not marshalled
public
constructor Create(AList: TArray<T>; AOwnsItems: Boolean = True); overload;
constructor Create; overload;
destructor Destroy; override;
property List: TArray<T> read FList;
end;
TMyClass = class
ID: integer;,
Name: String;
Desc: String;
Desc1: String;
Desc2: String;
Desc3: String;
....
constructor Create;
end;
implementation
constructor TMarshalList<T>.Create(AList: TArray<T>; AOwnsItems: Boolean);
begin
FList := AList;
if AOwnsItems then FFlags := [ofOwned];
end;
constructor TMarshalList<T>.Create;
begin
FFlags := [ofOwned];
end;
destructor TMarshalList<T>.Destroy;
var
LItem: T;
begin
if ofOwned in FFlags then
for LItem in FList do
LItem.Free;
inherited;
end;
constructor TMyClass.create;
var
i: integer;
begin
//this I made just to have objects with noticeable size...
desc := '01101011010110101010101010101010110101010100110100101010010101010101001010101010101010010100100010001111011010010101010110101101'+
'01101011010110101010101010101010110101010100110100101010010101010101001010101010101010010100100010001111011010010101010110101101'+
'01101011010110101010101010101010110101010100110100101010010101010101001010101010101010010100100010001111011010010101010110101101'+
'01101011010110101010101010101010110101010100110100101010010101010101001010101010101010010100100010001111011010010101010110101101';
for I := 1 to 8 do
desc := desc + desc;
desc1 := desc;
desc2 := desc;
....
end;
ServerContainer
MyList: TList<TMyClass>;
procedure TServerContainer.DataModuleCreate(Sender: TObject);
var
c: TMyClass;
I: Integer;
begin
MyList := TList<TMyClass>.create;
for I := 0 to 5 do begin
c := TMyClass.create;
c.ID := i;
c.Name := inttostr(i);
MyList.Add(c);
end;
end;
procedure TServerContainer.DataModuleDestroy(Sender: TObject);
var
I: Integer;
begin
for I := 0 to myList.Count-1 do
myList[i].Free;
myList.Free;
end;
ServerMethods
function TServerMethods.getMarshalList: TMarshalList<TMyClass>;
var
c: TMyClass;
I: Integer;
l: TList<TMyClass>;
begin
l := TList<TMyClass>.create;
for I := 0 to servercontainer.MyList.Count-1 do begin
c := TMyClass.create;
c.ID := servercontainer.MyList[i].ID;
c.Name := servercontainer.MyList[i].Name;
l.Add(c);
end;
Result := TMarshalList<TMyClass>.Create(l.ToArray, True);
l.free;
GetInvocationMetaData.CloseSession := True;
end;
Client
procedure TForm1.Button1Click(Sender: TObject);
var
server: TServerMethodsClient;
c: TMyClass;
I: Integer;
list: TMarshalList<TMyClass>;
begin
ticks := GetTickCount;
memo1.Lines.clear;
server := TServerMethodsClient.Create(SQLConnection1.DBXConnection);
list := server.getMarshalList;
for i := 0 to high(list.List)-1 do begin
c := list.List[i];
end;
server.Free;
end;
you can download the sources here: https://kikimor.com/owncloud/index.php/s/Aw55lBzvFX9Q6tl