Best method for silent exception handling

delphi package - automated exception handling
dkeith
Posts: 10
Joined: Wed Aug 19, 2015 5:32 pm

Best method for silent exception handling

Post by dkeith »

Long time EurekaLog user, moving to madshi due to many bugs/problems w/EurekaLog.

Currently evaluating madExcept, trying to understand how best to do the following things:

Catch all exceptions/leaks etc. program wide
Show no madExcept dialogs, send no madExcept emails, save no madExcept files etc.
Store the complete text of all exceptions/leaks from all threads/objects etc. in variable(s)
Programmatically, conditionally control the three "final options": Continue App/Close App/Restart App

We want to surface our own exception/leak dialogs.

Here are my feeble attempts so far:

Code: Select all


program TestApp;

uses
  madExcept,
  madLinkDisAsm,
  madListHardware,
  madListProcesses,
  madListModules,
  Vcl.Forms,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}


begin
  RegisterExceptionHandler(TWSExceptHandler.madExceptionHandler,stTrySyncCallAlways);
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm1, Form1);
  Application.Run;

end.



unit Unit1;

interface

uses
  madExcept,
  madExceptVcl,
  ...

type
  TWSExceptHandler = class
  public
    class procedure madExceptionHandler(const exceptIntf: IMEException; var handled: Boolean);
  end;

  TForm1 = class(TForm)
  ...
  end;



var
  Form1: TForm1;
  madExcept: TMadExceptionHandler;

implementation

{$R *.dfm}

uses
  ...
  madTypes,
  madStackTrace,
  ...

const
  KStackTraceHeader: UnicodeString = 'Module Name                     |Unit Name                       |Function Name                   |Line #|Relative Line #';

class procedure TWSExceptHandler.madExceptionHandler(const exceptIntf: IMEException; var handled: Boolean);

  function FormatColumn(const colValue: UnicodeString): UnicodeString;
  begin
    Result := colValue;
    if Length(colValue) < 32 then
      while Length(Result) < 32 do
        Result := Result + #32;
  end;

var
  stackTrace: TStackTrace;
  stackItem: TStackItem;
  i,n: Integer;
  s: UnicodeString;
begin
  s := KStackTraceHeader;
  i := 0;
  while SizeOf(stackTrace) > SizeOf(TStackTrace) do
  begin
     stackTrace := exceptIntf.Callstacks[i];
     for n := 0 to Pred(Length(stackTrace)) do
     begin
       stackItem := stackTrace[n];
       with stackItem do
          s := s + FormatColumn(ModuleName) + ' | ' + FormatColumn(UnitName) + ' | ' + FormatColumn(FunctionName) + ' | ' + FormatColumn(IntToStr(Line)) + ' | ' + FormatColumn(IntToStr(RelLine)) + #13#10;
     end;
     for n := 1 to Length(KStackTraceHeader) do
       s := s + '_';
     inc(i);
   end;
   ShowMessage(s);
end;

initialization
  madExcept := TMadExceptionHandler.Create(Nil);
  madExcept.OnException := TWSExceptHandler.madExceptionHandler;

finalization
  madExcept.Free;

end.

Not working yet. Length of stackTrace is 0. Please point to resources that explain this well.

Thanks.
madshi
Site Admin
Posts: 10753
Joined: Sun Mar 21, 2004 5:25 pm

Re: Best method for silent exception handling

Post by madshi »

I'm a bit confused by your code. It seems to both call RegisterExceptionHandler and also use TMadExceptionHandler. I'd recommend to call "madExcept.RegisterExceptionHandler()" instead of using the TMadExceptionHandler class. The class was made for point-and-click users. Calling RegisterExceptionHandler() gives me more control.

If you want to show your own dialog, there is the problem with thread safety. The VCL is *not* thread safe. So you have to use "stTrySyncCallAlways" or "stTrySyncCallOnSuccess". It's preferable to use "stTrySyncCallAlways" but if you use that, your exception handler is not *guaranteed* to be called in the main thread's context. It could also be called in the context of a secondary thread, although madExcept *tries* to call it in the main thread context. Practically this means you have to check "if GetCurrentThreadId = MainThreadId then". Only if that is true, you can use the VCL to show your own dialog. If your exception handler runs in the context of a secondary thread, you can't use the VCL.

