Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Sonstige Fragen zu Delphi (https://www.delphipraxis.net/19-sonstige-fragen-zu-delphi/)
-   -   ADO Memoryleak / Speicherleck ?! (https://www.delphipraxis.net/194490-ado-memoryleak-speicherleck.html)

markusef 29. Nov 2017 11:19

ADO Memoryleak / Speicherleck ?!
 
Hey,

ich habe eine Client/Server-Serviceanwendung programmiert und soweit funktioniert auch alles. Bis auf einen einzigen Punkt.
Und zwar sobald eine Datenbank Verbindung innerhalb eines Threads ins Spiel kommt steigt mein Arbeitsspeicherverbrauch der .exe mit jeder Anfrage.
Ich habe es schon soweit dezimiert das lediglich eine ADOConnection instanziiert wird, ein ConnectionString übergeben wird, danach die Verbindung geöffnet und anschließend (ohne ein SQL Command auszuführen !) wieder geschlossen & ge-FreeAndNil't wird. Dennoch steigt der Arbeitsspeicherverbrauch mit jedem Durchlauf dieses Vorganges.

Ich weis so langsam nicht mehr wie ich das Unterbinden kann.
Leider bin ich auch auf die ADO-Geschichte angewiesen da wir eine ältere SAP-MaxDb Datenbank benutzen (müssen).
Quellcode:

Connect Funktion:
Code:
function TdatabaseInfo.Connect : boolean;
begin
  try
    if fConnectionString <> '' then
    begin
      ADOConnection.ConnectionString := fConnectionString; // Übergibt Connection String
    end
    else
    begin
      Result := false;
      nxLogging.Logger.error('TdatabaseInfo.Connect', 'connection string empty');
      Exit;
    end;

    ADOConnection.Open;

    if ADOConnection.Connected then
    begin
      Result := true;
    end;
  except
    on e : exception do
    begin
      Result := false;
      nxLogging.Logger.error('TdatabaseInfo.Connect', 'error during ADO connect: ' + E.Message);
    end;
  end;
end;
Disconnect Funktion:
Code:
function TdatabaseInfo.Disconnect : boolean;
begin
  try
    ADOConnection.Close;
    ADOConnection.Connected := false;
  finally
    Result := true;
  end;
end;
Ist jetzt nicht schön und auch nur fix "zusammengehackt", aber läuft, bis auf das Memoryleak. :idea:

Jemand ne Ahnung woran das liegt oder wie sich das Unterbinden lässt ?

Beste Grüße

haentschman 29. Nov 2017 11:22

AW: ADO Memoryleak / Speicherleck ?!
 
Moin...:P
Zeige mal den kompletten Thread incl. der Instanzierung der ADO.

markusef 29. Nov 2017 12:57

AW: ADO Memoryleak / Speicherleck ?!
 
Hey, Sorry ich hab mich bei der Erklärung nicht ordentlich ausgedrückt. :-D
Also ich habe eine Klasse "tdatabaseinfo" abgeleitet von TDataModule inkl. einer Form wo die ADOConnection als Komponente drauf liegt.
Diese Klasse erzeuge ich im execute-Teil des Threads und gebe sie auch mit FreeAndNil wieder frei. Das passiert innerhalb des Threads
in einer "while not terminated"-Schleife, mit sleep dazwischen.

Quellcode:

Thread create:
Code:
constructor TWorkerThread.create(aCreateSuspended: Boolean; aConnStr : String; aHost : String; aPort : Word; aSleepTime : Integer);
begin
  inherited create(aCreateSuspended);
  FreeOnTerminate := false;
  fConnStr       := aConnStr;
  fSleepTime     := aSleepTime;
  fHost          := aHost;
  fPort          := aPort;
  fConnected     := false;

  fTcpClient               := TIdTCPClient.Create(nil);
  fTcpClient.Host          := aHost;
  fTcpClient.Port          := aPort;
  fTcpClient.ConnectTimeout := 3000;
  fTcpClient.ReadTimeout   := fSleepTime;

  MsgCenter := TMsgCenter.Create(fConnStr);
  ErrCnt   := 0;

  nxLogging.Logger.info('TWorkerThread.create', 'created..');
end;
Thread execute:
Code:
procedure TWorkerThread.execute;
var
  ServerMsg  : String;
begin
  inherited;
  try
    fTcpClient.Connect;
    fConnected := true;

    nxLogging.Logger.trace('TWorkerThread.execute', 'tcp connected');
  except
    on e: Exception do
    begin
      nxLogging.Logger.error('TWorkerThread.execute', e.Message);
    end;
  end;

  while not self.Terminated do
  begin
    try
      if fTcpClient.Connected then
      begin
        databaseInfo := TdatabaseInfo.Create(nil);
        databaseInfo.ConnectionString := fConnectionString;

        if not databaseInfo.isConnected then
        begin
          databaseInfo.Connect;
        end;

        //do stuff, auskommentiert..

        databaseInfo.Disconnect;
        FreeAndNil(databaseInfo);
      end
      else
      begin
        nxLogging.Logger.error('TWorkerThread.execute', 'tcp connection failed');
        Reconnect;
      end;
    except
      on E: Exception do
      begin
        nxLogging.Logger.error('TWorkerThread.execute', 'error: ' + e.Message);
        Reconnect;
      end;
    end;
    sleep(fSleepTime);
  end;
  fTcpClient.Disconnect;
  nxLogging.Logger.trace('TWorkerThread.execute', 'tcp disconnected');
end;
databaseInfo.Connect:
Code:
function TdatabaseInfo.Connect : boolean;
begin
  try
    if fConnectionString <> '' then
    begin
      ADOConnection.ConnectionString := fConnectionString; // Übergibt Connection String
    end
    else
    begin
      Result := false;
      nxLogging.Logger.error('TdatabaseInfo.Connect', 'connection string empty');
      Exit;
    end;

    ADOConnection.Open;

    if ADOConnection.Connected then
    begin
      Result := true;
    end;
  except
    on e : exception do
    begin
      Result := false;
      nxLogging.Logger.error('TdatabaseInfo.Connect', 'error during ADO connect: ' + E.Message);
    end;
  end;
end;
databaseinfo.Disconnect:
Code:
function TdatabaseInfo.Disconnect : boolean;
begin
  try
    ADOConnection.Close;
    ADOConnection.Connected := false;
  finally
    Result := true;
  end;
end;

Jedesmal wenn der Thread durch die while-Schleife rennt steigt der Arbeitsspeicher um ein paar hundert Kilobyte. :?:

haentschman 29. Nov 2017 13:08

AW: ADO Memoryleak / Speicherleck ?!
 
Moin...:P
Delphi-Quellcode:

databaseInfo := TdatabaseInfo.Create(nil);
* Form oder Datenmodul?
* ADOConnection auf der Form?
* Was macht diese Form/Datamodule?
Delphi-Quellcode:

//do stuff, auskommentiert..
* Irgendwelche Zugriffe auf die VCL?
* Syncronize

Nachtrag:
Zitat:

inkl. einer Form wo die ADOConnection als Komponente drauf liegt
...Im Execute eines Threads ist das keine gute Idee.:?

PS: Ich muß dann mal Pause machen. Vieleicht kann dir das jemand in der Zwischenzeit erklären...8-)

p80286 29. Nov 2017 13:25

AW: ADO Memoryleak / Speicherleck ?!
 
Wo ist denn das
Delphi-Quellcode:
ADOConnection.Create
und das
Delphi-Quellcode:
ADOConnection.Free
?

Gruß
K-H

Vergesst es , ich hab da wohl einiges übersehen !

haentschman 29. Nov 2017 15:38

AW: ADO Memoryleak / Speicherleck ?!
 
Zitat:

inkl. einer Form wo die ADOConnection als Komponente drauf liegt
:zwinker:

Der schöne Günther 29. Nov 2017 15:51

AW: ADO Memoryleak / Speicherleck ?!
 
Statt zu raten wäre doch eigentlich der erste Schritt zu schauen was sich hier stapelt.

http://docwiki.embarcadero.com/Libra...eaksOnShutdown
http://www.delphipraxis.net/183846-f...nshutdown.html

Dann sollte man den Code doch ein einziges mal ausführen und schauen was dann an Speicherlecks herauskommt. Wenn man dann noch die Deluxe-Version des Speichermanagers einsetzt [1] kann man sich sogar den gesamten Stack anzeigen lassen was wann erstellt und nicht wieder freigegeben wurde.

[1] https://github.com/pleriche/FastMM4

OlafSt 29. Nov 2017 22:37

AW: ADO Memoryleak / Speicherleck ?!
 
Nur zur Erinnerung: Die ADOConnection ist NICHT threadsafe. Jeder Thread benötigt eine eigene Connection. Ist IIRC auch bei FireDAC so.

markusef 30. Nov 2017 10:13

AW: ADO Memoryleak / Speicherleck ?!
 
Es gibt nur diesen einen Thread, davon gibt es keine weiteren Instanzen, deshalb musste ich mir an dieser Stelle über Multithreading keine Gedanken machen.
Tdatabaseinfo ist eine TDataModule abgeleitete Klasse, die VCL benutze ich nicht da es ein reiner Windows Service ist :)
Momentan kann ich mir nur noch Erklären das der Fehler in der ADOConnection selbst liegt, da ja sonst keinerlei Aktionen mit dieser Connection getätigt werden außer die Verbindung zu öffnen und wieder zu schließen.

