Watch, Follow, &
Connect with Us

For forums, blogs and more please visit our
Developer Tools Community.


Welcome, Guest
Guest Settings
Help

Thread: SOAP Web service client seems to leak memory


This question is not answered. Helpful answers available: 2. Correct answers available: 1.


Permlink Replies: 5 - Last Post: Aug 30, 2017 1:59 AM Last Post By: Ertan Kucukoglu
Ertan Kucukoglu

Posts: 49
Registered: 7/4/09
SOAP Web service client seems to leak memory  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Aug 25, 2017 2:59 AM
On a Delphi 10.2, I have following WSDLImport generated lines.

 Items      = array of Item;                   { "urn:microsoft-dynamics-nav/xmlports/Items"[GblCplx] }
 
 
  // ************************************************************************ //
  // XML       : Item, global, <complexType>
  // Namespace : urn:microsoft-dynamics-nav/xmlports/Items
  // ************************************************************************ //
  Item = class(TRemotable)
  private
    FNo: string;
    FDescription: string;
    FUoM: string;
    FPrice: TXSDecimal;
    FStock: TXSDecimal;
  public
    destructor Destroy; override;
  published
    property No:          string      read FNo write FNo;
    property Description: string      read FDescription write FDescription;
    property UoM:         string      read FUoM write FUoM;
    property Price:       TXSDecimal  read FPrice write FPrice;
    property Stock:       TXSDecimal  read FStock write FStock;
  end;
 
destructor Item.Destroy;
begin
  System.SysUtils.FreeAndNil(FPrice);
  System.SysUtils.FreeAndNil(FStock);
  inherited Destroy;
end;
 
  WebIntegration_Port = interface(IInvokable)
  ['{DEEC41C7-051A-4C3B-8F29-23059440429B}']
    procedure Item(var item: Items; const sortFields: string; const ascending: Boolean; const itemNoFilter: string; const startRecord: Integer; const maxRecords: Integer; var totalRecords: Integer); stdcall;

I am calling that service using a code as follows. Please note that this is a DLL that I am making the calls to the service.

function GetItem(out JsonResponse, SoapError: WideString): UInt8; stdcall;
var
  RIO: THTTPRIO;
  WS: WebIntegration_Port;
  WsItems: Items;
  RecordsReceived: Integer;
  RecordCount: Integer;
  I: Integer;
  JItems: TItems;
begin
  try
    // *** THTTPRIO will free itself when created with no owner
    // http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Calling_Invokable_Interfaces
    RIO := THTTPRIO.Create(nil);
  except
    on E: Exception do
    begin
      SoapError := E.Message;
      Exit(SOAP_ERROR);
    end;
  end;
  RIO.HTTPWebNode.UserName := FUserName;
  RIO.HTTPWebNode.Password := FPassword;
  RIO.URL := FURL;
  if FUseProxy then
  begin
    RIO.HTTPWebNode.Proxy := FProxy;
  end;
 
  try
    WS := (RIO as WebIntegration_Port);
 
    // We get Total record count with first call to WebService
    WS.Item(WsItems, EmptyStr, False, EmptyStr, 1, 1, RecordCount);
 
    // Now, we know how many records in there. We can get them all now.
    WS.Item(WsItems, EmptyStr, False, EmptyStr, 1, RecordCount, RecordsReceived);
 
    // Do not depend on RecordsReceived variable returning from WebService. Count it yourself
    SetLength(JItems, Length(WsItems));
    for I := Low(WsItems) to High(WsItems) do
    begin
      JItems[i].FNo          := WsItems[i].No;
      JItems[i].FDescription := WsItems[i].Description;
      JItems[i].FUoM         := WsItems[i].UoM;
      JItems[i].FPrice       := BcdToDouble(WsItems[i].Price.AsBcd);
      JItems[i].FStock       := BcdToDouble(WsItems[i].Stock.AsBcd);
    end;
    WS := nil;
 
    // mORMot routines for json
    JsonResponse := WideString(RecordSaveJSON(JItems, TypeInfo(TItems)));
  except
    on E: Exception do
    begin
      SoapError := E.Message;
      Exit(SOAP_ERROR);
    end;
  end;
 
  Result := RESPONSE_OK;
