Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

FireDAC a wznawianie połączenia z bazą

134 views
Skip to first unread message

buni...@gmail.com

unread,
Feb 19, 2014, 8:34:59 AM2/19/14
to
Witam,

Mam pulę wątków. Dla każdego wątku tworze osobne połączenie z bazą danych (Firebird). Każdy wątek ma swoja transakcję do odczytu ReadOnly oraz tworzone dynamicznie transakcje do zapisu/kasowania zwalniane po danej akcji.

Bazę tworze w następujący sposób

FDataBase := TADConnection.Create(nil);
FDataBase.LoginPrompt := False;
FDatabase.ConnectionDefName := C_CONNECTION_NAME;
FDatabase.OnRecover := prRecoverConnection;
FDatabase.FormatOptions := ADatabase.FormatOptions;
FDatabase.ResourceOptions.AutoReconnect := True;
FDatabase.TxOptions.AutoCommit := True;
FDatabase.FetchOptions.RowsetSize := 200;
FDatabase.AfterConnect := prAfterConnect;

procedura prRecoverConnection wygląda następująco:

prRecoverConnection(ASender: TObject;
const AInitiator: IADStanObject; AException: Exception;
var AAction: TADPhysConnectionRecoverAction);
begin
try
if (FConnectionIsRestored) and (FTryConnectCount < 2) then begin
AAction := faRetry;
inc(FTryConnectCount);
end
else
begin
AAction := faFail;
FTryConnectCount := 0;
//faCloseAbort;
SQLLog.Error('[TKIMThreadWithTask] [$ThreadId$' + Self.CurrentId + '] Lost connection, Action: ' +
GetEnumName(TypeInfo(TADPhysConnectionRecoverAction), Integer(AAction)) +
', error: ' + AException.Message);
end;
except
raise AException;
end;
end;

przy próbie wznowienia wątku ustawiam FConnectionIsRestored i próbuje zrobić

FConnectionIsRestored := True;
try
Self.FDatabase.Ping;
finally
FConnectionIsRestored := False;
end;

Zakładam że jak ping przejdzie to jest połączenie z bazą i wątek może działać.
w każdym innym przypadku powinien polecić błąd. Czyli wątek nie obsłuży już tego "zlecenia".

Ale niestety wznawianie połączenia nie działa jak należy. Ping po komunikatach (debuger) przechodzi dalej ale niestety pierwsze otwarcie query zawiesza się.
Przy debugowaniu doszedłem do function TDataSet.GetNextRecord: Boolean; ale to chyba błędna droga.
Czy należy poustawiać coś jeszcze? Czy ogólnie wznawianie połączenia działa na FireDACu?

Może ktoś podpowie jak to inaczej można zrealizować.

wloochacz

unread,
Feb 19, 2014, 12:07:30 PM2/19/14
to
W dniu 2014-02-19 14:34, buni...@gmail.com pisze:
> Witam,
>
> Mam pulę wątków. Dla każdego wątku tworze osobne połączenie z bazą danych (Firebird). Każdy wątek ma swoja transakcję do odczytu ReadOnly oraz tworzone dynamicznie transakcje do zapisu/kasowania zwalniane po danej akcji.
A po co tę transakcję (obiekt klasy TFDTransaction) do zapisu/kasowania
tworzysz dynamicznie i niszczysz po każdej akcji?

> Bazę tworze w następujący sposób
Nie bazę, tylko connection...

> FDataBase := TADConnection.Create(nil);
> FDataBase.LoginPrompt := False;
> FDatabase.ConnectionDefName := C_CONNECTION_NAME;
> FDatabase.OnRecover := prRecoverConnection;
> FDatabase.FormatOptions := ADatabase.FormatOptions;
Uważaj na takie zapisy, akurat w FD zadziała to poprawnie, bo setter
jest napisany tak:
FOptionsIntf.FormatOptions.Assign(AValue);

Ale gdzie indziej możesz zrobić sobie kuku...

Poza tym, takie rzeczy jak FormatOptions powinieneś ustawić sobie w
ADManagerze.

Czytałeś to??
http://docs.embarcadero.com/products/rad_studio/firedac/Multi_Threading.html
Przy próbie wznowienia wątku czy połączenia?

>
> FConnectionIsRestored := True;
> try
> Self.FDatabase.Ping;
> finally
> FConnectionIsRestored := False;
> end;
A czemu nie tak:
FConnectionIsRestored := Self.FDatabase.Ping;
??

> Zakładam że jak ping przejdzie to jest połączenie z bazą i wątek może działać.
Nie.
Ping zawsze przejdzie i nie wywoła wyjątku, Ping jest funkcją która
zwraca Boolean.

