All
I have a main procedure which is used to invoke other sub-procedures. I've added the 'DECLARE EXIT HANDLER FOR SQLEXCEPTION' in the main procedure. But when any exception raised in sub-procedure, the HANDLER in main procedure doesn't work.
So how can I catch exceptions generated in all sup-procedures?
vertion of my Teradata is 13.1, and the following is the simplified version of my code.
REPLACE PROCEDURE proc_main()
BEGIN
-- # Handl SQLException
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
insert log table.
END;
CALL proc_sub();
END;
Thanks!
Frank Liu
Are those errors already catched by exception handlers in the sub-procedures?
Only unhanded exceptions are propagated to the calling procedure.
You can use RESIGNAL to throw an error again.
Can you provide more details?
Related
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.
I am working with the DUnitX framework, and am trying to test if the procedure throws an exception.
Currently I have the following test procedure:
procedure TAlarmDataTest.TestIdThrowsExceptionUnderFlow;
begin
Input := 0;
Assert.WillRaise(Data.GetId(input), IdIsZeroException, 'ID uninitialized');
end;
When I go to compile I get an error 'There is no overloaded version of 'WillRaise' that can be called with these arguments.'
Is there a better way to check if the procedure is raising a custom exception, or should I use a try, except block that passes if the exception is caught?
The first parameter of WillRaise is TTestLocalMethod. That is declared as:
type
TTestLocalMethod = reference to procedure;
In other words, you are meant to pass a procedure that WillRaise can call. You are not doing that. You are calling the procedure. Do it like this:
Assert.WillRaise(
procedure begin Data.GetId(input); end,
IdIsZeroException,
'ID uninitialized'
);
The point being that WillRaise needs to invoke the code that is expected to raise. If you invoke the code then the exception will be raised whilst preparing the parameters to be passed to WillRaise. So, we need to postpone execution of the code that is expected to raise until we are inside WillRaise. Wrapping the code inside an anonymous method is one simple way to achieve that.
For what it is worth, the implementation of WillRaise looks like this:
class procedure Assert.WillRaise(const AMethod : TTestLocalMethod;
const exceptionClass : ExceptClass; const msg : string);
begin
try
AMethod;
except
on E: Exception do
begin
CheckExceptionClass(e, exceptionClass);
Exit;
end;
end;
Fail('Method did not throw any exceptions.' + AddLineBreak(msg),
ReturnAddress);
end;
So, WillRaise wraps the call to your procedure in a try/except block, and fails if the desired exception is not raised.
If you are still struggling with understanding this then I suspect you need to brush up your knowledge of anonymous methods. Once you get on top of that I'm sure it will be obvious.
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...
I am a new programmer and trying to understand how Delphi's Application.OnException event works. A colleague has modified the default exception handling by creating his own method and instantiating it and passing it to Application.OnException in the initialization section of a unit. This unit is declared in the uses clause of another unit and otherwise unused.
unit ADLDebug;
...
class procedure TADLExceptionHandler.ADLHandleException (Sender: TObject; E: Exception);
...
initialization
Handler := TADLExceptionHandler.Create;
Application.OnException := Handler.ADLHandleException;
I can only step into the initialization section using the debugger, and not into the ADLHandleException method. I am trying to cause an exception in the code that will be caught by the redefined HandleException method.
Should the scope of the redefined HandleException method be only in the units that include ADLDebug? I was thinking that it should be application wide, but I can't seem to call it.
The scope of Application.OnException is indeed application wide. The event will fire whenever an exception is raised that is not handled.
You are failing to see the event fire because you are raising the exception at startup, before the message loop starts.
I have implemented an editable DBGrid. If a field is not filled correctly an exception is thrown and a message is shown, for example:
'08:00::00' is not a valid time
How can I catch those exceptions so that I can show messages written by me instead of the automatically generated ones? I would be thankful for any help.
As #teran pointed in his comment, the exception is raised by the TDataSet (or one of it's components) that is bind to the TDBGrid, or by the DB engine itself.
You can try to handle the TDataSet.OnPostError (see also OnUpdateError and OnEditError):
TDataSet.OnPostError: Occurs when an application attempts to modify or insert a record and
an exception is raised. Write an OnPostError event handler to handle
exceptions that occur when an attempt to post a record fails.
Note that you could always use Application.OnException global event handler to catch any EDBxxx exceptions in your application.
EDIT: The EConvertError exception is raised before any actual data modifications, or any Post operation by the TDateTimeField field i.e.:
0045af91 +085 Project1.exe SysUtils StrToDateTime <- EConvertError
004ab76a +042 Project1.exe Db TDateTimeField.SetAsString
004a9827 +007 Project1.exe Db TField.SetText
004a95d9 +029 Project1.exe Db TField.SetEditText
004d6448 +014 Project1.exe DBGrids TCustomDBGrid.UpdateData
004d087f +02b Project1.exe DBGrids TGridDataLink.UpdateData
004d599a +01a Project1.exe DBGrids TCustomDBGrid.MoveCol
StrToDateTime is throwing the exception inside TDateTimeField.SetAsString, not touching the data, and
the TDataSet.OnxxxError event handlers will not be fired at all.
So your choices are (test the application in release mode):
1.Intercept and handle EConvertError via Application.OnException.
2.Use TField.EditMask to restrict user input to a valid time format e.g. !90:00;1;_ or use inplace DateTimePicker editor inside your Grid. (and avoid catching this exception).
3.Override TDateTimeField: use persistent fields with your TDataSet and create an inter-poser class as such:
type
TDateTimeField = class(Db.TDateTimeField)
protected
procedure SetAsString(const Value: string); override;
end;
TForm1 = class(TForm)
...
procedure TDateTimeField.SetAsString(const Value: string);
begin
try
inherited SetAsString(Value);
except
on E: EConvertError do
begin
ShowMessage(E.Message);
Abort;
end;
end;
end;
If an exception is being raised then there should be two error messages being shown when run in the debugger. One of these will be caught by the debugger and the 2nd handled by the UI (when running your program as a user you will only see the 2nd).
The exception error message should have contain a string like
Appname.exe raised exception EExceptionName with message XXX
You will need to take note of EExceptionName.
Around the block of code creating the exception you will need to write
...
try
code that can cause the exception here
except
on e: EExceptionName do
begin
ShowMessage('Your apps nicer error message here');
end;
end;
note - if you don't make a call to exit; after handling an exception, your code will continue executing everything after your try..except block. Also, if there are many things that can cause the same error message in the same code block then you may not be able to write anything too specific. e.Message is a string that holds the message that is shown in the unhandled exception and may be useful to also present to the user.
Also try to move away from BDE - ADO is far better supported on modern systems.