Try except stops the service execution - delphi

List := FQueue.LockList;
for I := 0 to List.Count - 1 do
begin
Mail := TIdMessageTaskman(List[I]);
FEventLogger.LogMessage( 'Mail' + Mail.ToString, EVENTLOG_INFORMATION_TYPE , 0, 2);
try
try
FidSmtp.Connect();
FidSmtp.Send(Mail);
except
on e: exception do
begin
FEventLogger.LogMessage('Error sending mail ' + e.ClassName + ', ' +
e.Message, EVENTLOG_ERROR_TYPE, 0, 2);
MarkMailExecution(Mail.TaskID, Mail.NotificationID, False, e.Message);
Continue;
end;
end;
finally
begin
if FidSmtp.Connected then
FidSmtp.Disconnect;
end;
end;
FEventLogger.LogMessage( 'after finally', EVENTLOG_INFORMATION_TYPE , 0, 2);
MarkMailExecution(Mail.TaskID, Mail.NotificationID, True, '');
FreeAndNil(Mail)
So the following code works, but as soon as there is a problem sending an e-mail and the exception is raised, the service stops. Is there I way I can make it continue and go through all the Queue? Even if there are messages with errors. For example of an error that stops my service is when "I attach" a file that does not exist.

You said you've confirmed you get into the finally section. So there are 3 possibilities:
A line of code in the finally section blocks the code from continuing.
Another exception is raised in finally section.
When you enter the finally section, you're already in an "exception state". So leaving finally takes you to the next finally/except section in the call stack.
You'll have to add debug logging to confirm which, but I suspect number 3. Possible triggers for the existing exception state:
Your Mail instance is not valid, and your swallower caught an Access Violation. When you again try to use Mail in the except section, you get another Access Violation.
Something within MarkMailExecution triggers its own exception.
(I'm assuming your logging mechanism isn't failing because you have been getting some information from it.)

Related

Get exception text from FireDAC debugger notification

I'm trying to capture the debugger notification text, Firedac connection
//FDScript1.SQLScripts.Add.SQL.LoadFromFile('C:\SGI\Rodar_Apos_Atualizar_3.txt');
//FDScript1.ValidateAll;
//FDScript1.ExecuteAll;
//MSScript1.SQL.LoadFromFile('C:\SGI\Rodar_Apos_Atualizar_3.txt');
//MSScript1.Execute;
try
FDConnection1.Connected := True;
FDScript1.SQLScripts.Add.SQL.LoadFromFile('C:\SGI\Rodar_Apos_Atualizar_3.txt');
FDScript1.ExecuteAll;
FDScript1.ValidateAll;
except
//EMSSQLNativeException
on E: EMSSQLNativeException do
begin
//ShowMessage('Erro'+FDGUIxErrorDialog1.ErrorDialog.Caption);
//Memo1.Clear;
Memo1.Text := 'Erro de Sistema: '+#13#10+ E.Message;
end;
end;
It really would help if you would show us the scripts you are trying to execute,
or at least the script which does not execute correctly. In any case, your code is wrong
because the documentation
states
It is good practice to call the ValidateAll method before the ExecuteAll method.
Note the 'before'. Your ValidateAll is after ExecuteAll, not before. Both are Boolean
functions but you are not checking their results, which you should.
With some trivial experimenting I found that I can provoke a EMSSQLNativeException
using SqlServer 2014 with the code below:
procedure TForm2.Button1Click(Sender: TObject);
const
sScript = 'select m.*, d.* from master m join detail d on, m.masterid = d.masterid';
var
FDScript : TFDSqlScript;
begin
try
FDConnection1.Connected := True;
FDScript := FDScript1.SQLScripts.Add;
FDScript.SQL.Text := sScript;
//FDScript1.ExecuteAll;
//FDScript1.ValidateAll;
// FDScript1.ValidateAll then
//FDScript1.ExecuteAll;
FDQuery1.SQL.Text := sScript;
FDQuery1.Open();
except
//EMSSQLNativeException
on E: EMSSQLNativeException do
begin
//ShowMessage('Erro'+FDGUIxErrorDialog1.ErrorDialog.Caption);
//Memo1.Clear;
Memo1.Text := 'Erro de Sistema: '+#13#10+ E.Message;
end;
end;
end;
Note the blatantly wrong syntax in the Sql statement, namely the comma after the on.
When FDQuery1.Open is called, this exception is raised (and caught initially by the debugger)
---------------------------
Debugger Exception Notification
---------------------------
Project sqlerror.exe raised exception class EMSSQLNativeException with message '[FireDAC][Phys][ODBC][Microsoft][SQL Server Native Client 11.0][SQL Server]Incorrect syntax near 'd'.'.
---------------------------
Break Continue Help
---------------------------
When I click Continue, execution proceeds into your exception handler, exactly as #TomBrunberg described in a comment
and the exception's message text is inserted into Memo1.
So I cannot reproduce the behaviour you describe based on the information in your question. It
must be caused by something you are not telling us, possibly in code you have
not included in your q or some property setting of the components you are
using.
Hopefully, trying the code above, you will find the debugger behaving as I
have described and this may give you some clue as to why you are getting the
problem you've described. Note that it is very important that you try the code
above in a new project, not your existing one. The only property setting
you need to do before executing it is to set FDConnection1 so that it can connect
to your server.
FWIW, if I uncomment-out the lines
FDScript1.ValidateAll then
FDScript1.ExecuteAll;
they execute without complaint, and I get the exact same behaviour as i've described without them.

