Autor Téma: TFDEventAlerter a chybná implementace v RAD Studio 10.3 Rio  (Přečteno 2436 krát)

Offline Igor

  • Nováček
  • *
  • Příspěvků: 9
  • Karma: 1
    • Verze Delphi: 10.3.3 Enterprise
Zdravím všechny, kterým po aktualizace Delphi na verzi 10.3 Rio přestaly fungovat notifikace událostí objektem TFDEventAlerter. Příčinou je chybné volání metody TFDThread.Queue namísto dřívější metody TFDThread.Synchornize, aniž by vývojáři ošetřili, že ihned po zavolání metody uvolní objekt, se kterým má metoda pracovat. Chyba se projeví v případě, že je nastavený příznak TFDEventAlerter.Options.Synchronize = True (výchozí stav) porušením přístupu do paměti.

Jelikož řešení není docela triviální a volání Queue namísto Synchronize je pro tento účel v podstatě žádoucí, uvádím trošku rozsáhlejší řešení problému. Bude to ovšem vyžadovat změnu tří systémových knihoven FireDAC a zahrnutí složky $(BDS)\source\data\firedac do SearchPath projektu.

Takže, co se vlastně děje, když nastane událost databáze?
1) Nová událost (Event) je zařazena do fronty TFDThread.FMessages.
2) V metodě TFDThread.Execute je událost načtena do proměnné oMsg a pak je volána metoda oMsg.Perform. Poté je proměnná oMsg okamžitě uvolněna voláním FDFree(oMsg)

Nyní co se děje v metodě TFDPhysEventMessage.Perform:
Chování záleží na hodnotě Synchronize komponenty TFDEventAlerter:
1) Pro False je rovnou volána metoda BasePerform.
2) Pro True je volána metoda TFDThread.Queue(nil, BasePerform) a vykonání BasePerform je zařazeno do fronty volání v hlavním vlákně.

Nyní si představme, co se stane ve Win32, pokud je Synchronize = True (tj. není aktivní počítání referencí a automatický garbage collection):
1) oMsg je odebrána z fronty zpráv
2) Volání metody BasePerform je zařazeno do volání v hlavním vlákně
3) oMsg je uvolněno z paměti
4) Během několika milisekund začne metoda BasePerform pracovat s objektem oMsg, který již byl uvolněn

Řešení tohoto problému není úplně snadné, obzvlášť pro rozsáhlejší projekty, které spoléhají na synchronizované zpracování událostí. Samozřejmě můžete vypnout přínaz Synchronize pro všechny instance komponenty TFDEventAlerter a zpracovat si události sami.

Moje řešení je určeno především pro konkrétní ovladač (v mém případě MSSQL), ale pro ostatní databáze je to podobné. Implementace kroků 1-4 je univerzální a vyřeší problém pro všechny databáze, kroky 5 a 6 umožní využít výhody řazení událostí do fronty namísto přerušení threadu pro zpracování zpráv.
1) Přidejte novou metodu do třídy TFDThreadMsgBase ve FireDAC.Stan.Util (virtual, protected):
Kód: [Vybrat]
function TFDThreadMsgBase.GetFreesItself: Boolean;
begin
  Result := False;
end;
2) Přidejte novou vlastnost do třídy TFDThreadMsgBase ve FireDAC.Stan.Util:
Kód: [Vybrat]
  public
    property FreesItself: Boolean read GetFreesItself;
[code]
3) Změňte vnitřní cyklus metody TFDThread.Execute ve FireDAC.Stan.Util:
[code]
    oMsg := TFDThreadMsgBase(oReceivedMsgs[i]);
    try
      fMsgFreesItself := oMsg.FreesItself; // pro uchování příznaku použijte lokální proměnou typu Boolean
      if not oMsg.Perform(Self) then
        Break
      else if fMsgFreesItself then // zde zabráníme duplicitnímu uvolnění objektu
        oMsg := nil;
    finally
      FDFree(oMsg);
    end;
4) Aktualizujte metodu TFDPhysEventMessage.Perform ve FireDAC.Phys:
   - nahraďte volání "TFDThread.Queue(nil, BasePerform)" následujícím kódem:
Kód: [Vybrat]
  if FreesItself then
    TFDThread.Queue(nil, BasePerform)
  else
    TFDThread.Synchronize(nil, BasePerform)
5) Přidejte novou chráněnou metodu do třídy TFDPhysMSSQLEventMessage ve FireDAC.Phys.MSSQL (overridden, protected):
Kód: [Vybrat]
function TFDPhysMSSQLEventMessage.GetFreesItself: Boolean;
begin
  Result := True;
end;
6) Aktualizujte metodu TFDPhysMSSQLEventAlerter.InternalHandle ve FireDAC.Phys.MSSQL:
Kód: [Vybrat]
begin
  oMsg := TFDPhysMSSQLEventMessage(AEventMessage);
  try
    { current implementation from the second line }
  finally
    if oMsg.FreesItself then
      FDFree(oMsg);
  end;
end;

Uvedené změny by měly obnovit funkčnost existujícího kódu a zlepšit funkčnost notifikací událostí pro MS SQL díky volání TFDThread.Queue namísto TFDThread.Synchronize.

Offline František

  • Guru
  • *****
  • Příspěvků: 904
  • Karma: 8
    • Verze Delphi: comunity 10.4.2, D2007, D11
Re:TFDEventAlerter a chybná implementace v RAD Studio 10.3 Rio
« Odpověď #1 kdy: 05-02-2024, 11:01:37 »
neviete ci toto bolo uz odstanene v pozdejsich verziach?

Offline Igor

  • Nováček
  • *
  • Příspěvků: 9
  • Karma: 1
    • Verze Delphi: 10.3.3 Enterprise
Re:TFDEventAlerter a chybná implementace v RAD Studio 10.3 Rio
« Odpověď #2 kdy: 06-02-2024, 22:12:18 »
Excellent
Rated 2 times
neviete ci toto bolo uz odstanene v pozdejsich verziach?

Popravdě nevím, ale v Delphi 11 je to rozhodně celé předělané. Klíčové jsou asi jednotky FireDAC.Stan.Util a FireDAC.Phys. V té první je třída TFDThreadMsgBase, která má virtuální funkci třídy ThreadOwned, vracející defaultně True. To zajišťuje, že se v rámci vlákna objekt vždy uvolní popsaným způsobem, který nefunguje pro příznak Synchornized. Ale pro události je ve druhé jednotce definována třída TFDPhysEventMessage, která chování uvedené funkce přepisuje a vrací False, takže uvolnění si řídí přímo metoda BasePerform této třídy. A to je chování, které by mělo být správné. Já si tedy myslím, že to opravené je.