The next problem, and the reason why the callstack is empty, is that madExcept tries to show the exception box as soon as possible, so the user doesn't have to wait. And that's usually before the callstacks are fully calculated. The callstacks are then calculated in the background and the exception box is updated when the callstack calculation is done. Practically this means that if you ask the callstacks too early, you'll get empty results. You can work around that by:

1) ... either forcing madExcept to complete the callstack calculation before it returns to you. You can do that by calling "GetBugReport" (doesn't matter what you do with the result of that call).
2) ... or you can use the "epCompleteReport" parameter when calling RegisterExceptionHandler. This means that your handler only gets called once the bug report is complete.

Or you can register two different handlers, one for e.g. epQuickFiltering and one for epCompleteReport. That would allow you to make your own exception dialog behave the same way as madExcept's box: Showing the box first, and then later updating it once the callstacks are available.

Hope that helps?
dkeith
Posts: 10
Joined: Wed Aug 19, 2015 5:32 pm

Re: Best method for silent exception handling

Post by dkeith »

Thanks. I dropped the TMadExceptionHandler component, and added the epCompleteReport phase to the call in the project source to RegisterExceptionHandler.

In my exception handler, I made the first line 'exceptIntf.GetBugReport'. That returns the full exception report in one convenient (huge!) string.

Incidentally how do I get the count of CallStacks?

So I've reduced the code (on your advice) to this:

Code: Select all

program TestApp;

uses
  madExcept,
  madLinkDisAsm,
  madListHardware,
  madListProcesses,
  madListModules,
  Vcl.Forms,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}


begin
  RegisterExceptionHandler(TWSExceptHandler.madExceptionHandler,stTrySyncCallAlways,epCompleteReport);
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm1, Form1);
  Application.Run;

end.


unit Unit1;

interface

uses
  madExcept,
  madExceptVcl,
  ...

type
  TWSExceptHandler = class
  public
    class procedure madExceptionHandler(const exceptIntf: IMEException; var handled: Boolean);
  end;

  TForm1 = class(TForm)
  ...
  end;



var
  Form1: TForm1;
  madExcept: TMadExceptionHandler;

implementation

{$R *.dfm}

uses
  ...
  madTypes,
  madStackTrace,
  ...

const
  KStackTraceHeader: UnicodeString = 'Module Name                     |Unit Name                       |Function Name                   |Line #|Relative Line #';

class procedure TWSExceptHandler.madExceptionHandler(const exceptIntf: IMEException; var handled: Boolean);

  function FormatColumn(const colValue: UnicodeString): UnicodeString;
  begin
    Result := colValue;
    if Length(colValue) < 32 then
      while Length(Result) < 32 do
        Result := Result + #32;
  end;

var
  stackTrace: TStackTrace;
  stackItem: TStackItem;
  i,n: Integer;
  s: UnicodeString;
begin
  exceptIntf.GetBugReport;
  s := KStackTraceHeader;
  i := 0;
  stackTrace := exceptIntf.Callstacks[i];
  while Length(stackTrace) > 0 do
  begin
    for n := 0 to Pred(Length(stackTrace)) do
    begin
      stackItem := stackTrace[n];
      with stackItem do
        s := s + FormatColumn(ModuleName) + ' | ' + FormatColumn(UnitName) + ' | ' + FormatColumn(FunctionName) + ' | ' + FormatColumn(IntToStr(Line)) + ' | ' + FormatColumn(IntToStr(RelLine)) + #13#10;
    end;
    for n := 1 to Length(KStackTraceHeader) do
      s := s + '_';
    inc(i);
    stackTrace := exceptIntf.Callstacks[i];
  end;
  MessageDlg(s,mtError,[mbOk],0);
end;

end.

Also I'm still getting a madExcept 4 dialog box popping up. It simply says, 'An error occurred in the application.', and includes a button with the caption 'close application'. I need to suppress this.

I noticed in the result from the call to GetBugReport that I get back a huge single dimensional string relating to a deliberate access violation, which looks quite comrehensive. I'd need to pare that down to useful information, and dump the rest.

Also, are leaks handled from a separate mechanism? In your dialog I get a list of leaked items with a separate call stack for each item. Does the leak report come from the same exception handler? I need to handle leaks with the same (or a similar) silent mechanism.