Clientdataset Append/Post fails intermittently

I have an external message coming in every second.
The message payload is saved in a ClientDataSet and displayed in a dbGrid.
No data base is involved. RAM storage only.
This works fine,
BUT,
I have intermittent problems when the dataset is empty and populated the first time.
The code is as follows:
procedure TCtlCfg_DM_WarningsFaults_frm.DecodeRxFrame(Protocol: TProtocolSelection;
// PROVA UTAN VAR VAR Frame : CAN_Driver.TCAN_Frame);
Frame : CAN_Driver.TCAN_Frame);
var
OldRecNo : integer;
// OldIxname : string;
// bMark : TBookMark;
WasFiltered : Boolean;
IdBitFields : TCanId_IdBitFields;
Msg : TCan_Msg;
MsgType : integer;
GlobalNode : TCanId_GlobalNode;
LocalNode : TCanId_LocalNode;
SubNode : TCanId_SubNode;
EntryType : integer;
SubSystemType : integer;
SubSystemDeviceId : integer;
IsActive : Boolean;
IsAcked : Boolean;
begin
with cdsWarningsFaults do
begin
if not Active then Exit;
Msg := Frame.Msg;
IdBitFields := DecodeCanId(Protocol, Frame.ID);
if IdBitFields.SubNode <> cSubNode_Self then Exit; // Ignore non controller/slave messages
if IdBitFields.AddressMode <> cCanId_AddrMode_CA then Exit;
MsgType := IdBitFields.MessageType;
if MsgType <> cMsg_CTL_CA_Broadcast_WarningAndFaultList then Exit;
if Frame.MsgLength < 5 then Exit;
GlobalNode := IdBitFields.GlobalNode;
LocalNode := IdBitFields.LocalNode;
SubNode := IdBitFields.SubNode;
// Silent exit if wrong node
if GlobalNode <> fNodeFilter.GlobalNode then Exit;
if LocalNode <> fNodeFilter.LocalNode then Exit;
if SubNode <> fNodeFilter.SubNode then Exit;
EntryType := Msg[1];
SubSystemType := Msg[2];
IsActive := (Msg[3] = 1);
SubSystemDeviceId := Msg[4];
IsAcked := (Msg[8] = 1);
DisableControls; // 2007-12-03/AJ Flytta inte scrollbars under uppdatering
OldRecNo := RecNo;
// OldIxName := IndexName; // Save current index
// IndexName := IndexDefs.Items[0].Name;
WasFiltered := Filtered; // Save filter status
Filtered := False;
try
try
if Findkey([GlobalNode, LocalNode, SubNode, EntryType, SubSystemType, SubSystemDeviceId]) then
begin // Update record
Edit;
FieldByName('fIsActive').AsBoolean := IsActive;
FieldByName('fIsAcked').AsBoolean := IsAcked;
FieldByName('fTimeout').AsDateTime := GetDatabaseTimeoutAt;
Post;
MainForm.AddToActivityLog('CtlCfg_DM_WF: DecodeRxFrame: Efter Edit. N=' + IntToStr(GlobalNode) + ' ' +
IntToStr(LocalNode) + ' ' +
IntToStr(SubNode) +
' RecCnt=' + IntToStr(RecordCount) + ' ET=' + IntToStr(EntryType) + ' SST=' + IntToStr(subSystemType) + ' SSD=' + IntToStr(SubSystemDeviceId), False);
end
else
begin // Create new record
Append;
MainForm.AddToActivityLog('CtlCfg_DM_WF: DecodeRxFrame: Efter Append. N=' + IntToStr(GlobalNode) + ' ' +
IntToStr(LocalNode) + ' ' +
IntToStr(SubNode) +
' RecCnt=' + IntToStr(RecordCount) + ' ET=' + IntToStr(EntryType) + ' SST=' + IntToStr(subSystemType) + ' SSD=' + IntToStr(SubSystemDeviceId), False);
try
FieldByName('fGlobalNode').AsInteger := GlobalNode;
FieldByName('fLocalNode').AsInteger := LocalNode;
FieldByName('fSubNode').AsInteger := SubNode;
FieldByName('fEntryType').AsInteger := EntryType;
FieldByName('fSubSystemType').AsInteger := SubSystemType;
FieldByName('fSubSystemDeviceId').AsInteger := SubSystemDeviceId;
FieldByName('fIsActive').AsBoolean := IsActive;
FieldByName('fIsAcked').AsBoolean := IsAcked;
FieldByName('fTimeout').AsDateTime := GetDatabaseTimeoutAt;
finally
try
Post; // VArför biter inte denna post så att det blir edit nästa gång
except
MainForm.AddToActivityLog('CtlCfg_DM_WF: DecodeRxFrame: Exception efter Post.', True);
end;
MainForm.AddToActivityLog('CtlCfg_DM_WF: DecodeRxFrame: Efter Post. N=' + IntToStr(GlobalNode) + ' ' +
IntToStr(LocalNode) + ' ' +
IntToStr(SubNode) +
' RecCnt=' + IntToStr(RecordCount) + ' ET=' + IntToStr(EntryType) + ' SST=' + IntToStr(subSystemType) + ' SSD=' + IntToStr(SubSystemDeviceId), False);
end;
end;
except
on E: Exception do
begin
MainForm.AddToActivityLog('Post exception message: [' + E.Message + ']', False);
MainForm.AddToActivityLog('Post exception class: [' + E.ClassName + ']', False);
MainForm.AddToActivityLog('Post exception Error code: [' + IntToStr(EDBCLIENT (E).ErrorCode) + ']', False);
MainForm.AddToActivityLog('Post exception ReadOnly is: [' + BoolToStr(ReadOnly) + ']', False);
MainForm.AddToActivityLog('Post exception CanModify is: [' + BoolToStr(CanModify) + ']', False);
MainForm.AddToActivityLog('DecodeRxFrame: Exception inside FindKey block', False);
Cancel;
end;
end;
finally
// IndexName := OldIxName; // Restore previous index
Filtered := WasFiltered; // Restore filter state
if (OldRecNo >= 1) and (OldRecNo <= RecordCount) then RecNo := OldRecNo;
EnableControls;
end;
end;
//MainForm.AddToActivityLog('DecodeRxFrame: Exit ur proceduren', False);
end;
The problem is when the record does not already exist,
and I need to Append a new record.
It often works fine, but many times it seems the POST does not work,
and the append is repeated a few or many times when new data comes in.
Suddenly the append works, and subbsequent updates are done using edit,
and as far as I can tell, after that it then works forever.
The issue is intermittent and the number of tries needed to succeed vary.
It feels like a timing issue, but I cannot figure it out.
Any ideas greatly appreciated.
Thanks,
Anders J
As mentioned in my comment a lot can be figured out about how the code flows using an extract of the logs. (Also as a side-note, sometimes you need to be careful of the reliability of your logging system. Your logging is at least partially home-brew, so I have no idea what it means when you arbitrarily pass True/False values to the AddToActivityLog method.)
However, I am still able to offer some guidance to help you identify your problem. I also have some general comments to help you improve your code.
You're not shy to use logging to narrow down your problem: this is a good thing!
However you technique could use a little improvement. You're trying to determine what's going wrong around the Post method. Given such a clear goal, your logging seems surprisingly haphazard.
You should do the following:
//Log the line before Post is called. It confirms when it is called.
//Log important state information to check you're ready to post "correctly"
//In this it's especially useful to know the Sate of the dataset (dsEdit/dsInsert).
Post;
//Log the line after Post to confirm it completed.
//Note that "completed" and "succeeded" aren't always the same, so...
//Again, you want to log important state information (in this case dsBrowse).
If you had this this logging, you might (for example) be able to tell us that:
Before calling Post dataset is in dsInsert state.
And (assuming no exceptions as you say): after calling Post the dataset is still in dsInsert state.
NOTE: If it were in dsBrowse but Post still considered "unsuccessful", you'd be told to log details of the record before and after Post.
So now: Post "completing" without the record being "posted" would give a few more things to look at:
What events are hooked to the data set? Especially consider events used for validation.
Since you're working with TClientDataSet there's an important event you'll want to take a look at OnPostError. DBClient uses this callback mechanism to notify the client of errors while posting.
If you log OnPostError I'm sure you'll get a better idea of the problem.
Finally I mentioned you have a lot of other problems with your code; especially the error handling.
Don't use with until you know how to use it correctly. When you know how to use it correctly, you'll also know there's never a good reason to use it. As it stands, your code is effectively 2 characters short of a subtle bug that could have been a nightmare to even realise it even existed; but would be a complete non-issue without with. (You declared and used a property called IsActive differing by only 2 characters from TDataSet's Active. I'm sure you didn't realise this; and their difference is but an accident. However, if they had been the same, with would very quietly use the wrong one.)
You need to write smaller methods - MUCH smaller! Blobs of code like you have are a nightmare to debug and are excellent at hiding bugs.
Your exception handling is fundamentally wrong:
Your comment about logging and exception handling suggests that you've been simply adding what you can out of desperation. I think it pays to understand what's going on to keep your logging useful and avoid the clutter. Let's take a close look at the most problematic portion.
/_ try
/_ FieldByName('fGlobalNode').AsInteger := GlobalNode;
/_E FieldByName('fLocalNode').AsInteger := LocalNode;
| FieldByName('fSubNode').AsInteger := SubNode;
| FieldByName('fEntryType').AsInteger := EntryType;
| FieldByName('fSubSystemType').AsInteger := SubSystemType;
| FieldByName('fSubSystemDeviceId').AsInteger := SubSystemDeviceId;
| FieldByName('fIsActive').AsBoolean := IsActive;
| FieldByName('fIsAcked').AsBoolean := IsAcked;
| FieldByName('fTimeout').AsDateTime := GetDatabaseTimeoutAt;
|_ finally
/_ try
/_ Post;
/ except
| MainForm.AddToActivityLog(..., True);
| end;
|_ MainForm.AddToActivityLog(..., False);
/ end;
|
...
So, in the above code:
If no exceptions happen, you'd simply step from one line to the next.
But as soon as an exception happens, you jump to the next finally/except block.
The first problem is: Why would you try to force a Post if you haven't finished setting your field values. It's a recipe for headaches when you end up with records that have only a fraction of the data they should - unless you're lucky and Post fails because critical data is missing.
When finally finishes during an exception, code immediately jumps to the next finally/except in the call-stack.
Except is slightly different, it only gets called if something did go wrong. (Whereas finally guarantees it will be called with/without an exception.
TIPS: (good for 99% of exception handling cases.
Only use try finally for cleanup that must happen in both success and error cases.
Nest your try finally blocks: The pattern is <Get Resource>try .... finally<Cleanup>end (The only place to do another resource protection is inside the .....)
Avoid except in most cases.
The main exception to the previous rule is when cleanup is needed only in the case of an error. In which case: do the cleanup and re-raise the exception.
Other than that: only implement an except block without re-raising if you can fully resolve an error condition. (Meaning the lines of code immediately after the exception swallower truly don't care about the previous exception.

User friendly exception error delphi

I have this method where i execute a sql statement and catch a error in a try except statement
AdoQuery := TAdoQuery.Create(self);
AdoQuery.connection := AdoConnection;
AdoQuery.SQL.Add(sqlStr);
AdoQuery.Prepared := true;
try
begin
AdoQuery.ExecSql;
AdoQuery.Active := false;
end;
except on e:eAdoError do
ShowMessage('Error while creating the table: ' + e.Message);
end;
I can catch the error like this and show it to the user but it's showing some useless info for the user. I Would like to show only the %msg part of the error, take a look at the pic:
I tought e.MEssage allow me to get only the %msg part but it give me the whole thing hardly understoodable by a random user. How do i get only the usefull info in this case
Table reftabtest.rPCE already exists
Thank you.
You can use the Errors property of the TADOConnection object, what you want is the Description member of the Error object.
In your case:
function ParseOBDCError(ErrorDescription : String) : String;
var
Ps : Integer;
Pattern : String;
begin
Pattern := '%msg:';
Ps := Pos(Pattern, ErrorDescription);
if Ps > 0 then
begin
Result := Copy(ErrorDescription, Ps+Length(Pattern)+1);
// if you want, you can clean out other parts like < and >
Result := StringReplace(Result, '<', , '', [rfReplaceAll]);
Result := StringReplace(Result, '>', , '', [rfReplaceAll]);
Result := Trim(Result);
end
else
Result := ErrorDescription;
end;
...
AdoQuery := TAdoQuery.Create(self);
AdoQuery.connection := AdoConnection;
AdoQuery.SQL.Add(sqlStr);
AdoQuery.Prepared := true;
try
AdoQuery.ExecSql;
AdoQuery.Active := false;
except on e : Exception do
begin
if AdoConnection.Errors.Count > 0 then
ShowMessageFmt('Error while creating the table: %s',
[ParseOBDCError(AdoConnection.Errors[0].Description)])
else
ShowMessageFmt('something went wrong here: %s', [e.Message]);
end;
end;
That message dialog wasn't shown by your ShowMessage() code.
First, the icon is wrong - that's not the ShowMessage icon.
Second, the text you added ('Error while creating the table: ') to the message is missing.
This means your exception swallower is not catching the exception, because it's not of the EADOError class. So what's happening is the application's default exception handler is showing the exception.
Before I explain how to fix it, I need to point out that your exception swallower is wrong (your's should not be misnamed an exception handler).
Because you're swallowing the exception: If another method calls yours it will incorrectly think you method succeeded, and possibly do something it shouldn't. You should never write code that makes an assumption that there isn't a significant call stack leading into your method.
Swallowing to show a message to the user doesn't help, because it hides the error from the rest of the program. Especially since, as you can see: there is already code in one place that tells the user about the error without hiding it from the rest of the program. (The problem you have is that you want a friendlier message.)
To fix it:
First find out what the actual exception class is, so you're able to catch the correct error.
Now you have a number of options, but the simplest is as follows:
First log the exception, preferably with call stack. You don't want to be stuck in the situation where the user gets a friendly message but you as developer lose critical information if you need to do some debugging.
To get the call stack you can consider third-party tools like Mad Except, Exceptional Magic, JCLDebug to name a few.
Now show the message to the user.
Finally call Abort. This raises an EAbort exception which by convention is a "silent exception". It tells the rest of the program that there was an error (so it doesn't do things assuming everything is fine). But by convention, any further exception handlers should not show another message to the user. This includes the default handler's dialog in your question.
If the default handler is incorrectly showing EAbort messages, then it should be fixed.

Exception is generated twice by TNMPOP3.Connect

Long time ago I wrote following code to retrieve emails from mailbox:
pop3 := TNMPOP3.Create(Self);
try
pop3.Host := FAppSettings.ServerName;
pop3.Port := FAppSettings.ServerPort;
pop3.UserID := FAppSettings.Login;
pop3.Password := FAppSettings.Password;
try
pop3.Connect;
except
on E:Exception do AddError(E.Message);
end;
if not pop3.Connected then Exit;
if pop3.MailCount > 0 then begin
pop3.DeleteOnRead := False;
pop3.AttachFilePath := GetTempDirectory;
ProcessMsgs(pop3);
end
else begin
TCommon.InfMsg('There are no messages in mailbox');
end;
pop3.Disconnect;
finally
pop3.Free;
end;
Now, when mail service provider switched entirely to SSL this code fails obviously, but in a strange way:
pop3.Connect line causes an exception but with an empty text in E.Message making the problem unclear to end user.
Investigation of the problem in Delphi debugger reveals that the first time the right exception is generated:
Project .... raised exception class Exception with message 'Authentication failed'.
but then, when I press F8 (Step Over) again, execution point remains in the same line and another exception is generated:
Project .... raised exception class Exception with message ''.
and only this exception is catched by try-except.
Why?
To answer your actual question of "why?", the sequence you describe means that TNMPOP3.Connect() is internally catching the original authentication exception and throwing a new exception without an error message. Whether that is a bug or intentional, there is no way to know without looking at the source code for TNMPOP3. Delphi did not ship with that source code, and NetMasters are no longer around so you can't ask them for it. TNMPOP3 does not support SSL anyway, so you will have to switch to another component/library to handle your POP3+SSL functionality moving forward.

displaying custom error message rather than system error message in Delphi

I would like my custom error message to show up rather than the one generated by the system. Specifically, in this case, if the user tries to open a file that is already in use, I would like to catch the error and display my message, but the system always beats me to it and generates the following message: "Project...raised exception class EFOpenError with message 'Cannot open file "File path and name". The process cannot access the file because it is being used by another process'." When I close this error message, that is when my message is displayed. I would like only my message to be displayed. I do not want the system message displayed. Is there a way to do this? My code does not work.
Here is my code:
begin
//check if input file can be opened
try
if OpenDialog1.Execute then
begin
Application.ProcessMessages;
Memo1.Clear;
try
Memo1.Lines.LoadFromFile(OpenDialog1.FileName);
InputFile := TStringList.Create;
InputFile.LoadFromFile(OpenDialog1.FileName);
ActiveFileName := ExtractFileName(OpenDialog1.FileName);
lblActiveFileName.Caption := 'Active File: ' + ActiveFileName;
mmuDisplayClear.Enabled := True;
mmuAnalysisOptions.Enabled := True;
except on
EFOpenError do //if file does not exist or is in use
MessageDlgPos('File either does not exist or may be ' +
'in use!', mtError, [mbOK], 0, 300, 300);
end;
end;
finally
mmuDispInstructions.Enabled := True;
mmuDispData.Enabled := False;
end;
end;
You are seeing the debugger catch the exception before your application does. That is perfectly normal behavior. Simply press F9 to pass the exception to your app for normal handling. If you don't want the debugger to display the exception, put the exception class type in the debugger's Ignore list, or wrap the code in breakpoints that disable/enable the debugger's exception handling.
When you are running an application through Delphi IDE (debuging it) and an exceptions is raised from within try..except..end clause the exception will first be detected by IDE and if you then press F9 code from the except block will fire.
So if you generate custom message there that message will be shown. But you don't need to show a message. You might wanna handle that exception silently.
Let's take a look at the next example: You are trying to establish a network connection to some server. In the call for establishing connection you specify certain timeout after which atempt to connect is considered as failed. After this timeout expires most network components raise a Etimeout exception so you know that establishing connection wa unsucsessfull. But due to the way how networks sometimes behave you might wanna go and retry the atempt to connect to the server again before showing an error to the user. You can do this by simply calling connect method again within the except block in order to initiate another atempt wihout rasing any errors. So code would look someting like this:
Timeout := 2000;
try
NetworkComponent.Connect(Timout);
except
try
NetworkComponent.Connect(Timeout);
except
MessageDlgPos('Connection could not be established!,
mtError, [mbOK], 0, 300, 300);
end;
end;
As you see in the example in first try..except..end block we handle the exception silently but in the next try..except..end block which is nested within the except block of the first try..except..end; block where we finally show message to the user.
While this is generaly considered as bad practice sometimes you might not even want to show any message abot such error. For instance if you are making some system service or a background process one thing that you definitly woul not want is to bother user with bunch of error messages. it is still good to athleast write the errors in some log for further debuging.
Now if you application is ran outside the Delphi IDE (not debuging) or if you turn of specific Exception detection in IDE then the code within the except block will fire directly. when specific exception is raised.
EDIT: Removed unrelated text.

Resources