Delphi XE8 unknown memory leaks in simple DataSnap client and server app
Asked Answered
S

2

6

I have created a simple DataSnap client/server application with the wizard in Delphi XE8 using the echostring and reversestring sample methods. When I put "ReportMemoryLeaksOnShutdown := True" in the Server dpr and call the echostring and/or reversestring methods from the client the result is good but when I close the server application (after closing the client) I always get 2 or more unknown memory leaks. Is this a known bug which I can't find on the internet or is there a solution?

Server code:

unit ServerMethodsUnit;

interface

uses System.SysUtils, System.Classes, System.Json,
Datasnap.DSServer, Datasnap.DSAuth, DataSnap.DSProviderDataModuleAdapter;

type
{$METHODINFO ON}
  TServerMethods = class(TDataModule)
  private
    { Private declarations }
  public
    { Public declarations }
    function EchoString(Value: string): string;
    function ReverseString(Value: string): string;
  end;
{$METHODINFO OFF}

implementation

{%CLASSGROUP 'FMX.Controls.TControl'}

{$R *.dfm}


uses System.StrUtils;

function TServerMethods.EchoString(Value: string): string;
begin
  Result := Value;
end;

function TServerMethods.ReverseString(Value: string): string;
begin
  Result := System.StrUtils.ReverseString(Value);
end;

end.

dfm

object ServerContainer: TServerContainer
  OldCreateOrder = False
  Height = 271
  Width = 415
  object DSServer1: TDSServer
    Left = 96
    Top = 11
  end
  object DSTCPServerTransport1: TDSTCPServerTransport
    Server = DSServer1
    Filters = <>
    Left = 96
    Top = 73
  end
  object DSServerClass1: TDSServerClass
    OnGetClass = DSServerClass1GetClass
    Server = DSServer1
    Left = 200
    Top = 11
  end
end

dfm project file

program DataSnap_Server;

uses
  FMX.Forms,
  Web.WebReq,
  IdHTTPWebBrokerBridge,
  ServerMainForm in 'ServerMainForm.pas' {Form2},
  ServerMethodsUnit in 'ServerMethodsUnit.pas' {ServerMethods: TDataModule},
  ServerContainerUnit in 'ServerContainerUnit.pas' {ServerContainer: TDataModule};

{$R *.res}

begin
  ReportMemoryLeaksOnShutdown := True;
  Application.Initialize;
  Application.CreateForm(TForm2, Form2);
  Application.CreateForm(TServerContainer, ServerContainer);
  Application.Run;
end.

client side code generated source

// 
// Created by the DataSnap proxy generator.
// 14-5-2015 22:45:56
// 

unit ClientClassesUnit;

interface

uses System.JSON, Data.DBXCommon, Data.DBXClient, Data.DBXDataSnap, Data.DBXJSON, Datasnap.DSProxy, System.Classes, System.SysUtils, Data.DB, Data.SqlExpr, Data.DBXDBReaders, Data.DBXCDSReaders, Data.DBXJSONReflect;

type
  TServerMethodsClient = class(TDSAdminClient)
  private
    FEchoStringCommand: TDBXCommand;
    FReverseStringCommand: TDBXCommand;
  public
    constructor Create(ADBXConnection: TDBXConnection); overload;
    constructor Create(ADBXConnection: TDBXConnection; AInstanceOwner: Boolean); overload;
    destructor Destroy; override;
    function EchoString(Value: string): string;
    function ReverseString(Value: string): string;
  end;

implementation

function TServerMethodsClient.EchoString(Value: string): string;
begin
  if FEchoStringCommand = nil then
  begin
    FEchoStringCommand := FDBXConnection.CreateCommand;
    FEchoStringCommand.CommandType := TDBXCommandTypes.DSServerMethod;
    FEchoStringCommand.Text := 'TServerMethods.EchoString';
    FEchoStringCommand.Prepare;
  end;
  FEchoStringCommand.Parameters[0].Value.SetWideString(Value);
  FEchoStringCommand.ExecuteUpdate;
  Result := FEchoStringCommand.Parameters[1].Value.GetWideString;
end;

function TServerMethodsClient.ReverseString(Value: string): string;
begin
  if FReverseStringCommand = nil then
  begin
    FReverseStringCommand := FDBXConnection.CreateCommand;
    FReverseStringCommand.CommandType := TDBXCommandTypes.DSServerMethod;
    FReverseStringCommand.Text := 'TServerMethods.ReverseString';
    FReverseStringCommand.Prepare;
  end;
  FReverseStringCommand.Parameters[0].Value.SetWideString(Value);
  FReverseStringCommand.ExecuteUpdate;
  Result := FReverseStringCommand.Parameters[1].Value.GetWideString;
end;


constructor TServerMethodsClient.Create(ADBXConnection: TDBXConnection);
begin
  inherited Create(ADBXConnection);
end;


constructor TServerMethodsClient.Create(ADBXConnection: TDBXConnection; AInstanceOwner: Boolean);
begin
  inherited Create(ADBXConnection, AInstanceOwner);
end;


destructor TServerMethodsClient.Destroy;
begin
  FEchoStringCommand.DisposeOf;
  FReverseStringCommand.DisposeOf;
  inherited;
end;

end.

Own source

unit ClientMainForm;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    Button2: TButton;
    Label1: TLabel;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses
  ClientModuleUnit;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Label1.Caption := ClientModule.ServerMethodsClient.EchoString(Edit1.Text);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  Label1.Caption := ClientModule.ServerMethodsClient.ReverseString(Edit1.Text);
end;