Please specify.

Thanks for your prompt reply.
madshi
Site Admin
Posts: 10753
Joined: Sun Mar 21, 2004 5:25 pm

Re: Best method for silent exception handling

Post by madshi »

I don't really understand your "while SizeOf(stackTrace) > SizeOf(TStackTrace) do" code. Shouldn't that always return false?

The way to know the numbers of callstacks is to check the .ThreadIDs property for 0. See documentation, it's directly above .Callstacks.

If you use "epCompleteReport" then you don't have to call exceptIntf.GetBugReport.

You still don't check for "if GetCurrentThreadId = MainThreadId".

You can disable the madExcept exception dialog either by changing the bottom right combobox on this page to "don't show anything":

http://help.madshi.net/madExceptSettings3.htm

Or alternatively you can set "handled := true" in an exception handler. This has to be a different phase than epCompleteReport, though, because epCompleteReport is too late to stop the madExcept exception box from appearing.

Leaks are a completely separate topic. The difficult thing about leaks is that you only know for sure what is a leak and what isn't a leak until all finalization code has run through. But if all finalization code has run through, some units are no longer able to function at all. The way this is solved in madExcept is that leak reporting is not really done by your EXE/DLL files at all. Instead it's done by the external "madExcept32.dll". This way your EXE/DLL files can fully finalize, and after the finalization is complete, madExcept32.dll can do the further processing.

I understand your desire to treat leaks in a specific way, but please consider that if you try to handle leaks from within the EXE which you want to check for leaks, you're not going to get very reliable results, because at the moment you check the leaks, some allocations could still be freed later in the finalization. So you'll get some (or sometimes *a lot*) false positives if you do that.

Maybe the leak APIs can help some?

http://help.madshi.net/madExceptUnit.ht ... akChecking
dkeith
Posts: 10
Joined: Wed Aug 19, 2015 5:32 pm

Re: Best method for silent exception handling

Post by dkeith »

Does this look adequate?

Code: Select all

procedure TExceptGrid.AddData(ExceptIntf: IMEException);
var
  stackTrace: TStackTrace;
  stackItem: TStackItem;
  i: Integer;
  threads: Cardinal;
begin
  i := 0;
  while ExceptIntf.ThreadIds[i] <> 0 do
  begin
    threads := ExceptIntf.ThreadIds[i];
    if threads = MainThreadId then
    begin
      Break;
    end
    else
      inc(i);
  end;
  stackTrace := exceptIntf.Callstacks[i];
  for i := 0 to Pred(Length(stackTrace)) do
  begin
    stackItem := stackTrace[i];
    with stackItem do
      AppendRecord([ModuleName,UnitName,FunctionName,IntToStr(Line),IntToStr(RelLine)]);
  end;
end;
madshi
Site Admin
Posts: 10753
Joined: Sun Mar 21, 2004 5:25 pm

Re: Best method for silent exception handling

Post by madshi »

Looks ok on a quick check if you just want the main thread's callstack. Usually you should be most concerned about the thread which crashed, though. Which might often be the main thread, but it doesn't have to be. If you only want to extract the stack of the thread which crashed, then simply use "exceptIntf.Callstacks[0]" because the crashing thread's callstack is always stored in index 0.
dkeith
Posts: 10
Joined: Wed Aug 19, 2015 5:32 pm

Re: Best method for silent exception handling

Post by dkeith »

Our app is single threaded. We do use various components like Indy, TWebBrowser, and many others. I'm sure several of these are multi-threaded. We log all exceptions in a file.

How do I know when I need to only log the main thread stack and when I need to log another thread stack? If I always log the thread in which the exception occurs, I could end up logging a thread inside of a third party component, right? So I won't know where the main thread's execution was at when the thread crashed, right?

If I log the main thread and the crashed thread, do you think I would be missing anything important that I might need to debug the problem?

Again, our code is expressly single threaded, it's only the vcl & third party components that might have sub threads.

Thanks.
madshi
Site Admin
Posts: 10753
Joined: Sun Mar 21, 2004 5:25 pm

Re: Best method for silent exception handling

Post by madshi »