Naprawdę, dokumentacja nie gryzie:
"The Ping method checks whether a connection to a DBMS server is alive.
When the connection is dead and ResourceOptions.AutoReconnect is True,
then an automatic reconnection is attempted. When a connection is
inactive, the Ping method will try to open a connection."


> w każdym innym przypadku powinien polecić błąd. Czyli wątek nie obsłuży już tego "zlecenia".
Jak wywołasz tam Raise to błąd może i poleci...

> Ale niestety wznawianie połączenia nie działa jak należy.
No popatrz - a mi działa i to jak należy.

> Ping po komunikatach (debuger) przechodzi dalej ale niestety pierwsze otwarcie query zawiesza się.
Bo nie sprawdzasz, czy ping zwrócił True czy False.
Zwrócił Ci False i nie ma połączenia z bazą danych, więc to nie zadziała.

> Przy debugowaniu doszedłem do function TDataSet.GetNextRecord: Boolean; ale to chyba błędna droga.
> Czy należy poustawiać coś jeszcze?
A masz na pewno ustawione ConentionPooling poprawnie?


> Czy ogólnie wznawianie połączenia działa na FireDACu?
Działa porpawnie; sprawdzone na MSSQL, Oracle i Firebrid.

> Może ktoś podpowie jak to inaczej można zrealizować.
Źle używasz pinga.
Nie jestem pewien czy dobrze ustawiłeś ConnectionPooling.

--
wloochacz

buni...@gmail.com

unread,
Feb 19, 2014, 2:16:14 PM2/19/14
to
W dniu środa, 19 lutego 2014 18:07:30 UTC+1 użytkownik wloochacz napisał:
> W dniu 2014-02-19 14:34, buni...@gmail.com pisze:
>
> > Witam,
>
> >
>
> > Mam pulę wątków. Dla każdego wątku tworze osobne połączenie z bazą danych (Firebird). Każdy wątek ma swoja transakcję do odczytu ReadOnly oraz tworzone dynamicznie transakcje do zapisu/kasowania zwalniane po danej akcji.
>
> A po co tę transakcję (obiekt klasy TFDTransaction) do zapisu/kasowania
>
> tworzysz dynamicznie i niszczysz po każdej akcji?

Można by utworzyć jeden obiekt TADTransaction do zapisu i startować przed update/delete/inster a potem commit. Wydaje mi się że samo utworzenie obiektu transakcji nie trwa na tyle długo aby to był problem, ale pewnie się mylę.
>
>
> > Bazę tworze w następujący sposób
>
> Nie bazę, tylko connection...
Tak, tworzę obiekt podłączenia do bazy
>
>
>
> > FDataBase := TADConnection.Create(nil);
>
> > FDataBase.LoginPrompt := False;
>
> > FDatabase.ConnectionDefName := C_CONNECTION_NAME;
>
> > FDatabase.OnRecover := prRecoverConnection;
>
> > FDatabase.FormatOptions := ADatabase.FormatOptions;
>
> Uważaj na takie zapisy, akurat w FD zadziała to poprawnie, bo setter
>
> jest napisany tak:
>
> FOptionsIntf.FormatOptions.Assign(AValue);
>
>
>
> Ale gdzie indziej możesz zrobić sobie kuku...
tego jestem świadomy, niemniej dziękuje za wskazówkę.
>
>
>
> Poza tym, takie rzeczy jak FormatOptions powinieneś ustawić sobie w
>
> ADManagerze.
>
>
>
> Czytałeś to??
>
> http://docs.embarcadero.com/products/rad_studio/firedac/Multi_Threading.html
>
tak ale najwyraźniej nie doczytałem że mogę tam też inne parametry ustawiać. Tak czy owak "pobieram" je z głównego połączenia. Doczytam i poprawie.
przy próbie wznowienia wątku. Dokładnie chodzi tutaj o serwer HTTP (Indy) i wątki/zadnia TIdThreadWithTask (nadpisane). Czyli gdy dany wątek jest pobierany z TIdSchedulerOfThreadPool do obsługi przychodzącego requesta.
> >
>
> > FConnectionIsRestored := True;
>
> > try
>
> > Self.FDatabase.Ping;
>
> > finally
>
> > FConnectionIsRestored := False;
>
> > end;
>
> A czemu nie tak:
>
> FConnectionIsRestored := Self.FDatabase.Ping;
>
> ??
zmienna FConnectionIsRestored ustawiana jest tylko pota aby jak połączenie się wywali w każdym innym przypadku niż "start/restart" wątku to odrazu ma być zwrócone faFail z eventu FDatabase.OnRecover. W zależności od tego co tam ustawimy tak się dalej potoczą losy próby połączenia. faFail zwraca wyjątek że nie udało się nawiązac połączenia z bazą danych. Dla faRetry FD próbuje wznowić połączenie. Mam tam licznik aby próbował powiedzmy 3 razy potem wylotka
>
>
>
> > Zakładam że jak ping przejdzie to jest połączenie z bazą i wątek może działać.
>
> Nie.
>
> Ping zawsze przejdzie i nie wywoła wyjątku, Ping jest funkcją która
>
> zwraca Boolean.

