5

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

Kiril Hadjiev
  • 302
  • 2
  • 13
  • Anyway, tried with TObjectList, result is same - after first client call server uses more than 40MB. I read that this memory is used to cache data for the session, but I set up the server LifeCycle to invocation, so session should be closed right after the call. But it doesn't. In about 20 min server releases most of the used memory, but not all of if. If all clients disconnect, server memory does not shrink to it's initial use. And this behaviour is not good for a 24/7 server. – Kiril Hadjiev Jan 12 '16 at 12:11
  • What happens if the server method returns a very simple class instance, with a simple string property of (for example) 1K length? (just to see if usage of Generics makes a difference) – mjn Jan 12 '16 at 14:45
  • Tried with a class, containing array of TMyClass, with destructor, that frees the objects in the array and setlength(myarray, 0). No difference. – Kiril Hadjiev Jan 12 '16 at 18:41
  • Finally, gave up on DataSnap. Seems it is not a working solution for me. It was pretty easy to move to mORMot. mORMot is MUCH faster and less memory consuming. Thank you Synopse Team! – Kiril Hadjiev Jul 19 '16 at 06:28

0 Answers0