Douglas Maia
Douglas Maia

Reputation: 21

how to send / receive TObjectList by DataSnap solving the memory leak?

Prototype of a client application and DataSnap server. I want to transmit a TObjectList from the server to the client.

This is working , but all the objects that I convey remain in memory on the server and the client.

What am I doing wrong ?

Lifecycle = Session

The TPessoa object implements a TObjectList of another class (TConta):

   TConta = class(TObject)
  private
    FBanco: string;
    FConta: Integer;
    FAgencia: Integer;
    procedure SetAgencia(const Value: Integer);
    procedure SetBanco(const Value: string);
    procedure SetConta(const Value: Integer);
   published
     property Banco : string read FBanco write SetBanco;
     property Agencia : Integer read FAgencia write SetAgencia;
     property Conta : Integer read FConta write SetConta;
   end;

   TContasCollection = TObjectList<TConta>;

   TPessoa = class(TObject)
  private
    FContas: TContasCollection;
    FId: Integer;
    FNome: string;
    procedure SetContas(const Value: TContasCollection);
    procedure SetId(const Value: Integer);
    procedure SetNome(const Value: string);
   published
     property Id : Integer read FId write SetId;
     property Nome : string read FNome write SetNome;
     property Contas : TContasCollection read FContas write SetContas;
   end;

Method public in ServerMetodsUnit :

function getPessoa(id : Integer) : Tpessoa;

function TServerMethods1.getPessoa(id: Integer): Tpessoa;
begin
  result := Tpessoa.create;
  result.id := id;
  result.nome := 'NoName';
  result.contas := getContas;
end;

function TServerMethods1.getContas: TContasCollection;
var conta : TConta;
begin
  conta := TConta.Create;
  conta.Banco := 'CEF';
  conta.Agencia := 1;
  conta.Conta := 123;

  Result := TContasCollection.Create();
  Result.Add(conta);
end;

Client:

procedure TForm2.btn1Click(Sender: TObject);
var pessoa : Tpessoa;
begin
  pessoa := ClientModule1.ServerMethods1Client.getPessoa(1);
  mmo1.Lines.Add(pessoa.Nome);
  mmo1.Lines.Add(pessoa.Contas[0].Banco);
end;

correct result, however the memory leak message is displayed in the Server and the Client (System.ReportMemoryLeaksOnShutdown := true;):

Unexpected Memory Leak An unexpected memory leak has occurred. The unexpected small block leaks are:

1 - 12 bytes: TMoveArrayManager x 1, Unknown x 1 13 - 20 bytes: TConta x 1, UnicodeString x 1 37 - 44 bytes: TObjectList x 1

how to solve this memory leak not to affect the service ?

Upvotes: 2

Views: 911

Answers (1)

Dalija Prasnikar
Dalija Prasnikar

Reputation: 28519

Memory leak occurs because you are not releasing objects you are creating.

For start in client part you are creating pessoa object in btn1Click as local variable but you are not releasing it.

procedure TForm2.btn1Click(Sender: TObject);
var pessoa : Tpessoa;
begin
  pessoa := ClientModule1.ServerMethods1Client.getPessoa(1);
  mmo1.Lines.Add(pessoa.Nome);
  mmo1.Lines.Add(pessoa.Contas[0].Banco);
  pessoa.Free; // you are no longer using pessoa object after that point so release it
end;

TPessoa class has FContas field that we don't see how is created or released, maybe you have just omitted the code, and maybe it is not there at all. Anyway, FContas has also be released at some point. If TPessoa class is owner of FContas field (and it looks like it should be) then you would have to add destructor to TPessoa class where you would free FContas collection.

FContas is TObjectList that by default owns added objects and those would be released when FContas is released.

There is also possible leak of FContas object in SetContas method, but without knowing how that code looks it is hard to tell is it leaking or not.

TPessoa = class(TObject)
....
public
  destructor Destroy; override;
end;

destructor TPessoa.Destroy; 
begin
  FContas.Free;
  inherited;
end;

procedure SetContas(const Value: TContasCollection);
begin
  FContas.Free; // release old FContas collection if there is one
  FContas := Value; // reference FContas grabs ownership here 
end;

Basically, every object you create you have to free after you no longer need it, unless there is some other object instance that will grab ownership of that object and do it for you, like TObjectList does.

Above code assumes that FContas field is owner of collection it holds, and it is important that you don't grab that reference and hold it beyond lifetime of it's owner object (TPessoa) instance.

For example, following code would be wrong:

procedure TForm2.btn1Click(Sender: TObject);
var 
  pessoa : Tpessoa;
  contas: TContasCollection;
begin
  pessoa := ClientModule1.ServerMethods1Client.getPessoa(1);
  contas := pessoa.Contas;
  pessoa.Free; // <-- after that point contas points to released object
  mmo1.Lines.Add(contas[0].Banco); // <-- dangling pointer use
end;

There are also reference counted object instances that are automatically released after the last reference to them goes out of scope (usually descendants of TInterfacedObject class), but managing those is completely different story. You are not using them here and I am mentioning them only for completeness.

Upvotes: 1

Related Questions