Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Datenbanken (https://www.delphipraxis.net/15-datenbanken/)
-   -   Delphi TOracleQuery Threaded --> Wann Query-Objekt freigeben? (https://www.delphipraxis.net/151066-toraclequery-threaded-wann-query-objekt-freigeben.html)

sirius 4. Mai 2010 18:27

Datenbank: Oracle • Version: 11 • Zugriff über: DOA (4.1.1 für D7)

TOracleQuery Threaded --> Wann Query-Objekt freigeben?
 
So, nach mehrere Tagen glaube ich zu behaupten, mein Problem/Fehler eingegrenzt zu haben. Und ich vermute die Fehlerursache bei TOracleQuery bzw. dessen Anwendung.

Was macht das Programm
Das Programm habe ich soweit reduziert, wie es ging.
Jetzt ist nur noch ein Timer enthalten, der recht oft ein TOraclequery mit einer einfachen Select-Abfrage erstellt und ausführt. Das ganze im Threaded Mode (zugehörige OracleSession ist auch threadsafe eingestellt)
Im Original-Code ist der Timer eigentlich ein OracleEvent (habe ich nur zum Test durch einen Timer ersetzt)


wichtigstes Fehlersymptom
Programm ist plötzlich nicht mehr bedienbar (kann im ersten Query passieren, oder erst nach 20 Minuten (bei durchschnittlich 1 Query / 5sek). Es läuift aber noch und im TaskManager wird konstant "wird ausgeführt" angezeigt. Besagtes Oracleevent löst auch nach wie vor (asynchron) aus. Kann man einen Breakpoint reinsetzen. An allen anderen Stellen im Code hilft ein Breakpoint nicht.
Es sieht also aus, wie ein Deadlock (ich vermute mal mit MsgWaitForMultipleObjects; müsste man aber nochmal nachbauen)

wann tritt das Problem auf
Der Deadlock tritt anscheinend auf, wenn ich das Query-Objekt freigebe. Ein Problem tritt genauso auf (allerdings als Exception), wenn ich das Query-Objekt erneut nutze ohne es vorher freizugeben (ist dann als Feld der Klasse deklariert). Ich warte allerdings immer QueryThreadFinished ab und sende von da sogar noch eine Message via PostMessage und erst hier wird die Freigabe gestartet, oder das Objekt zur erneuten Nutzung freigegeben.

wichtiger Code
Delphi-Quellcode:
procedure TMainForm.LoadIPList; //wird regelmäßig durch ein Event oder Timer ausgelöst
var Query:TOracleQuery;
begin
  if FQueryCount=0 then
  begin

   
    Log('LoadIPList');
    FTempIPList:=TIPList.Create(true);

    Query:=TORacleQuery.Create(self);
    Query.Session:=ORacleSession;
    Query.Threaded:=true;
    Query.ThreadSynchronized:=true;
    Query.OnThreadFinished:=QueryThreadFinished;
    Query.OnThreadRecord:=QueryThreadRecord;
    Query.OnThreadError:=QueryThreadError;
    Query.SQLW:='Select distinct SrcIP as x from dbt_Traffic';
    Query.Execute;
    inc(FQueryCount);

  end;
end;



procedure TMainForm.QueryThreadFinished(Query: TOracleQuery);
begin
  log('QueryThreadFinished');
  postmessage(handle,CM_QueryFree,0,integer(Query));
end;


procedure TMainForm.CMQueryFree(var msg: TMessage); //message CM_QueryFree;
var del:boolean;
    c:integer;
begin
  if FQueryCount>0 then dec(FQueryCount);
 
  //hier habe ich mit einer Schleife und sleep auch schon auf ThreadisRunning und Status=qsIdle gewartet, bringt auch nichts
 
  TObject(msg.lparam).free;
  Log('QueryFree $'+inttohex(msg.LParam,8)+' '+inttostr(FQueryCount));
end;

//QueryThreadRecord kann man komplett auskommentieren, der Fehler erscheint trotzdem.
//das Query kann ich auch als Feld der Klasse deklariern. Da müsste ich es nicht freigeben, allerdings kommt dann ein fehler bei LoadIPList (wahrscheinlich, wenn es sonst einen Deadlock gegeben hätte)
Was habe ich jetzt gemacht
Ich habe eine TObjectList, in der ich mir die Query-Objekte merke und lösche in LoadIPList dann immer das vorletzte. Bisher ohne Probleme. Aber das kann ja nicht die Lösung sein. Gibt es da nicht etwas besseres? Ich habe damals leider nicht die Sourcen mitgekauft um da mal nachzusehen.

DataCool 5. Mai 2010 09:08

Re: TOracleQuery Threaded --> Wann Query-Objekt freigeben
 
Hi Sirius,

leider kenne ich TORacleQuery nicht, aber beim Lesen Deines Posts habe ich mir die Frage gestellt warum DU das so gelöst hast.

Ich hoffe mal das :
Delphi-Quellcode:
Query.Execute;
sich bei TOracleQuery auf Execute eines TThreads bezieht und nicht dem Execute einer TQuery, in dem Fall müßte es nämlich Query.Active := true; heissen weil Du ein "Select" SQL hast.
Ich gehe jetzt mal ganz stark von Execute eines TThreads aus, dabei würde mich interessieren wann das Programm an die Zeile :
Delphi-Quellcode:
inc(FQueryCount);
kommt ?! Werden vorher die Events OnThreadRecord, OnThreadFinished aufgerufen ?
Wenn ja, kannst Du nach dem Execute die Query einfach freigeben.

Dein jetziger Code mit der FQueryCount=0 "Sperre" ist nicht wirklich "Thread-Safe",
gerade wenn Du das ganze nachher aus einem DB-Event aus aufrufen möchtest,
solltest Du das ganze besser mit einer TCriticalSection schützen.

Ist Deine Funktion "Log" den Thread-Safe ? Alternativ könntest Du erstmal "OutputDebugSTring" verwenden.

Wenn das Oracle Query-Object auf einem Thread aufbaut, vielleicht gibt es sogar die Eigenschaft "FreeOnTerminate",
dann bis Du aus der Sache fein raus.

Aber ich denke die meisten brauchen um Dir zu helfen ein wenig mehr imput was "TOracleQuery" angeht,

Greetz Data

sirius 5. Mai 2010 16:26

Re: TOracleQuery Threaded --> Wann Query-Objekt freigeben
 
Zitat:

Zitat von DataCool
Hi Sirius,
leider kenne ich TORacleQuery nicht
, aber beim Lesen Deines Posts habe ich mir die Frage gestellt warum DU das so gelöst hast.
Ich hoffe mal das :
Delphi-Quellcode:
Query.Execute;
sich bei TOracleQuery auf Execute eines TThreads bezieht

Nein, das ist was bei anderen Datenbankkomponenten "Open" und "ExecSQL" ist. Hier ist nur beides in einem, es gibt kein open. Hier liefert auch Execute ein Recordset zurück.

Der Thread wird in der Komponente intern gestartet, den sehe ich von außen nicht und hab auch wegen fehlenden Sourcen keinen Einfluss darauf.

sirius 6. Mai 2010 15:40

Re: TOracleQuery Threaded --> Wann Query-Objekt freigeben
 
So, ich habe rausgefunden, dass das Programm bei der Freigabe von TORacleQuery auch den internen Thread freigebt und vorher noch mit TThread.Waitfor auf das Ende wartet. Allerdings wurde der Thread kurz zuvor mit suspend schlafen gelegt (warum weis ich nicht).

Hier mal der Code zum selber testen:
Delphi-Quellcode:
//ein Doppelklick aufs Memo würde den Test starten

type
  TForm1 = class(TForm)
    OracleSession1: TOracleSession; //Verbindung im OI eingestellt und connected auf true; threadsafe auch auf True
    Memo1: TMemo;
    procedure OracleQueryThreadError(Sender: TOracleQuery;
      ErrorCode: Integer; const ErrorMessage: String);
    procedure OracleQueryThreadExecuted(Sender: TOracleQuery);
    procedure OracleQueryThreadFinished(Sender: TOracleQuery);
    procedure OracleQueryThreadRecord(Sender: TOracleQuery);
    procedure Memo1DblClick(Sender: TObject);
  private
    { Private-Deklarationen }
    FRecorded:Integer;
    procedure WMUser(var Msg:TMessage); message WM_User;
    procedure StartQuery;
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.OracleQueryThreadError(Sender: TOracleQuery;
  ErrorCode: Integer; const ErrorMessage: String);
begin
  memo1.Lines.add('Error: '+inttostr(ErrorCode)+' '+ErrorMessage);
end;

procedure TForm1.OracleQueryThreadExecuted(Sender: TOracleQuery);
begin
  memo1.lines.add('Executed');
end;

procedure TForm1.OracleQueryThreadFinished(Sender: TOracleQuery);
begin
  memo1.lines.add('Finished - '+inttostr(FRecorded)+' Records');
  postmessage(handle,WM_User,0,integer(Sender)); //start new
end;

procedure TForm1.OracleQueryThreadRecord(Sender: TOracleQuery);
begin
  inc(FRecorded);
end;

procedure TForm1.Memo1DblClick(Sender: TObject);
begin
  StartQuery;
end;

procedure TForm1.WMUser(var Msg: TMessage);
var OracleQuery:TOracleQuery;
begin
  OracleQuery:=TOracleQuery(msg.LParam);
  //while OracleQuery.ThreadIsRunning do sleep(1); //mit der Zeile passiert es deutlich seltener, aber das ist ja auch keine Lösung
  OracleQuery.Free;
  StartQuery;
end;

procedure TForm1.StartQuery;
var OracleQuery:TOracleQuery;
begin
  FRecorded:=0;
  OracleQuery:=TOracleQuery.Create(self);
  OracleQuery.OnThreadExecuted:=self.OracleQueryThreadExecuted;
  OracleQuery.OnThreadRecord:=self.OracleQueryThreadRecord;
  OracleQuery.OnThreadFinished:=self.OracleQueryThreadFinished;
  OracleQuery.OnThreadError:=self.OracleQueryThreadError;
  OracleQuery.Threaded:=true;
  OracleQuery.ThreadSynchronized:=true;
  OracleQuery.Session:=OracleSession1;
  ORacleQuery.SQLW:='Select * from all_users';
  OracleQuery.Execute;
end;

end.


Alle Zeitangaben in WEZ +1. Es ist jetzt 13:24 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