end;


If I debug my DLL and set to report leaks, and call the web service once it reports "TXSDecimal", and "Item" (defined in WSDL file) among UnicodeStrings which all these counts to total records received. Additional call to the service are simply increasing the leak amount.

It is documented that THTTPRIO which is created without any owner will free itself. Link: http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Calling_Invokable_Interfaces

I was expecting that THTTPRIO and my variable WS would be freeing what is allocated.

I wonder if I am doing something wrong here.

Any help is appreciated.

Thanks.
Jeff Overcash (...

Posts: 1,529
Registered: 9/23/99
Re: SOAP Web service client seems to leak memory  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Aug 25, 2017 10:58 AM   in response to: Ertan Kucukoglu in response to: Ertan Kucukoglu
Ertan Kucukoglu wrote:
On a Delphi 10.2, I have following WSDLImport generated lines.

 Items      = array of Item;                   { "urn:microsoft-dynamics-nav/xmlports/Items"[GblCplx] }
 
 
  // ************************************************************************ //
  // XML       : Item, global, <complexType>
  // Namespace : urn:microsoft-dynamics-nav/xmlports/Items
  // ************************************************************************ //
  Item = class(TRemotable)
  private
    FNo: string;
    FDescription: string;
    FUoM: string;
    FPrice: TXSDecimal;
    FStock: TXSDecimal;
  public
    destructor Destroy; override;
  published
    property No:          string      read FNo write FNo;
    property Description: string      read FDescription write FDescription;
    property UoM:         string      read FUoM write FUoM;
    property Price:       TXSDecimal  read FPrice write FPrice;
    property Stock:       TXSDecimal  read FStock write FStock;
  end;
 
destructor Item.Destroy;
begin
  System.SysUtils.FreeAndNil(FPrice);
  System.SysUtils.FreeAndNil(FStock);
  inherited Destroy;
end;
 
  WebIntegration_Port = interface(IInvokable)
  ['{DEEC41C7-051A-4C3B-8F29-23059440429B}']
    procedure Item(var item: Items; const sortFields: string; const ascending: Boolean; const itemNoFilter: string; const startRecord: Integer; const maxRecords: Integer; var totalRecords: Integer); stdcall;

I am calling that service using a code as follows. Please note that this is a DLL that I am making the calls to the service.

function GetItem(out JsonResponse, SoapError: WideString): UInt8; stdcall;
var
  RIO: THTTPRIO;
  WS: WebIntegration_Port;
  WsItems: Items;
  RecordsReceived: Integer;
  RecordCount: Integer;
  I: Integer;
  JItems: TItems;
begin
  try
    // *** THTTPRIO will free itself when created with no owner
    // http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Calling_Invokable_Interfaces
    RIO := THTTPRIO.Create(nil);
  except
    on E: Exception do
    begin
      SoapError := E.Message;
      Exit(SOAP_ERROR);
    end;
  end;
  RIO.HTTPWebNode.UserName := FUserName;
  RIO.HTTPWebNode.Password := FPassword;
  RIO.URL := FURL;
  if FUseProxy then
  begin
    RIO.HTTPWebNode.Proxy := FProxy;
  end;
 
  try
    WS := (RIO as WebIntegration_Port);
 
    // We get Total record count with first call to WebService
    WS.Item(WsItems, EmptyStr, False, EmptyStr, 1, 1, RecordCount);
 
    // Now, we know how many records in there. We can get them all now.
    WS.Item(WsItems, EmptyStr, False, EmptyStr, 1, RecordCount, RecordsReceived);
 
    // Do not depend on RecordsReceived variable returning from WebService. Count it yourself
    SetLength(JItems, Length(WsItems));
    for I := Low(WsItems) to High(WsItems) do
    begin
      JItems[i].FNo          := WsItems[i].No;
      JItems[i].FDescription := WsItems[i].Description;
      JItems[i].FUoM         := WsItems[i].UoM;
      JItems[i].FPrice       := BcdToDouble(WsItems[i].Price.AsBcd);
      JItems[i].FStock       := BcdToDouble(WsItems[i].Stock.AsBcd);
    end;
    WS := nil;
 
    // mORMot routines for json
    JsonResponse := WideString(RecordSaveJSON(JItems, TypeInfo(TItems)));
  except
    on E: Exception do
    begin
      SoapError := E.Message;
      Exit(SOAP_ERROR);
    end;
  end;
 
  Result := RESPONSE_OK;
end;


If I debug my DLL and set to report leaks, and call the web service once it reports "TXSDecimal", and "Item" (defined in WSDL file) among UnicodeStrings which all these counts to total records received. Additional call to the service are simply increasing the leak amount.

It is documented that THTTPRIO which is created without any owner will free itself. Link: http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Calling_Invokable_Interfaces

I was expecting that THTTPRIO and my variable WS would be freeing what is allocated.

I wonder if I am doing something wrong here.

Any help is appreciated.

Thanks.

Under ARC yes. If you are assigning the result of the THTTPRio.Create(nil) to
an IAccessRio variable also yes. If you are assigning it to a concrete object
variable you have to free it.

Everywhere in the VCL source that you find THTTPRio.Create(nil) you will see it
gets freed in the end. Even the GetXxx templates that get generated when you
import WSDL will free the Rio it creates when you don't pass in a RIO.

--
Jeff Overcash (TeamB)
(Please do not email me directly unless asked. Thank You)
Learning is finding out what you already know. Doing is demonstrating that you
know it. Teaching is reminding others that they know it as well as you. We are
all leaners, doers, teachers. (R Bach)
Ertan Kucukoglu

Posts: 49
Registered: 7/4/09
Re: SOAP Web service client seems to leak memory  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Aug 26, 2017 12:39 AM   in response to: Jeff Overcash (... in response to: Jeff Overcash (...
Jeff Overcash (TeamB) wrote:
Under ARC yes. If you are assigning the result of the THTTPRio.Create(nil) to
an IAccessRio variable also yes. If you are assigning it to a concrete object
variable you have to free it.

Everywhere in the VCL source that you find THTTPRio.Create(nil) you will see it
gets freed in the end. Even the GetXxx templates that get generated when you
import WSDL will free the Rio it creates when you don't pass in a RIO.

--
Jeff Overcash (TeamB)
(Please do not email me directly unless asked. Thank You)
Learning is finding out what you already know. Doing is demonstrating that you
know it. Teaching is reminding others that they know it as well as you. We are
all leaners, doers, teachers. (R Bach)

English is not my mother tongue. I am not sure as to your reply.

Is there something I am doing wrong?

My initial code, I was manually freeing RIO. That code, I get Invalid pointer operation error. That is why I thought my code is proof of leaks.
Jeff Overcash (...

Posts: 1,529
Registered: 9/23/99
Re: SOAP Web service client seems to leak memory [Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Aug 26, 2017 11:48 AM   in response to: Ertan Kucukoglu in response to: Ertan Kucukoglu
Ertan Kucukoglu wrote:
Jeff Overcash (TeamB) wrote:
Under ARC yes. If you are assigning the result of the THTTPRio.Create(nil) to
an IAccessRio variable also yes. If you are assigning it to a concrete object
variable you have to free it.

Everywhere in the VCL source that you find THTTPRio.Create(nil) you will see it
gets freed in the end. Even the GetXxx templates that get generated when you
import WSDL will free the Rio it creates when you don't pass in a RIO.

--
Jeff Overcash (TeamB)
(Please do not email me directly unless asked. Thank You)
Learning is finding out what you already know. Doing is demonstrating that you
know it. Teaching is reminding others that they know it as well as you. We are
all leaners, doers, teachers. (R Bach)

English is not my mother tongue. I am not sure as to your reply.

Is there something I am doing wrong?

My initial code, I was manually freeing RIO. That code, I get Invalid pointer operation error. That is why I thought my code is proof of leaks.

Post your failing code and we might be able to help you. There are not many
examples in the source of them freeing it, but one is here

function GetUDDIService(const Operator: String): InquireSOAP;
var
   HTTPRIO: THTTPRIO;
begin
   Result := nil;
   HTTPRIO := THTTPRIO.Create(nil);
   try
     Result := HTTPRIO as InquireSOAP;
     HTTPRIO.URL := Operator;
   finally
     if Result = nil then
       HTTPRIO.Free;
   end;
end;


another is in the templates created in the GetXXX methods when you import a service

function GetIEMailProxy(UseWSDL: Boolean; Addr: string; HTTPRIO: THTTPRIO): 
IEMailProxy;
const
   defSvc  = 'IEMailProxyservice';
   defPrt  = 'IEMailProxyPort';
var
   RIO: THTTPRIO;
begin
   Result := nil;
   if (Addr = '') then
   begin
     if UseWSDL then
       Addr := defWSDL
     else
       Addr := defURL;
   end;
   if HTTPRIO = nil then
     RIO := THTTPRIO.Create(nil)
   else
     RIO := HTTPRIO;
   try
     Result := (RIO as IEMailProxy);
     if UseWSDL then
     begin
       RIO.WSDLLocation := Addr;
       RIO.Service := defSvc;
       RIO.Port := defPrt;
     end else
       RIO.URL := Addr;
   finally
     if (Result = nil) and (HTTPRIO = nil) then
       RIO.Free;
   end;
end;


So in the above if you don't pass in a RIO and the address being resolved does
not have a IEMailProxy interface (the resulting AS would be nil) the created RIO
is freed.

This little snippet does not fail

procedure TForm5.btn1Click(Sender: TObject);
var
   rio : THTTPRIO;
begin
   rio := THTTPRIO.Create(nil);
   ShowMessage(rio.RefCount.ToString);
   rio.Free;
end;


and the ref count as expected is 0 because no interface has a reference to it.
If you put a breakpoint in TRIO.BeforeDestruction you will see that it is
properly cleaning up on the Free, but it you remove the Free you will see the
destructor is not called with the local object falls out of scope causing a leak.

--
Jeff Overcash (TeamB)
(Please do not email me directly unless asked. Thank You)
Learning is finding out what you already know. Doing is demonstrating that you
know it. Teaching is reminding others that they know it as well as you. We are
all leaners, doers, teachers. (R Bach)

Ertan Kucukoglu

Posts: 49
Registered: 7/4/09
Re: SOAP Web service client seems to leak memory [Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Aug 26, 2017 12:51 PM   in response to: Jeff Overcash (... in response to: Jeff Overcash (...
Jeff Overcash (TeamB) wrote:
Post your failing code and we might be able to help you. There are not many
examples in the source of them freeing it, but one is here

Below are relevant lines from my WSDL. I am posting just a single method from web service not to bloat code in here. I especially set parameter for WideString definitions while importing WSDL in order to reduce the leak amount as much as I can.
  Item                 = class;                 { "urn:microsoft-dynamics-nav/xmlports/Items"[GblCplx] }
  Items      = array of Item;                   { "urn:microsoft-dynamics-nav/xmlports/Items"[GblCplx] }
 
  // ************************************************************************ //
  // XML       : Item, global, <complexType>
  // Namespace : urn:microsoft-dynamics-nav/xmlports/Items
  // ************************************************************************ //
  Item = class(TRemotable)
  private
    FNo: WideString;
    FDescription: WideString;
    FUoM: WideString;
    FPrice: TXSDecimal;
    FStock: TXSDecimal;
  public
    destructor Destroy; override;
  published
    property No:          WideString  read FNo write FNo;
    property Description: WideString  read FDescription write FDescription;
    property UoM:         WideString  read FUoM write FUoM;
    property Price:       TXSDecimal  read FPrice write FPrice;
    property Stock:       TXSDecimal  read FStock write FStock;
  end;
 
  WebIntegration_Port = interface(IInvokable)
  ['{DEEC41C7-051A-4C3B-8F29-23059440429B}']
    procedure Item(var item: Items; const sortFields: WideString; const ascending: Boolean; const itemNoFilter: WideString; const startRecord: Integer; const maxRecords: Integer; 
                   var totalRecords: Integer); stdcall;
 
function GetWebIntegration_Port(UseWSDL: Boolean; Addr: WideString; HTTPRIO: THTTPRIO): WebIntegration_Port;
const
  defWSDL = 'https://somedomain/webnup/WS/DemoNAV/Codeunit/WebIntegration?wsdl';
  defURL  = 'https://somedomain/webnup/WS/DemoNAV/Codeunit/WebIntegration?wsdl';
  defSvc  = 'WebIntegration';
  defPrt  = 'WebIntegration_Port';
var
  RIO: THTTPRIO;
begin
  Result := nil;
  if (Addr = '') then
  begin
    if UseWSDL then
      Addr := defWSDL
    else
      Addr := defURL;
  end;
  if HTTPRIO = nil then
    RIO := THTTPRIO.Create(nil)
  else
    RIO := HTTPRIO;
  try
    Result := (RIO as WebIntegration_Port);
    if UseWSDL then
    begin
      RIO.WSDLLocation := Addr;
      RIO.Service := defSvc;
      RIO.Port := defPrt;
    end else
      RIO.URL := Addr;
  finally
    if (Result = nil) and (HTTPRIO = nil) then
      RIO.Free;
  end;
end;
 
 
destructor Item.Destroy;
begin
  System.SysUtils.FreeAndNil(FPrice);
  System.SysUtils.FreeAndNil(FStock);
  inherited Destroy;
end;


My code for calling is in a DLL library. It is not a regular desktop application. Calling for Item method is as follows:
function GetItem(out JsonResponse, SoapError: WideString): UInt8; stdcall;
var
  RIO: THTTPRIO;
  WS: WebIntegration_Port;
  WsItems: Items;
  RecordsReceived: Integer;
  RecordCount: Integer;
  I: Integer;
  JItems: TItems;
begin
  try
    // *** THTTPRIO will free itself when created with no owner
    // http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Calling_Invokable_Interfaces
    RIO := THTTPRIO.Create(nil);
  except
    on E: Exception do
    begin
      SoapError := E.Message;
      Exit(SOAP_ERROR);
    end;
  end;
  RIO.HTTPWebNode.UserName := FUserName;
  RIO.HTTPWebNode.Password := FPassword;
  RIO.URL := FURL;
  if FUseProxy then
  begin
    RIO.HTTPWebNode.Proxy := FProxy;
  end;
 
  try
    WS := (RIO as WebIntegration_Port);
 
    // We get Total record count with first call to WebService
    WS.Item(WsItems, EmptyStr, False, EmptyStr, 1, 1, RecordCount);
 
    // Now, we know how many records in there. We can get them all
    WS.Item(WsItems, EmptyStr, False, EmptyStr, 1, RecordCount, RecordsReceived);
 
    // Do not depend on RecordsReceived variable returning from WebService. Count it yourself
    SetLength(JItems, Length(WsItems));
    for I := Low(WsItems) to High(WsItems) do
    begin
      JItems[i].FNo          := WsItems[i].No;
      JItems[i].FDescription := WsItems[i].Description;
      JItems[i].FUoM         := WsItems[i].UoM;
      JItems[i].FPrice       := BcdToDouble(WsItems[i].Price.AsBcd);
      JItems[i].FStock       := BcdToDouble(WsItems[i].Stock.AsBcd);
    end;
    WS := nil;
 
    // mORMot json routines used for building json string reply.
    JsonResponse := WideString(RecordSaveJSON(JItems, TypeInfo(TItems)));
  except
    on E: Exception do
    begin
      SoapError := E.Message;
      Exit(SOAP_ERROR);
    end;
  end;
 
  Result := RESPONSE_OK;
end;


If I set ReportMemoryLeaksOnShutdown to True, and run a debug, after closing the host application of DLL, I get leak report for TXSDecimal, Item and some UnicodeString. Item being exactly total received record count. TXSDecimal is double of received record count as it is used in two different places.

I also did debug a in a regular desktop application. RIO was placed on the form at design time. URL, Proxy settings are all set at design time as well. No manual creation of it in this test. Relevant lines from test application are as follows:
procedure TForm1.Button1Click(Sender: TObject);
var
  WS: WebIntegration_Port;
  WsItems: Items;
  RecordsReceived: Integer;
  RecordCount: Integer;
  I: Integer;
begin
  Screen.Cursor := crAppStart;
  try
    WS := (HTTPRIO1 as WebIntegration_Port);
    WS.Item(WsItems, EmptyStr, False, EmptyStr, 1, 10, RecordCount);
    WS.Item(WsItems, EmptyStr, False, EmptyStr, 1, RecordCount, RecordsReceived);
    kbmMemTable1.EmptyTable();
    Memo1.Lines.Clear();
    Memo1.Lines.Add('Raw data received as follows:');
    for I := Low(WsItems) to High(WsItems) do
    begin
      kbmMemTable1.Append();
      kbmMemTable1.FieldByName('No').AsString          := WsItems[i].No;
      kbmMemTable1.FieldByName('Description').AsString := WsItems[i].Description;
      kbmMemTable1.FieldByName('UoM').AsString         := WsItems[i].UoM;
      kbmMemTable1.FieldByName('Price').AsBCD          := WsItems[i].Price.AsBcd;
      kbmMemTable1.FieldByName('Stock').AsBCD          := WsItems[i].Stock.AsBcd;
      kbmMemTable1.Post();
      Memo1.Lines.Add(WsItems[i].No + ', ' + WsItems[i].Description + ', ' + WsItems[i].UoM + ', ' + WsItems[i].Price.DecimalString + ', ' + WsItems[i].Stock.DecimalString)
    end;
    kbmMemTable1.First();
    CRDBGrid1.AdjustColumns();
  finally
    Screen.Cursor := crDefault;
  end;
end;


Before starting debugging, I set a break point in Soap.SOAPHTTPClient.pas around line number 85:
destructor THTTPRIO.Destroy;
begin
  if Assigned(FConverter) then
    FConverter := nil;
  if Assigned(FWebNode) then
    FWebNode := nil;
  if Assigned(FWSDLView) then
    FWSDLView.Free;
 
  { All components we own are automatically cleaned up }
  inherited;
end;


When I close application, execution stopped at my break point. What I understand is that above lines are executed. Even that happened, I received memory leak report after that.

Same way of debugging is not working for DLL as set break point displayed as incorrect in editor and code execution is not stopping there.

Thank you for your helps.
Ertan Kucukoglu

Posts: 49
Registered: 7/4/09
Re: SOAP Web service client seems to leak memory [Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Aug 30, 2017 1:59 AM   in response to: Ertan Kucukoglu in response to: Ertan Kucukoglu
I wonder if I am seemingly doing everything correct in above example.

I appreciate any help.

Thanks.
Legend
Helpful Answer (5 pts)
Correct Answer (10 pts)

Server Response from: ETNAJIVE02