If your own code is completely single-threaded then I'd suggest checking "if exceptIntf.ThreadIds[0] = MainThreadId". If it is, then just log the first callstack and you're done. If the crashing thread is different from the main thread, then I'd suggest that you use something similar to the code you posted earlier which searches for the main thread and logs it. I think if you log both the main thread and the crashing thread, you should have all the information you need - as long as you're only concerned about exceptions in your own code.

Sometimes there are deadlocks or weird crashes which are hard to debug if you don't have all the callstacks. But that usually applies to multi-threaded programming. So in your case I'd say you should be fine logging just the main thread's and crashing thread's callstack (just one, if both are identical).
dkeith
Posts: 10
Joined: Wed Aug 19, 2015 5:32 pm

Re: Best method for silent exception handling

Post by dkeith »

Ok, so I set up a couple of exceptions. The first exception is in a spawned TThread descendant Execute procedure. I simply declare an object var, then try to access it's classname without instantiating the object. This in turn gives me my thread crash, and enabled me to test my exception writing code so that I write out header elements that I am interested in, and write out the call stacks for the main thread and the crashed thread.

The other exception that I created tries to access another component (TFDConnection) before it has been instantiated, in the main vcl thread. This produces the expected error, which I parse and record the callstack for the main thread only. Great.

When I call both breaking methods in the same procedure, and click 'continue' on my dialog (which calls ContinueApplication) it excepts both times as expected, and I get two separate dialogs, both showing the error that I expect them to show.

In my code I send out an email with a screenshot attachment and an exception report attached as text, using the ExceptionInterface.SendBugReport. However, when I call both crashing methods in sequence, and click continue on both dialogs that display, only the second exception/email gets sent with attachments. The first exception sends the email without attachments.

I set up the exception handling environment in the project source using the MESettings interface, and assign a single exception handler using the phase 'epComplete'. Then my dialog takes the exceptionintf interface and does everything else at the point of the exception.

I realize that this is probably an edge case, as most situations would have the app not continue on to the subsequent exception(s). However, I wonder if I'm doing something wrong, or is this expected behavior?

Should I do something to clear the ExceptionInterface after sending each message? Is there an initialization or finalization method that I should call?

Thanks.
madshi
Site Admin
Posts: 10753
Joined: Sun Mar 21, 2004 5:25 pm

Re: Best method for silent exception handling

Post by madshi »

madExcept itself handles one exception at a time. I suppose due to the way you're using the exception callbacks, somehow it results in multiple exceptions being handles at the same time. Especially the epCompleteReport handler is a corner case because it's done by a seperate thread compared to the other callbacks. Do you still have a callback registered with a different phase than epCompleteReport, e.g. use epMainPhase or epPostProcessing. If your handler doesn't return from these, madExcept shouldn't be able to finish handling the exception, and the next exception should then "wait".

In which handler are you showing your own VCL form atm?
dkeith
Posts: 10
Joined: Wed Aug 19, 2015 5:32 pm

Re: Best method for silent exception handling

Post by dkeith »

I have only designated one handler, with phase epCompleteReport.
madshi
Site Admin
Posts: 10753
Joined: Sun Mar 21, 2004 5:25 pm

Re: Best method for silent exception handling

Post by madshi »

Ah, I see, that makes sense. The disadvantage of doing that is that your own exception box only appears when the bug report is fully rendered. Is that ok for you? It would be more optimal, if you would show your exception box already in epMainPhase, and then to update it in epCompleteReport. This way your exception box would show up much more quickly, but you'd have to add extra code to first create it with just a subset of information, and later update it when the epCompleteReport event fires. If you want to go this way, please make sure that you don't ask "exceptIntf.BugReport" in epMainPhase, because doing that would block until the bug report is fully rendered.

If you're fine with the exception box appearing with a small delay, then I'd suggest a very simple change: Instead of epCompleteReport simply use epMainPhase, and then in your exception handler access "exceptIntf.BugReport". Doing so forces madExcept to finish the creation of the bug report before returning to you. While doing that, madExcept will show a "please wait" progress box, though. If you don't like that, you can disable it in the madExcept IDE settings dialog. See option "show please wait box".