Tutaj się nie zgodzę. Tak samo jak Open dla Query tak samo PING wywołuje event OnRecover i to my decydujemy co tam się stanie.
>
>
>
> Naprawdę, dokumentacja nie gryzie:
>
> "The Ping method checks whether a connection to a DBMS server is alive.
>
> When the connection is dead and ResourceOptions.AutoReconnect is True,
>
> then an automatic reconnection is attempted. When a connection is
>
> inactive, the Ping method will try to open a connection."
>
>
>
>
>
> > w każdym innym przypadku powinien polecić błąd. Czyli wątek nie obsłuży już tego "zlecenia".
>
> Jak wywołasz tam Raise to błąd może i poleci...
pisząc wyjątek miałem na myśli wywołanie faFail (jak to wyżej pisałem )
>
>
>
> > Ale niestety wznawianie połączenia nie działa jak należy.
>
> No popatrz - a mi działa i to jak należy.
>
>
>
> > Ping po komunikatach (debuger) przechodzi dalej ale niestety pierwsze otwarcie query zawiesza się.
>
> Bo nie sprawdzasz, czy ping zwrócił True czy False.
>
> Zwrócił Ci False i nie ma połączenia z bazą danych, więc to nie zadziała.

Przy tak napisanym kodzie jak powyżej ping zwróci True gdy udało się podniesć połączenie lub poleci exception (dla faFail)
>
>
>
> > Przy debugowaniu doszedłem do function TDataSet.GetNextRecord: Boolean; ale to chyba błędna droga.
>
> > Czy należy poustawiać coś jeszcze?
>
> A masz na pewno ustawione ConentionPooling poprawnie?

oParam := TStringList.Create;
try
oParam.Add('Database=D:\Database\TEST.FDB');
oParam.Add('User_Name=');
oParam.Add('Password=');
oParam.Add('Server=10.0.15.22');
oParam.Add('CharacterSet=WIN1250');
oParam.Add('DriverID=IB');
oParam.Add('SQLDialect=1');
oParam.Add('Pooled=True');
ADManager.AddConnectionDef('BUNIAS', 'IB', oParam);
ADManager.ResourceOptions.AutoReconnect := False;
ADManager.Active := True;
finally
FreeAndNil(oParam);
end;

ADConnection1.ConnectionDefName := 'BUNIAS';
ADConnection1.Connected := True;
>
>
>
>
>
> > Czy ogólnie wznawianie połączenia działa na FireDACu?
>
> Działa porpawnie; sprawdzone na MSSQL, Oracle i Firebrid.
>
>
>
> > Może ktoś podpowie jak to inaczej można zrealizować.
>
> Źle używasz pinga.
>
> Nie jestem pewien czy dobrze ustawiłeś ConnectionPooling.


Zrobiłem taki prosty przykład

procedure TSQLThread.Execute;
var
oQry: TADQuery;
oWait: TWaitResult;
begin
//inherited;
while True do begin
if (Terminated) then
Exit;
oWait := FEvent.WaitFor(1000);
if (oWait = wrSignaled) then begin
FEvent.ResetEvent;
FOnWork := true;
try
try
if (not FDatabase.Ping) then
raise Exception.Create('PING !');
oQry := TADQuery.Create(nil);
oQry.Connection := FDatabase;
oQry.Transaction := FTransaction;
oQry.ResourceOptions.SilentMode := True;
oQry.SQL.Text := 'SELECT current_timestamp from RDB$DATABASE';
oQry.Open();
if (not oQry.Eof) then
FResult := oQry.Fields[0].AsString;
except
on e: Exception do begin
FResult := e.Message;
end;
end;
finally
FOnWork := False;
end;
end;
end;
end;

procedure TSQLThread.prRecoverConnection(ASender: TObject;
const AInitiator: IADStanObject; AException: Exception;
var AAction: TADPhysConnectionRecoverAction);
begin
if (FCount > 1) then begin
AAction := faFail;
Exit;
end;
AAction := faRetry;
Inc(FCount);
end;

