Exceptionhandling with IOmniParallelTask not working - delphi

Unhandled exceptions within IOmniParallelTask execution should (as I understand the docs) be caught by the OTL and be attached to IOmniTaskControl instance, which may be accessed by the termination handler from IOmniTaskConfig.
So after setting up the IOmniParallelTask instance with a termination handler like this:
fTask := Parallel.ParallelTask.NoWait.NumTasks(1);
fTask.OnStop(HandleOnTaskStop);
fTask.TaskConfig(Parallel.TaskConfig.OnTerminated(HandleOnTaskThreadTerminated));
fTask.Execute(TaskToExecute);
any unhandled exceptions within TaskToExecute:
procedure TFormMain.TaskToExecute;
begin
Winapi.Windows.Sleep(2000);
raise Exception.Create('async operation exeption');
end;
should be attached to the IOmniTaskControl instance you get within the termination handler:
procedure TFormMain.HandleOnTaskThreadTerminated(const task: IOmniTaskControl);
begin
if not Assigned(task.FatalException) then
Exit;
memo.Lines.Add('an exception occured: ' + task.FatalException.Message);
end;
The issue at this point is, that the exception is not assigned to IOmniTaskControl.FatalException and I have no clue why.
Maybe some of you guys have some ideas on what I am doing wrong. The whole VCL sampleproject may be found here: https://github.com/stackoverflow-samples/OTLTaskException