Showing your form in e.g. epMainPhase in a modal way (so your epMainPhase handler doesn't return until the box is closed by the user) should prevent the handling of two exceptions at once, I think.
Tahtu
Posts: 51
Joined: Thu Sep 06, 2012 12:03 pm

Re: Best method for silent exception handling

Post by Tahtu »

madshi wrote:If you want to show your own dialog, there is the problem with thread safety. The VCL is *not* thread safe. So you have to use "stTrySyncCallAlways" or "stTrySyncCallOnSuccess". It's preferable to use "stTrySyncCallAlways" but if you use that, your exception handler is not *guaranteed* to be called in the main thread's context.
How do you decide, if the exception handler is called in the main thread?

Code: Select all

  // stTrySyncCallAlways:    Try to call the handler in the context of the main thread.
  //                         If the main thread doesn't react, call the handler nevertheless.
What do you mean with "doesn't react"?

Do you wait for a time by sending a message to the main window ... and if no response is given, you the hander will be called in a different thread?
madshi wrote:If you want to show your own dialog, there is the problem with thread safety. The VCL is *not* thread safe.
I'm using EurekaLog since 15 years. To create the bug report, I need my main thread to add further information. I never had problems with EurekaLog. So I think, I can use stTrySyncCallAlways to handle my own VCL dialog.

Or what can I do to collect the needed information otherwise?

Indeed, I could protect my information with a thread save critical section...
madshi
Site Admin
Posts: 10753
Joined: Sun Mar 21, 2004 5:25 pm

Re: Best method for silent exception handling

Post by madshi »

Tahtu wrote:What do you mean with "doesn't react"?

Do you wait for a time by sending a message to the main window ... and if no response is given, you the hander will be called in a different thread?
Exactly.
Tahtu wrote:I'm using EurekaLog since 15 years. To create the bug report, I need my main thread to add further information. I never had problems with EurekaLog.
What does EurekaLog do if the exception was raised by a secondary thread? Is your exception callback still called within the context of the main thread? If so, what happens if your main thread is "stuck" (e.g. in an endless loop without message handling) in this situation?
Tahtu wrote:So I think, I can use stTrySyncCallAlways to handle my own VCL dialog.

Or what can I do to collect the needed information otherwise?

Indeed, I could protect my information with a thread save critical section...
What kind of information does your main thread add, and where does that data come from? How is it stored?

Although the VCL is not thread safe, some things can still be done from the context of another thread. A critical section is a valid method to make things thread safe, but I'd need to know more details in order to give a good recommendation.

Using stTrySyncCallAlways should work fine for you. In most cases your exception handler will be called in the context of the main thread. But you should check, just to be safe, by doing "if GetCurrentThreadId = MainThreadId then", because if you try to create a VCL window from within a secondary thread, there will be problems.
Tahtu
Posts: 51
Joined: Thu Sep 06, 2012 12:03 pm

Re: Best method for silent exception handling

Post by Tahtu »

madshi wrote:What does EurekaLog do if the exception was raised by a secondary thread?
... hm, I never ask this by myself... :oops:

Until now, I thought it handle the exception inside the main thread.
madshi wrote:If so, what happens if your main thread is "stuck" (e.g. in an endless loop without message handling) in this situation?
Well, I'm really implementing a lot of bugs... :oops: but endless loops are not the kind I do. But how should I know, if I'm right: I got messages about freezing of the program very rarely.

But reporting bugs is a big problem, I think. Without the automatic handling, a lot of users will not go to my homepage and report it there.

Additional, I implemented a silent bug reporting. Or with other words: My program is sending bug reports to me silently via HTTP - the user does can't see that. I invite him nevertheless to send a bug report via mail to me.

I found out: I get some bugs via mail - but not via HTTP. So some users have a active firewall, I think.

But I get nearly 100 bug report via HTTP, while I get 1 bug report via E-Mail. Indeed some bug reports via HTTP are identical. But I realy get VERY, VERY more bug report silently.

But I know how paranoid some users, because of this, I don't want to send information like user name, Windows version or something like that. Just sending the exception message, the call stack and the logging of my separated thread.
madshi wrote:What kind of information does your main thread add, and where does that data come from? How is it stored?
They are a log of a separated thread. That thread is synchronized with the main thread, so the main thread can access to the log without problems. And if the main thread would freeze, the separated thread would be stoped within a lot of seconds too.
Post Reply