Pomińmy proszę kwestę zwracania FResult czy Eventu (TSimpleEvent).
Podłączenie do bazy ustawiam przy tworzeniu wątku. Scenariusz taki.
Mamy włączonego Firebirda. odpalamy event wątku i otrzymujemy aktualny czas.
Zatrzymujemy FB. Odpalamy evnet. Po dwóch próbach w fResult mamy coś takiego
'[FireDAC][Phys][IB]Unable to complete network request to host "10.0.15.22".
Failed to establish a connection.
Nie można nawiązać połączenia, ponieważ komputer docelowy aktywnie go odmawia.
i fajnie bo połącznia nie ma. ale jak widzisz PING przeszedł.
włączamy FB. Ping przechodzi ładnie i dochodzi do oQry.Open(); i wisi.

Wydaje mi się że może to mieć coś wspólnego z transakcja ale w sumie query powinno samo wystartować transakcje.

Pozdrawiam,
Przemek

wloochacz

unread,
Feb 19, 2014, 3:21:20 PM2/19/14
to
W dniu 2014-02-19 20:16, buni...@gmail.com pisze:
> W dniu środa, 19 lutego 2014 18:07:30 UTC+1 użytkownik wloochacz napisał:
>> W dniu 2014-02-19 14:34, buni...@gmail.com pisze:
/ciach/

> Wydaje mi się że może to mieć coś wspólnego z transakcja ale w sumie query powinno samo wystartować transakcje.
Niezupełnie query.... ale zostawmy to.
Zrób program testowy z bazą danych o opisz scenariusz, a potem wystaw to
gdzieś.
Sprawdzę co i jak, bo wcale nie jest powiedziane, że nie znalazłeś
babola w FD ;-)

Tak czy siak, mnie odzyskiwanie połączenia działa bez pudła - również
dla FB.

--
wloochacz

wloochacz

unread,
Feb 19, 2014, 3:45:42 PM2/19/14
to
W dniu 2014-02-19 20:16, buni...@gmail.com pisze:
> W dniu środa, 19 lutego 2014 18:07:30 UTC+1 użytkownik wloochacz napisał:
>> W dniu 2014-02-19 14:34, buni...@gmail.com pisze:
/ciach/
Jeszcze raz ;-)

> Pomińmy proszę kwestę zwracania FResult czy Eventu (TSimpleEvent).
> Podłączenie do bazy ustawiam przy tworzeniu wątku. Scenariusz taki.
Ale to connection jest tworzone gdzie - w wątku czy poza nim?
Poza tym, nie ma potrzeby używania obiektu TFDConnection, wystarczy
przypisać nazwę definicji Twojego prywatnego connection, które wcześniej
zarejestrowałeś w ADManagerze.

Po drugie - ja w w ogóle nie używam TADTransaction, to jest wymysł
Firebrida/Interbase (i nie znam żadnej innej bazy, która to obsługuje).
Transakcjami zarządza się z poziomu Connection - po prostu. Zwłaszcza,
kiedy robisz bazodanowe fiku-miku w wątkach.

czyli tak:
adQuery.Connectionname := 'BUNIAS';
adQuery.PointedConnection.StartTransaction;

itd.

Ale wszystko robisz w wątku, a więc to query ma być prywatnym obiektem w
wątku - lub, tak jak napisałeś, zmienną lokalną w TThread.Execute.

To tak ogólnie, bo są pewne wyjątki od tego - ale zostawmy to; tak jak
napisałem wyżej jest bezpieczniej.

> Mamy włączonego Firebirda. odpalamy event wątku i otrzymujemy aktualny czas.
> Zatrzymujemy FB. Odpalamy evnet. Po dwóch próbach w fResult mamy coś takiego
> '[FireDAC][Phys][IB]Unable to complete network request to host "10.0.15.22".
> Failed to establish a connection.
> Nie można nawiązać połączenia, ponieważ komputer docelowy aktywnie go odmawia.
> i fajnie bo połącznia nie ma. ale jak widzisz PING przeszedł.
> włączamy FB. Ping przechodzi ładnie i dochodzi do oQry.Open(); i wisi.
>
> Wydaje mi się że może to mieć coś wspólnego z transakcja ale w sumie query powinno samo wystartować transakcje.
Spróbuj to zrobić tak jak napisałem wyżej, bez używania TADTransaction.

A projekt wyślij, jak dalej nie będzie działać ;-)


--
wloochacz