This is an abstraction layer problem. Parallel.ParallelTask stores threaded code exception in a local field which is not synchronized with the IOmniTaskControl.FatalException property. (I do agree that this is not a good behaviour but I'm not yet sure what would be the best way to fix that.)
Currently the only way to access caught exception of an IOmniParallelTask object is to call its WaitFor method. IOmniParallelTask should really expose a FatalException/DetachException pair, just like IOmniParallelJoin. (Again, an oversight, which should be fixed in the future.)
The best way to solve the problem with the current OTL is to call WaitFor in the termination handler and catch the exception there.
procedure TFormMain.HandleOnTaskThreadTerminated(const task: IOmniTaskControl);
begin
try
fTask.WaitFor(0);
except
on E: Exception do
memo.Lines.Add('an exception occured: ' + E.Message);
end;
CleanupTask;
end;
I have also removed the HandleOnTaskStop and moved the cleanup to the termination handler. Otherwise, fTask was already nil at the time HandleOnTaskThreadTerminated was called.
EDIT
DetachException, FatalException, and IsExceptional have been added to the IOmniParallelTask so now you can simply do what you wanted in the first place (except that you have to use the fTask, not task).
procedure TFormMain.HandleOnTaskThreadTerminated(const task: IOmniTaskControl);
begin
if not assigned(fTask.FatalException) then
Exit;
memo.Lines.Add('an exception occured: ' + FTask.FatalException.Message);
CleanupTask;
end;
EDIT2
As noted in comments, OnTerminate handler relates to one task. In this example this is not a problem as the code makes sure that only one background task is running (NumTasks(1)).
In a general case, however, the OnStop handler should be used for this purpose.
procedure TFormMain.btnExecuteTaskClick(Sender: TObject);
begin
if Assigned(fTask) then
Exit;
memo.Lines.Add('task has been started..');
fTask := Parallel.ParallelTask.NoWait.NumTasks(1);
fTask.OnStop(HandleOnStop);
fTask.Execute(TaskToExecute);
end;
procedure TFormMain.HandleOnStop;
begin
if not assigned(fTask.FatalException) then
Exit;
memo.Lines.Add('an exception occured: ' + FTask.DetachException.Message);
TThread.Queue(nil, CleanupTask);
end;
As HandleOnStop is called in a background thread (because NoWait is used), CleanupTask must be scheduled back to the main thread, as in the original code.

Related

Accessing TObject instance when it is locked by TMonitor.Enter in the calling thread

What will happen if a thread tries to access the same object locked by another thread? I know it can be handled by TMonitor.Wait(), but what if there is no handling codes to check if it is locked? Will there be an error?
In the example below, Thread1Process locks the object and Thread2Process attempts to assign a value to the object's property. Will the Thread2Process automatically wait before Thread1Process releases the lock to execute the next line var x: Integer := 1; or will it stop and throw an exception?
procedure Thread1Process(const AObject: TObjectDescendant);
begin
TMonitor.Enter(AObject);
try
// lengthy process here...
finally
TMonitor.Exit(AObject);
end;
end;
procedure Thread2Process(const AObject: TObjectDescendant);
begin
AObject.StringProperty := 'Any value';
var x: Integer := 1;
end;
We are using Delphi 11 Alexandria.
TMonitor is just a synchronization lock, nothing more. Much like TMutex, TSemaphore, etc.
It doesn't do anything to the object itself. If one thread decides to enter the lock, and a second thread doesn't, the second thread will not be blocked in any way, and no exception will be raised 1, but there is no guarantee to the stability of the object or its members. Race conditions occur due to lack of proper synchronization by all involved threads cooperating with each other.
1: unless the object itself decides to raise an exception, or a system exception is raised, like from accessing invalid memory, etc.
On a side note, your call to TMonitor.Enter() needs to be outside the try block, eg:
procedure Thread1Process(const AObject: TObjectDescendant);
begin
TMonitor.Enter(AObject);
try
// lengthy process here...
finally
TMonitor.Exit(AObject);
end;
end;

Exception on FormCreate is not thrown

By default any unhandled exception occurring in TForm.OnCreate will lead to an error message beeing shown instead of an exception beeing thrown.
The reason is this code in VCL.Forms:
function TCustomForm.HandleCreateException: Boolean;
begin
Application.HandleException(Self);
Result := True;
end;
All my forms inherit from TMyForm. I plan to override this function to solve the problem:
function TMyForm.HandleCreateException: Boolean;
begin
Result := False;
end;
But I cannot imagine anybody wanting an exception on form creation to be swallowed, leaving a potentially halfly initialized form, yet this code still exists in VCL. This leaves me with the question:
Are there any reasons to not treat unhandled exceptions on form creation this way? Or are there better options to handle my problem?
Edited to clarify the original problem. I have the following code:
try
//...
Frm := TBooForm.Create(...);
Frm.ShowModal;
//...
except
//exception in TBooForm.FormCreate not landing here.
end;
If I don't touch global exception handling, an unhandled exception in TBooForm.FormCreate() will not land in the exception block. Instead Frm will display in halfly initialized state, leading to hard to track errors.

ADODataSet.Open bypasses try catch with `ArgumentOutOfRange` exception, hangs application - Delphi 10.2

I maintain an application that runs as a service in a server environment. It is multithreaded, where each thread does work according to a task queue. This task queue is just a list of strings with "job types" as their values. So while several threads may be running, each thread would be a different job, and each thread internally runs just one task at a time.
I'm experiencing an intermittent issue that happens when calling Open on a TADODataSet. Sometimes, not always, and with no discernible pattern, Data.Win.ADODB will throw an EArgumentOutOfRangeException, bypassing my own attempt to catch any exceptions. This exception hangs the entire thread and prevents future execution from being possible until I completely restart the service.
Being relatively new to the world of Delphi, I've been scratching my head at this issue for quite some time, and have struggled to find any answers. My question is: why is this happening, and how do I stop, catch, or fix it?
Here is a snippet of my offending code. This is the method from which the stack trace originates. It is called from another method in the same unit, where I open a different dataset, loop through its records, and on each record call this function to get some info based on the value passed in.
function TFQFoo.DoSomething(IncNo : Int64): string;
var
ItemList : string;
MySQL: string;
ComponentString: string;
begin
result:='';
if IncNo<=0 then
Exit;
ItemList := '';
MyQuery.Close;
MySQL := 'select ID from tbl ' +
' where val = ' + IntToStr(IncNo) +
' order by col1 DESC, col2, col3';
try
try
MyQuery.CommandText := (MySQL);
MyQuery.Open;
while not (MyQuery.EOF) do
begin
if (ItemList <> '') then
ItemList := ItemList + ',';
ItemList := ItemList +
MyQuery.FieldbyName('ID').asstring;
MyQuery.Next;
end;
except
// exception handling code omitted for brevity -- none of it
// is ever reached, anyway (see below)
end;
finally
MyQuery.Close;
end;
Result := ItemList;
end;
The call stack from the exception indicates that it's occurring at Open. No try..catch block will capture the exception and log it for me -- I have to use EurekaLog in order to see any details. The stack trace methods (too big to post here) look like:
TFQFoo.DoSomething
TDataSet.Open
... internal things
TADOConnection.ExecuteComplete
CheckForAsyncExecute
...
TCustomConnection.GetDataSet
TListHelper.GetItemRange
Thinking possibly my TADODataSet component was somehow getting corrupted / its properties altered at runtime, I added some logging to capture that data for me so I could see if something funky was going on there. I didn't see anything, but here it is in case it's pertinent.
object MyQuery: TADODataSet
AutoCalcFields = False
CacheSize = 15
Connection = FGlobals.RIMSDB
CursorType = ctStatic
LockType = ltReadOnly
CommandText =
'select ID from tbl where val = 202005070074 order by col1 ' +
'DESC, col2, col3'
ParamCheck = False
Parameters = <>
Left = 32
Top = 216
end
For the curious, this is the method actually throwing the exception, from Data.Win.ADODB. Note the except, which I guess hops over my own try..catch block and sends the exception straight to EurekaLog.
procedure CheckForAsyncExecute;
var
I: Integer;
begin
try
if not Assigned(pError) and Assigned(pRecordset) and
((pRecordset.State and adStateOpen) <> 0) then
for I := 0 to DataSetCount - 1 do
if (DataSets[I].Recordset = pRecordset) and (eoAsyncExecute in DataSets[I].ExecuteOptions) then
begin
DataSets[I].OpenCursorComplete;
Break;
end;
except
ApplicationHandleException(Self);
end;
end;
What I have tried:
Many, many iterations of tweaking component properties on the ADODataSet itself
Using the CommandText and Parameters from within the designer, and assigning the parameter prior to execution at runtime
Adding / removing a (NOLOCK) hint to the query itself
Taken the problem to senior members of my team for input
Googling (for hours)
Reading up on Delphi and ADO documentation (not fruitful for this)
Attempted reproduction - I've never been able to get this to occur on any test system I use. This leads me to think it may be environment-related, but I have absolutely no clue how
My question, restated:
How do I stop this from happening? What am I doing wrong? I don't want to just catch the EArgumentOutOfRangeException; I want to learn why it's happening in the first place and prevent it from happening in the future.
I know that sometimes, the query execution will not return results, but the typical CommandText does not return a result set message is never seen nor encountered due to the lower-level code bypassing my own catch statement. Beyond this, I don't know what else to look for.
I've only found one other occurrence of something similar so far, but it relates just to the exception not getting caught: http://www.delphigroups.info/2/d9/410191.html
The call to ApplicationHandleException(Self) in CheckForAsyncExecute() is swallowing exceptions, which is why your except block is not being triggered:
// in Data.Win.ADODB.pas:
procedure CheckForAsyncExecute;
var
I: Integer;
begin
try
...
except
ApplicationHandleException(Self); // <-- a caught exception is NOT re-raised here!
end;
end;
Inside of the Data.Win.ADODB unit, caught exceptions will call the unit's own ApplicationHandleException() function, which then calls System.Classes.ApplicationHandleException if assigned, otherwise it simply exits:
// in Data.Win.ADODB.pas:
procedure ApplicationHandleException(Sender: TObject);
begin
if Assigned(System.Classes.ApplicationHandleException) then
System.Classes.ApplicationHandleException(Sender);
end;
System.Classes.ApplicationHandleException is initialized to nil.
In both a VCL1 and FMX app, the TApplication constructor assigns the TApplication.HandleException() method to System.Classes.ApplicationHandleException, where HandleException() ignores EAbort exceptions, and calls the TApplication.OnException event handler (if assigned), the TApplication.ShowException() method, or the System.SyUtils.ShowException() function, depending on the type of exception being handled.
1: in a VCL TService app, TServiceApplication uses Vcl.Forms.TApplication internally.
TApplication.ShowException() displays the details of the exception to the user in a popup MessageBox and then exits, and System.SysUtils.ShowException() displays the details of the exception to the user in a Console or MessageBox and then exits.
So, at no point does ADO's CheckForAsyncExecute() re-raise a caught exception into user code. And needless to say, displaying a popup MessageBox in a service is not a good idea, as the user will likely not see it so they can dismiss it.
Of course, the best option would be to avoid the EArgumentOutOfRangeException exception from being raised in the first place. But there are other conditions that can also raise exceptions, too.
So, your only option to handle swallowed ADO exceptions yourself, and avoid popup MessageBoxes, is to assign a TApplication.OnException event handler (either directly, or via the TApplicationEvents component).

MadExcept for delphi is not printing stacktrace after application crash

Detailed question :
We are trying to capture the stacktrace (bugreport.txt) using MadExcept in a delphi application where a thread is crashing the application with a fatal error. But MadExcept doesn't print any stacktrace after the application crashes. Any ideas why?
OUR CODE :
procedure TMainForm.WSServerExecute(AContext: TIdContext);
begin
try
HTMLExecute(AContext);
except
on E: Exception do
begin
if not(E is EIdException) then
begin
LogData.AddError('HTMLExecute error: ' + E.Message);
madExcept.HandleException;
end;
raise;
end;
end;
end;
This procedure is called when the client makes a websocket connection back to the server. This is a thread produced by the Indy TCPServer component. The HTMLExecute function is what reads and writes packets between the client and server. I've wrapped that in a try..except block to catch any exceptions. The LogData line is what records the error to the Error Log and the madExcept line is supposed to create the bugreport.txt file. The Raise line passes the exception back to Indy so that it knows a fatal error occurred and will abort the thread.
The reason why madExcept is not handling the exception is because you already caught it with on E:Exception do handling it yourself. Just give madExcept.HandleExcept the exception to handle it:
madExcept.HandleException(etNormal, E);
You could try using RegisterHiddenExceptionHandler(stDontDync). See documentation for more details. In your handler then simply do this:
procedure YourHiddenExceptionHandler(const exceptIntf: IMEException; var handled: boolean);
begin
handled := false;
end;
The above is a trick to force madexcept to work also with handled exceptions, of course it is risky to use it in production...

Another TIdTCPServer deadlock situation

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.

Resources