end.
Santasantacruz answered 14/5, 2015 at 21:37 Comment(10)
If the memory leaks do not grow while the apps are running, then don't worry about it. When the application is terminated, windows will recover all memory it was using.Surly
@RohitGupta true, but having a memory leak report window on every termination it makes it harder to notice 'new' leaks. Fixing all leaks and fixing all compiler warnings are standard 'quality assurance' goalsUnblown
can you try 3rd party software EurekaLog 7.2 for catch memory leak?Aubree
I agree with mjn, this is only a simple example what if the application gets more complicated, what then! The strange thing is these memory leaks only happens when there is some kind of communication between the client and the server. If I only work with the server then there are no memory leaks. Also when regenerate the clientclasses with the proxy generator (so communicating with the server) it gives me a third memory leak, even bigger then the first two.Santasantacruz
@Zam: tried EurekaLog but it always gives me a access violation at address 004091F0 in module 'DataSnap_Server.exe'. Read of address FFFFFF. I don't know EurekaLog that good to know what to change there to solve the access violation so that I can see the memory leaks.Santasantacruz
You could add exception into Ignore list. EurekaLog project options -> Advanced -> Exception filtes -> Activate Exceptions filters -> add "EAccessViolation", type "All", and try to change handler to "RTL" or to "None".Aubree
@Rohit Gupta, the same way you can say that your application can freely crash because the computer will shut down one time.Incipient
@Zam, tried the ignore list but still I get the same access violation. Doesn't seem to work.Santasantacruz
Can you email me your projects? Client and Server. [email protected]Aubree
@Zam, I have emailed you the files.Santasantacruz
A
0

Memory leak looks like always exist, or, we doing something wrong.

What I checked:

I move all server app code into the one unit. I try server app without FMX - with VCL. I try to create TDSServer, TDSTCPServerTransport, TDSServerClass in runtime with parents Self and Nil. I try with TServerMethod class owner TPersistance and TComponent (Delphi help says to use it). I try with compiled server app as 32 bit and 64 bit application in Delphi XE7 Update 1 and in Delphi XE8.

EurekaLog 7.2.2 cannot catch details about memory leak also. For avoid catching Access Violation by EurekaLog need to use DSServer1.Stop before exit.

As we could see Access Violation when you using EurekaLog happens there Basically it's in System.TObject.InheritsFrom(???) System._IsClass($64AE4E0,TDSServerTransport) Datasnap.DSCommonServer.TDSCustomServer.StopTransports Datasnap.DSCommonServer.TDSCustomServer.Stop Datasnap.DSServer.TDSServer.Stop Datasnap.DSServer.TDSServer.Destroy System.TObject.Free System.Classes.TComponent.DestroyComponents System.Classes.TComponent.Destroy System.Classes.TDataModule.Destroy System.TObject.Free System.Classes.TComponent.DestroyComponents FMX.Forms.DoneApplication System.SysUtils.DoExitProc System._Halt0 :00408da8 TObject.InheritsFrom + $8

Server app:

unit ufmMain;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
  Datasnap.DSServer, Datasnap.DSTCPServerTransport, Datasnap.DSAuth, DataSnap.DSProviderDataModuleAdapter, Datasnap.DSCommonServer,
  IPPeerServer;

type
{$METHODINFO ON}
  TServerMethods = class(TComponent)
  private
    { Private declarations }
  public
    { Public declarations }
    function EchoString(Value: string): string;
  end;
{$METHODINFO OFF}


  TfmMain = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    DSServer1: TDSServer;
    DSTCPServerTransport1: TDSTCPServerTransport;
    DSServerClass1: TDSServerClass;
    procedure DSServerClass1GetClass(DSServerClass: TDSServerClass; var PersistentClass: TPersistentClass);
  end;

var
  fmMain: TfmMain;

implementation

{$R *.dfm}

uses System.StrUtils;

function TServerMethods.EchoString(Value: string): string;
begin
  Result := Value;
end;

procedure TfmMain.DSServerClass1GetClass(DSServerClass: TDSServerClass; var PersistentClass: TPersistentClass);
begin
  PersistentClass := TServerMethods;
end;

procedure TfmMain.FormCreate(Sender: TObject);
begin
  DSServer1 := TDSServer.Create(nil);
  DSServer1.Name := 'DSServer1';
  DSServer1.AutoStart := False;

  DSTCPServerTransport1 := TDSTCPServerTransport.Create(nil);
  DSTCPServerTransport1.Server := DSServer1;

  DSServerClass1 := TDSServerClass.Create(nil);
  DSServerClass1.Server := DSServer1;
  DSServerClass1.OnGetClass := DSServerClass1GetClass;

  DSServer1.Start;
end;

procedure TfmMain.FormDestroy(Sender: TObject);
begin
  DSServer1.Stop;

  DSServerClass1.Free;
  DSTCPServerTransport1.Free;
  DSServer1.Free;
end;

end.
Aubree answered 15/5, 2015 at 22:41 Comment(1)
Thanks Zam for testing and finding the solution for the access violation. Very strange that Eurekalog doesn't catch the memory leaks but Delphi itself does!Santasantacruz
S
0

I guess it is a known bug for XE8 by now, I think it's pretty serious, at least serious enough for us NOT to use XE8 before Embarcadero has given us an answer on what's going on. We had a similar issue in XE2, as far as I remember it was on heavy callbacks.

This Eurekalog doesn't tell me much, it looks like deep inside datasnap, sorry I don't know how to make the log more readable.

EDIT: I reported this issue to Embarcadero and got this response today:

// Hi Henrik,

Part of the memory leaks are due to a bug in the System.Collections.Generics.pas, we are looking at releasing a fix this issue in very near future.

brgds

Roy. //

Thought you might wanna know :)

Sproul answered 18/8, 2015 at 16:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.