Piotr Rezmer

unread,
Feb 20, 2014, 3:09:37 AM2/20/14
to
buni...@gmail.com pisze:
...

> try
> try
> ...
> oQry.Open();
> if (not oQry.Eof) then
> FResult := oQry.Fields[0].AsString;
> except
> on e: Exception do begin
> FResult := e.Message;
> end;
> end;
> finally
> FOnWork := False;
> end;

Rozumiem że to tylko przykład? Bo obsługa wyjątku poprzez zapisanie
nazwy wyjątku w polu FResult to delikatnie ujmując, niezbyt szczęśliwy
pomysł. Wloochacz zabrałby za to premię :) Tracisz całe dobrodziejstwo
wyjątków.

--
pozdrawiam
Piotr
XLR250&bmw_f650_dakar

buni...@gmail.com

unread,
Feb 20, 2014, 3:35:57 AM2/20/14
to
Tak, tak to tylko żeby przekazać że wylatuje z wyjątkiem nie Abort tylko nie nawiązano połączenia albo wywalony wcześniej "PING". Tak jak wyżej pisałem to jest prosty przykład mający demonstrować "nie nawiązywanie połączenia" a nie obsługę wyjątków czy wątki i synchronize.


buni...@gmail.com

unread,
Feb 20, 2014, 3:43:48 AM2/20/14
to
W dniu środa, 19 lutego 2014 21:45:42 UTC+1 użytkownik wloochacz napisał:
> W dniu 2014-02-19 20:16, buni...@gmail.com pisze:
>
> > W dniu środa, 19 lutego 2014 18:07:30 UTC+1 użytkownik wloochacz napisał:
>
> >> W dniu 2014-02-19 14:34, buni...@gmail.com pisze:
>
/ciach/

> > Podłączenie do bazy ustawiam przy tworzeniu wątku. Scenariusz taki.
>
> Ale to connection jest tworzone gdzie - w wątku czy poza nim?
Ten connection (TFDConnection) jest tworzony w wątku. Dla każdego wątku osoby connection.

>
> Poza tym, nie ma potrzeby używania obiektu TFDConnection, wystarczy
>
> przypisać nazwę definicji Twojego prywatnego connection, które wcześniej
>
> zarejestrowałeś w ADManagerze.

Próbowałem i ni jak mi to nie chce działać. W przykładzie są komentarze.
>
>
>
> Po drugie - ja w w ogóle nie używam TADTransaction, to jest wymysł
>
> Firebrida/Interbase (i nie znam żadnej innej bazy, która to obsługuje).
>
> Transakcjami zarządza się z poziomu Connection - po prostu. Zwłaszcza,
>
> kiedy robisz bazodanowe fiku-miku w wątkach.

No nad tym to się muszę zastanowić. Zawsze korzystałem z transakcji w FB i zawsze to się sprawdzało. Jeżeli kosztem wznawiania połączenia ma być rezygnacja z transakcyjności to sam nie wiem.

>
>
>
> czyli tak:
>
> adQuery.Connectionname := 'BUNIAS';
>
> adQuery.PointedConnection.StartTransaction;
>
Tak jak wyżej, niestety adQuery.PointedConnection jest nil'em. Pewne czegoś nie ustawiłem i to jest cały powód zmartwień.
>
> itd.
>
> > Mamy włączonego Firebirda. odpalamy event wątku i otrzymujemy aktualny czas.
>
> > Zatrzymujemy FB. Odpalamy evnet. Po dwóch próbach w fResult mamy coś takiego
>
> > '[FireDAC][Phys][IB]Unable to complete network request to host "10.0.15.22".
>
> > Failed to establish a connection.
>
> > Nie można nawiązać połączenia, ponieważ komputer docelowy aktywnie go odmawia.
>
> > i fajnie bo połącznia nie ma. ale jak widzisz PING przeszedł.
>
> > włączamy FB. Ping przechodzi ładnie i dochodzi do oQry.Open(); i wisi.
>
> >
>
> > Wydaje mi się że może to mieć coś wspólnego z transakcja ale w sumie query powinno samo wystartować transakcje.
>
> Spróbuj to zrobić tak jak napisałem wyżej, bez używania TADTransaction.
>
>
>
> A projekt wyślij, jak dalej nie będzie działać ;-)
>

https://docs.google.com/file/d/0ByYntEj2TVkURXlBXzlYOWZZcGM

Scenariusz taki jak powyżej. FB działą, w memo mamy czas. Po wyłączeniu FB po chwili mamy wyjątek jak wyżej. Włączamy FB i jest zwis.



0 new messages