RE: Habe das ganze jetzt nochmal in ein eigenes Projekt ausgelagt um in ruhe daran rumzuexperimentieren.

Aufbau: habe eine VLC Form mit start Button, der Button startet einen Thread welcher wiederrum in der Execute Methode eine TADOConnection created, connected, disconnected, freeAndNil`t.
Ergebnis = gleiches Problem :-/

Reportmemoryleaksonshutdown bringt keinerlei Meldung. Habe es im FormShow auf true gesetzt. ?!

Code:
Code:
unit Unit8;

interface

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

type

  TWorkerThread = class(TThread)
    private
      txt           : String;
      ADOConnection : TADOConnection;

      procedure Sync;

    public
      constructor create;
      destructor destroy; override;
      procedure execute; override;
  end;

  //--------------------------------------------

  TForm8 = class(TForm)
    btnStart     : TButton;
    mmo          : TMemo;

    procedure btnStartClick(Sender: TObject);
    procedure FormShow(Sender: TObject);

  private
    fStarted     : Boolean;
    fThread      : TWorkerThread;

  public
    { Public-Deklarationen }

  end;

  //--------------------------------------------

var
  Form8: TForm8;

implementation

{$R *.dfm}


procedure TForm8.FormShow(Sender: TObject);
begin
  fStarted := false;
  mmo.Clear;
  ReportMemoryLeaksOnShutdown := true;
end;

procedure TForm8.btnStartClick(Sender: TObject);
begin
  if fStarted then
  begin
    fThread.Terminate;
    fThread.WaitFor;
    FreeAndNil(fThread);
    fStarted := false;
    btnStart.Caption := 'Start';
  end
  else
  begin
    fThread := TWorkerThread.Create();
    fThread.start;
    fStarted := true;
    btnStart.Caption := 'Stop';
  end;
end;

// TWorkerThread -------------------------

constructor TWorkerThread.create;
begin
  inherited create(true);
  FreeOnTerminate := false;
  txt := 'created';
  Synchronize(Sync);
end;

destructor TWorkerThread.destroy;
begin
  txt := 'destroyed';
  Synchronize(Sync);
  inherited;
end;

procedure TWorkerThread.execute;
begin
  inherited;
  try
    CoInitialize(nil);

    try
      ADOConnection := TADOConnection.Create(nil);
      ADOConnection.ConnectionString := 'Provider=MSDASQL.1;Password=PASSWORD;Persist Security Info=True;User ID=ADMIN;Data Source=DB';
      ADOConnection.Open;
    finally
      sleep(1000);

      ADOConnection.Close;
      FreeAndNil(ADOConnection);
      CoUninitialize;
    end;
  except
    on e: Exception do
    begin
      txt := e.Message;
      Synchronize(Sync);
    end;
  end;
end;

procedure TWorkerThread.Sync;
begin
  Form8.mmo.Lines.Add(DateTimeToStr(now) + ' | ' + txt)
end;

end.

markusef 4. Dez 2017 09:26

AW: ADO Memoryleak / Speicherleck ?!
 
keiner eine Idee ?!

sakura 4. Dez 2017 10:06

AW: ADO Memoryleak / Speicherleck ?!
 
Zitat:

Zitat von markusef (Beitrag 1387863)
keiner eine Idee ?!

Der gleiche Code - anderer ConnectionString - läuft bei mir ohne jegliche Probleme bzw. Speicherlecks.

...:cat:...

HolgerX 4. Dez 2017 10:20

AW: ADO Memoryleak / Speicherleck ?!
 
Hmm..

Könnte eventuell mit dem ConnectionPool der ADO-Connection zusammenhängen.

Dieser Pool wird erst freigegeben, wenn ein Timeout ohne Anforderung einer neuen Connection (mit gleichem ConString) abgelaufen ist.

Da je nach Konfiguration der Connection bzw. des Servers hier andere Einstellungen verwendet werden, könnte sich daraus eine unterschiedliche Speichernutzung ergeben.


Nur so eine Idee ;)

markusef 4. Dez 2017 10:24

AW: ADO Memoryleak / Speicherleck ?!
 
Okay, ein weiterer Test beinhaltete folgendes:

Oberer Aufbau, wobei nach 20.000 SELECT-Abfragen automatisch der Thread gestoppt wurde. Nach jedem SELECT wurde die Query wie oben im Quellcode geschlossen und ein FreeAndNil angewandt. Das ganze habe ich diesmal aber auf einer lokalen MySQL Datenbank ausgeführt und einmal auf der SAP MaxDb. Beides über ODBC Treiber.

Ergebnis:

Der Speicherzuwachs tritt nur (!!!) bei der SAP MaxDb auf. Bei der Nutzung der MySQL Datenbank gab es keinerlei Speicherzuwachs.
Demnach muss der Fehler im ODBC Treiber der SAP MaxDb stecken.

sakura 4. Dez 2017 10:30

AW: ADO Memoryleak / Speicherleck ?!
 
Zitat:

Zitat von markusef (Beitrag 1387872)
Demnach muss der Fehler im ODBC Treiber der SAP MaxDb stecken.

Muss ist relativ, aber wahrscheinlich scheint es hier zu sein. Andere Treiberversion testen.

...:cat:...


Alle Zeitangaben in WEZ +1. Es ist jetzt 11:35 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz