I have one IBDatabase in DataModule linked with my IBTransaction.
In one module of project I need to control the persistence in two database.
For this, I am adding the second IBDatabase this way:
constructor TConnections.Create(AIBDatabase: TIBDatabase);
begin
if AIBDatabase = nil then
raise Exception.Create('The base connection is needed!');
inherited Create;
FIBDatabase := TIBDatabase.Create(nil);
FIBDatabase.LoginPrompt := false;
FIBDatabase.Params.Clear;
FIBDatabase.Params.Text := AIBDatabase.Params.Text;
FIBDatabase.DatabaseName := AIBDatabase.DatabaseName.Replace('DB.GDB', 'DB2.GDB');
end;
procedure TConnections.SetTransaction(AIBTransaction: TIBTransaction);
begin
if AIBTransaction = nil then
raise Exception.Create('Then Transaction is needed!');
AIBTransaction.AddDatabase(FIBDatabase);
FIBDatabase.DefaultTransaction := AIBTransaction;
FIBDatabase.Open;
end;
Any select commands are work fine, but in insert command the error occurs.
Well, I have this:
connections := TConnections.Create(Dm.Database);
try
connection.SetTransaction(Dm.Transaction);
qry := TIBQuery.Create(nil);
qry.Database := Dm.Database;
try
// here are commands with Dm.Transaction
// ...
qry.ExecSql;
finally
qry.Free;
end;
otherQry := TIBQuery.Create(nil);
otherQry.Database := connection.OtherDatabase;
try
// here are commands with connection.OtherDatabase but same Transaction
// ...
otherQry.ExecSql; // The error occurs here.
finally
otherQry.Free;
end;
Dm.Transaction.Commit;
finally
connection.Free;
end;
'invalid transaction handle (expecting explicit transaction start)'
These block is envolved in try except.
So, if I try again, after the error, the process runs smoothly.
What's wrong in my configuration?
This may occur if you started transaction explicitly. Every explicit transactions must be finished explicitly. So, if your connection is open explicitly, you should close it explicitly.
You may use :
//Commit(Stop) the transaction before open an other connection
if Dm.Transaction.InTransaction then
dm.Transaction.Commit;
Note: In applications that connect an InterBaseExpress dataset to a client dataset, every query must be in its own transaction. You must use one transaction component for each query component.
http://docwiki.embarcadero.com/Libraries/XE8/en/IBX.IBDatabase.TIBTransaction
Related
In trying to update 10000 records by using batch update method over remote mysql connection. my server has 200+ms latency and by using this method it would take forever to do this since its sending queries one by one! any workaround?
query.params.arraysize := 10000;
query.sql.text := 'update table set field=:f1 where id=:f2;'
for i := 0 to query.params.arraysize-1 do
begin
query.params[0].asstrings[i] := 'VERY LONG STRING > 10KB';
query.params[1].asintegers[i] := id;
end;
query.execute(10000);
Try this:
Define the parameterized query.
Configure query params for performance.
e.g:
FDQuery1.Params[0].DataType := ftString;
Set the array size.
Supply the parameters values.
Execute the query in a transaction.
FDConnection.StartTransaction;
try
FDQuery1.Execute(FDQuery1.Params.ArraySize);
FDConnection.Commit;
except
FDConnection.Rollback;
raise;
end;
I have a nasty error when executing a Firedac TFDScript error trying to drop a non-existent table:
Delphi Berlin 10.1 Upd 2
Database Firebird 2.5
It give an error when calling FDScript.ExecuteAll (it passes the FDScript.ValidateAll without any problem)
The code I am executing is as follows:
FDScript: TFDScript;
{...}
begin
FDScript.ScriptOptions.Reset;
FDScript.SQLScripts.Clear;
FDScript.SQLScriptFileName := '';
FDScript.ScriptOptions.CommandSeparator := ';';
FDScript.ScriptOptions.CommitEachNCommands := 1;
FDScript.ScriptOptions.DropNonexistObj := True; // seems to ignore this directive
FDConnection.StartTransaction;
try
FDScript.SQLScripts.Add.SQL.Add('drop table countries;');
FDScript.ValidateAll; // no errors here
ScriptStatus := GetEnumName(TypeInfo(TFDScriptStatus), Ord(FDScript.Status));
if FDScript.Status = ssFinishSuccess then begin
FDScript.ExecuteAll; // ERROR HERE! TABLE COUNTRIES DOES NOT EXIXTS
if FDScript.TotalErrors = 0 then begin
FDConnection.Commit;
end
else begin
FDConnection.Rollback;
end;
end
else begin
FDConnection.Rollback;
end;
except
FDConnection.Rollback;
raise;
end;
end;
Implementation seems to be correct. The engine checks if a raised exception is of ekObjNotExists kind, and if so and the DropNonexistObj property is enabled and command kind is DROP or ALTER, it logs to its console. Otherwise re-raises the caught exception.
The only explanation is then, that you are seeing an exception message dialog shown by debugger. These dialogs are displayed even for handled exceptions (so long you won't add them to an ignore list or turn this feature off, which you should not do).
I have the following problem:
1) I use Delphi XE7 to develop a 3-layer system.
2) The server layer, created with datasnap using REST.
3) I use Firebird as database and the access is performed with FireDAC.
4) I have a sequence with value 01.
5) I created the following query in the server layer:
Select GEN_ID (gen_my_sequence, 1) from rdb $ database
6) On the server returns the sequence value in the query is: 02.
7) But the client layer returns 03.
I do not understand why the query is executed twice.
Can anyone help me?
This is the nature of generators (sequences) in firebird. Their value is increased every time you request it and the value of the generator is updated from that request and remains updated. Also generators live outside of transaction control.
See this firebirdsql generatorguide-basics. It doesn't matter where you request it from.
I use technical standards that the Embarcadero indicates.
What I realized was this:
1) The unit Data.FireDACJSONReflect in TFDJSONInterceptor.ItemListToJSONObject routine has this block of code:
if not LActive then
LDataSet.Active := True;
try
LJSONDataSet := DataSetToJSONValue(LDataSet);
// Use AddPair overload that will accept blank key
AJSONObject.AddPair(TJSONPair.Create(LPair.Key, LJSONDataSet))
finally
if not LActive then
LDataSet.Active := False;
end;
See he activates the query once, causing the sequence to be incremented.
But in DataSetToJSONValue (LDataSet) routine; This code block is:
if (LMemTable = nil) then
begin
LMemTable := TFDMemTable.Create(nil);
LAdapter := TFDTableAdapter.Create(nil);
LMemTable.Adapter := LAdapter;
LAdapter.SelectCommand := ADataSet.Command;
LMemTable.Active := True;
end;
See he again activates the query, where the sequence is again incremented.
Now I do not know if I made a mistake or if it is a bug, but I created a new class inherited from TFDMemTable and thought there was some mistake in this class, but did a test with TFDMemTable component, standard component of FireDAC, and even then the activation of any query is performed twice, because the code does not consider any of these two classes, as a TFDCustomMemTable, even though they were inherited directly from this class.
I commented the code of DataSetToString routine (const ADataSet: TFDAdaptedDataSet) that looked like this:
LMemTable := nil;
LAdapter := nil;
try
//if (ADataSet is TFDCustomMemTable) then
LMemTable := TFDCustomMemTable(ADataSet);
{if (LMemTable = nil) then
begin
LMemTable := TFDMemTable.Create(nil);
LAdapter := TFDTableAdapter.Create(nil);
LMemTable.Adapter := LAdapter;
LAdapter.SelectCommand := ADataSet.Command;
LMemTable.Active := True;
end;}
In this way the problem was solved, and the performance of the application seemed to have improved.
In our Delphi XE4 application we are using an OmniThreadPool with MaxExecuting=4 to improve the efficiency of a certain calculation. Unfortunately we are having trouble with intermittent access violations (see for example the following MadExcept bug report http://ec2-72-44-42-247.compute-1.amazonaws.com/BugReport.txt). I was able to construct the following example which demonstrates the problem. After running the following console application, an access violation in System.SyncObjs.TCriticalSection.Acquire usually occurs within a minute or so. Can anybody tell me what I am doing wrong in the following code, or show me another way of achieving the desired result?
program OmniPoolCrashTest;
{$APPTYPE CONSOLE}
uses
Winapi.Windows, System.SysUtils,
DSiWin32, GpLists,
OtlSync, OtlThreadPool, OtlTaskControl, OtlComm, OtlTask;
const
cTimeToWaitForException = 10 * 60 * 1000; // program exits if no exception after 10 minutes
MSG_CALLEE_FINISHED = 113; // our custom Omni message ID
cMaxAllowedParallelCallees = 4; // enforced via thread pool
cCalleeDuration = 10; // 10 miliseconds
cCallerRepetitionInterval = 200; // 200 milliseconds
cDefaultNumberOfCallers = 10; // 10 callers each issuing 1 call every 200 milliseconds
var
gv_OmniThreadPool : IOmniThreadPool;
procedure OmniTaskProcedure_Callee(const task: IOmniTask);
begin
Sleep(cCalleeDuration);
task.Comm.Send(MSG_CALLEE_FINISHED);
end;
procedure PerformThreadPoolTest();
var
OmniTaskControl : IOmniTaskControl;
begin
OmniTaskControl := CreateTask(OmniTaskProcedure_Callee).Schedule(gv_OmniThreadPool);
WaitForSingleObject(OmniTaskControl.Comm.NewMessageEvent, INFINITE);
end;
procedure OmniTaskProcedure_Caller(const task: IOmniTask);
begin
while not task.Terminated do begin
PerformThreadPoolTest();
Sleep(cCallerRepetitionInterval);
end;
end;
var
CallerTasks : TGpInterfaceList<IOmniTaskControl>;
i : integer;
begin
gv_OmniThreadPool := CreateThreadPool('CalleeThreadPool');
gv_OmniThreadPool.MaxExecuting := cMaxAllowedParallelCallees;
CallerTasks := TGpInterfaceList<IOmniTaskControl>.Create();
for i := 1 to StrToIntDef(ParamStr(1), cDefaultNumberOfCallers) do begin
CallerTasks.Add( CreateTask(OmniTaskProcedure_Caller).Run() );
end;
Sleep(cTimeToWaitForException);
for i := 0 to CallerTasks.Count-1 do begin
CallerTasks[i].Terminate();
end;
CallerTasks.Free();
end.
You have here an example of hard-to-find Task controller needs an owner problem. What happens is that the task controller sometimes gets destroyed before the task itself and that causes the task to access memory containing random data.
Problematic scenario goes like this ([T] marks task, [C] marks task controller):
[T] sends the message
[C] receives the message and exits
[C] is destroyed
new task [T1] and controller [C1] are created
[T] tries to exit; during that it accesses the shared memory area which was managed by [C] but was then destroyed and overwritten by the data belonging to [C1] or [T1]
In the Graymatter's workaround, OnTerminated creates an implicit owner for the task inside the OmniThreadLibrary which "solves" the problem.
The correct way to wait on the task to complete is to call taskControler.WaitFor.
procedure OmniTaskProcedure_Callee(const task: IOmniTask);
begin
Sleep(cCalleeDuration);
end;
procedure PerformThreadPoolTest();
var
OmniTaskControl : IOmniTaskControl;
begin
OmniTaskControl := CreateTask(OmniTaskProcedure_Callee).Schedule(gv_OmniThreadPool);
OmniTaskControl.WaitFor(INFINITE);
end;
I will look into replacing shared memory record with reference-counted solution which would prevent such problems (or at least make them easier to find).
It looks like your termination message is causing the problem. Removing the message and the WaitForSingleObject stopped the AV. In my tests just adding a .OnTerminated(procedure begin end) before the .Schedule also did enough to change the flow and to stop the error. So the code in that case would look like this:
procedure PerformThreadPoolTest();
var
OmniTaskControl : IOmniTaskControl;
begin
OmniTaskControl := CreateTask(OmniTaskProcedure_Callee).OnTerminated(procedure begin end).Schedule(gv_OmniThreadPool);
WaitForSingleObject(OmniTaskControl.Comm.NewMessageEvent, INFINITE);
end;
It looks to me like this might be the problem. otSharedInfo_ref has a property called MonitorLock. This is used to block changes to otSharedInfo_ref. If for some reason otSharedInfo_ref is freed while the acquire is waiting then you are likely to get some very weird behavior
The code as it stands looks like this:
procedure TOmniTask.InternalExecute(calledFromTerminate: boolean);
begin
...
// with internal monitoring this will not be processed if the task controller owner is also shutting down
sync := nil; // to remove the warning in the 'finally' clause below
otSharedInfo_ref.MonitorLock.Acquire;
try
sync := otSharedInfo_ref.MonitorLock.SyncObj;
if assigned(otSharedInfo_ref.Monitor) then
otSharedInfo_ref.Monitor.Send(COmniTaskMsg_Terminated,
integer(Int64Rec(UniqueID).Lo), integer(Int64Rec(UniqueID).Hi));
otSharedInfo_ref := nil;
finally sync.Release; end;
...
end; { TOmniTask.InternalExecute }
If otSharedInfo_ref.MonitorLock.Acquire is busy waiting and the object behind otSharedInfo_ref is freed then we end up in a very nasty place. Changing the code to this stopped the AV that was happening in InternalExecute:
procedure TOmniTask.InternalExecute(calledFromTerminate: boolean);
var
...
monitorLock: TOmniCS;
...
begin
...
// with internal monitoring this will not be processed if the task controller owner is also shutting down
sync := nil; // to remove the warning in the 'finally' clause below
monitorLock := otSharedInfo_ref.MonitorLock;
monitorLock.Acquire;
try
sync := monitorLock.SyncObj;
if assigned(otSharedInfo_ref) and assigned(otSharedInfo_ref.Monitor) then
otSharedInfo_ref.Monitor.Send(COmniTaskMsg_Terminated,
integer(Int64Rec(UniqueID).Lo), integer(Int64Rec(UniqueID).Hi));
otSharedInfo_ref := nil;
finally sync.Release; end;
...
end; { TOmniTask.InternalExecute }
I did start getting AV's in the OmniTaskProcedure_Callee method then on the "task.Comm.Send(MSG_CALLEE_FINISHED)" line so it's still not fixed but this should help others/Primoz to further identify what is going on. In the new error, task.Comm is often unassigned.
I'm having the known issue: IdTCPServer hangs while trying to deactivate it. I've read lots of articles concerning such problem and I'm aware of the deadlock happening. But it's difficult for me to understand which code exactly causes the problem. My app doesn't have even a window so the synchronized call to VCL component isn't the reason. Got 2 event handlers OnConnect and OnExecute. The problem appears in OnConnect. The code:
TMyServer = class
...
private
FServer: TIdTCPServer;
...
end;
TMyServer.ServerConnect(AContext...); //onconnect
begin
try
if not Condition then
begin
...
AContext.Connection.Disconnect;
Stop;
end
else Start;
except
on E: Exception do
if E is EIdException then raise;
...
end;
TMyServer.Start;
begin
...
FServer.active := true;
FServer.StartListening;
...
end;
TMyServer.Stop;
begin
...
FServer.StopListening;
FServer.Active := false;
...
end;
TMyServer.ServerExecute(AContext...); //onexecute
begin
LLine := AContext.Connection.IOHandler.ReadLn;
case (LLine) of
...
end;
end;
The code of ServerExecute is quite huge so I won't post it here. Besides I suppose the problem isn't in it.
When the client connects to the server and Condition is false the server tries to disconnect this client and hangs on the line FServer.Active := false;
I've already excluded all the logging from the code (I thought the problem was in the threads access to log file). So in the event handlers there are only calculations and nothing which can cause the deadlock). I've read about re-raising of Indy exceptions but that didn't help me too.
I would appreciate any help and explanation cause I think I don't fully understand the purpose of this situation.
You are trying to deactivate the server from inside one of its event handlers. That is why your code is deadlocking.
Setting the Active property to False terminates all running client threads and waits for them to fully terminate. The OnConnect, OnDisconnect, and OnExecute events are triggered in the context of those threads. So you end up with a thread that is trying to wait on itself and deadlocks.
To have the server deactivate itself safely, you must do it asynchronously, such as by using the TIdNotify class, for example:
uses
..., IdSync;
TMyServer.ServerConnect(AContext...); //onconnect
begin
try
if not Condition then
begin
...
AContext.Connection.Disconnect;
TIdNotify.NotifyMethod(Stop);
end
else
TIdNotify.NotifyMethod(Start);
except
on E: Exception do
if E is EIdException then raise;
...
end;
end;
BTW, you do not need to call the StartListening() and StopListening() methods manually. The Active property setter does that internally for you.