10

I've found several C# application crashes in response to error conditions such as obj = null or obj.member = null. A lot of time, the obj from the interface of 3rdPartyApp. And caused both 3rdPartyApp and MyCsApp crashed together.

How could I add exception handling in all possible areas so that my application can survive in these disastrous situations? It is a challenge to add try-catch to ALL places, and recover from the situation.

How could I accomplish this in a way which is realistic, reliable and bullet-proof?

[Update: Industry Automation Control]

Structure:

GUI(asp.net, c++) - RuntimeApp (C++) - MyCsApp(cs) - 3rdPartyApp(Cs)

Normal procedure:

  1. HostApp --(Connect through ethernet Cabele)-- MyCsApp
  2. Operator -- GUI -- RuntimeApp -- MyCsApp

Abnormal conditions:

  1. Some non-standard operation procedure;
  2. some hardware issue occurred;
  3. etc.

I'd better handle all the abnormall conditions. And most importantly, I must think how to recover from the situations.

Nano HE
  • 9,109
  • 31
  • 97
  • 137
  • 17
    How about fixing the bugs instead? – Aaronaught Feb 12 '10 at 02:38
  • @Aaronaught, we already protected some places currently, That means We can only fixed it after happend the cursh under abnormal operations. MyCsApp a multi-threads application based on the 3rdPartyApp. We must read several kinds logs(HostApp log, runtimeApp log,MyCsApp Log and 3rdPartyApp Log) and try our best to duplicate the issue. Then we can fix it. – Nano HE Feb 12 '10 at 02:45
  • 9
    Two words. Don't do it. Damn, that was three... Well now I may as well say why. You will prevent your app from gracefully exiting when it needs to. If you use the global and thread level exception handlers mentioned by others below, have a plan for WHAT to do when it's reached. . . Logging the error is fine, including the call stack will help with fixing the bug. Keeping the app up after may not be a good idea in most cases... But then again it might... Know your code and environment to know for sure. – Jason D Feb 12 '10 at 03:00
  • 3
    I'm sorry to have to tell you that it's too late. You already failed your company. The time to worry about exception handling was when the application was being designed. In fact, I'm shocked to hear of factory automation software not being designed for safety. Are you from Toyota? – John Saunders Feb 12 '10 at 03:03
  • 7
    If each and every exception is truly coming from a 3rd-party app - and isn't just the result of various bugs in processing the data from it - then you obviously need to treat this app as untrustworthy, and put error handling in every instance of communication with this app, at the point of origin, *where you might actually be able to handle it*. If a `NullReferenceException` bubbles to the top of the stack, you're doomed, there's no graceful recovery. – Aaronaught Feb 12 '10 at 03:15
  • 3
    And having said that, looking at your comment, reading from a 3rd-party application's log file is **not** the way to do interop. There are several options - memory-mapped files, named pipes, shared databases - but if you're reading directly from a log file, I would suspect that you are running into a number of locking issues and race conditions. The fact that you mention multi-threading also lends credence to the explanation of inadequate synchronization and possible race conditions. Sounds like you're in for a long month of debugging. – Aaronaught Feb 12 '10 at 03:16
  • @Aaronaught, And I would like to verify "every instance of communication with my app" as your suggestion. Could you give me more guide about **- memory-mapped files, named pipes, **. Can I add some trace log to MyCsApp and create the memory-mapped file and named pipes file? thanks. – Nano HE Feb 12 '10 at 03:39
  • 1
    @Aaronaught: I think it may be too late. If this were someone who understood memory mapped files, etc., then maybe it could get done. I think a better strategy would be to hire a consultant with advanced skills to clean up the mess, or to at least tell them how. I'm very concerned about the current path. I'm not visiting any factories until I know what company this guy works for. – John Saunders Feb 12 '10 at 03:50
  • @Nano HE: Memory-mapped files and named pipes are *methods of inter-process communication*, ones that you would design into an IPC application from the start. They are not related to application tracing or exception handling in any significant way. I suggest starting your research here: http://stackoverflow.com/questions/50153/interprocess-communication-for-windows-in-c-net-2-0 – Aaronaught Feb 12 '10 at 04:01
  • @John Saunders: Maybe it is too late, but if there's any chance we can help to prevent one more engineering disaster (without actually working for free), then I'll take the odds. Call me an optimist! – Aaronaught Feb 12 '10 at 04:03
  • @John Saunders: Hello. I am newbie from China, I worked as a hardware engineer till Aug 2008. After layoff, I found a new job to develope software from Apr 2009. Acturally I am not a Computer background student in university, but I am familiar to hardware. My new boss told me that I can get the job if I really enjoied to be a software engineer and detail oriented coding. Maybe I asked some dumb questions. It's an exciting thing to learn new programming knowledge for me. My team also try our best to prevent crush happening again. I posted here to get kind suggestion. thank you. – Nano HE Feb 12 '10 at 04:46
  • @John Saunders: "Are you from Toyota?" <- Ha! Hysterical! – Jim G. Feb 27 '10 at 22:31
  • @Nano HE: glad to hear more from China: It's not a dumb question - you've just got some dumb managers, apparently. If I'm ever fortunate enough to visit China, I will not be visiting any factories. – John Saunders Feb 27 '10 at 22:46

7 Answers7

19

You do not want to catch every exception everywhere.

You want to prevent exceptions from "leaking out" of the lower layers of your application up to where they can kill the application or corrupt it.

But preventing corruption is going to require more than just catching exceptions. You're going to have to make sure that the application is always safe to interrupt at every point where an exception could be thrown. This may mean that you need to clean up complicated operations. For example:

ComplexBuilder cb = new ComplexBuilder();
try
{
    cb.AddOperation(...);  // Once building starts,
    cb.AddOperation(...);  // it's not safe to use cb
    cb.AddOperation(...);
}
catch (SpecificException ex)
{
    cb.Cleanup();          // until it's cleaned up
}

// Now safe to access cb, whether or not an exception was thrown

I recently ran into an application with a similar attitude. There was piece of this application that was considered to be "important". When that "important" thing happened, there were other things that were supposed to happen, but which were considered "not important". The idea was that if there was an exception in the "not important" part, then it was necessary for the "important" part to continue.

What happened is that an attempt to read a resource failed for some reason. This returned null instead of the string resource. This caused an ArgumentNullException in a String.Format call. This caused the exception to be caught by code that just continued.

But between the first exception and the last one, an object was to have been allocated, and the reference to the object was to have been set. But because of the exception, setting the reference never happened. The result was that I saw a NullReferenceException, four stack levels up, and two .csproj files away from where the actual problem happened.

So when you talk about catching exceptions so that your program can continue, you need to keep in mind that the control flow of your program is changed drastically by catching all these exceptions. In fact, it could be changed so much that you can no longer determine whether it's safe for your program to continue executing.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
7

This is something that a lot of developers don't get. By the time your exception catch-all gets hit, your application has already crashed. Something unexpected happened, which means that your code didn't anticipate it, and things are very likely to be in an indeterminate state (i.e. you can't be certain exactly how much of the offending function completed at the point the exception was generated, you don't know how much data got written out, what bits got set in the hardware, etc.). Is it safe to continue on? Should you try to save out the user's data? Who knows!

When you reach this high-level catch-all you're going to provide, you haven't prevented your app from crashing. You're just deciding what to do about the crash at that point. You can put up a different message than the standard:

This application has performed an illegal operation

...but what's your custom message going to say that's any better?

We're shutting down without warning for unscheduled maintenance, but rest assured that it had nothing to do with a flaw in this excellent software

...?

Scott Smith
  • 3,900
  • 2
  • 31
  • 63
4

you should certainly not add try catch everywhere

you just need a top level catch of all exceptions. If this is a GUI app then just display a nice dialog with a button saying 'please report to support' (it can write out a stack trace snapshot to the screen or a file)

if you are lucky then the app can continue (lucky since you have no way of knowing if you are really stuck badly)

note that you can also do this

        AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
        Forms.Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);

but that doesnt stop it crshing, it just lets you capture the failure

pm100
  • 48,078
  • 23
  • 82
  • 145
3

It would make sense to cure the disease first, find out why it's causing to crash, sure the code is crashing because of obj = null or similar - using exception handling and swallowing all exceptions is just masking the problem....That's what it is not used for! It sounds like there's a lot of code-smells that is triggering the crashes - Protecting your application from crashing is not the right way to deal with it and only making things worse...

Ok, you can follow John Saunders's and pm100's suggestion to do that...but handle it in a manner to see what's the root cause, do not treat it as a 'magic silver bullet', at the end of the day, the code that is interacting with the third party application needs to be debugged thoroughly...

for instance

object foo = null;
bar baz;

// ....
// foo is now set by thirdparty app

if (foo != null && foo is bar)  baz = (bar)foo as bar;

if (baz != null){

  // Continue on, baz is a legitimate instance of type 'bar'

}else{

  // Handle it gracefully or throw a *user defined exception*

}

Notice how the 'as' is used to check if 'foo' is of the right type for 'bar' instance - now compare with this, that is a typical code smell...

object foo = null;
bar baz;

// foo is now set by thirdparty app - ARE YOU REALLY SURE ITS NON-NULL?
// IS IT REALLY OF TYPE 'BAR'?

baz = foo; // CRASH! BANG! WALLOP! KERRUNCH!
t0mm13b
  • 34,087
  • 8
  • 78
  • 110
  • @tommieb75 & Aaronaught. I would like to accept your reply as my company application solution. **Try to find the root cause and fix it**. @ALL. I would like to do more study and practice based on your repliese. If possible, I would try to add the **sub module level** crash handle with your solutions. THANKS A LOT. – Nano HE Mar 02 '10 at 02:28
0

One technique to avoid such exceptions is to use the null object pattern.

So instead of having Account account = null; you would do Account account = Account.SpecialNullAccount; and in your Account class you would define SpecialNullAccount as a static Account object. Now if you try to use account.Name in some inconsequential code (like, say logging code) you don't get an Exception, instead you get a value like, say "NO NAME" defined on the static null object instance.

Of course it's important that such an object DOES throw Exceptions in any important case like .PayInvoice() or objectContext.SaveChanges() so take steps to ensure that happens.

Shog9
  • 156,901
  • 35
  • 231
  • 235
Ian Mercer
  • 38,490
  • 8
  • 97
  • 133
  • 1
    @Hightechrider: yes, the null object pattern prevents exceptions, but it doesn't make the program any more correct. It would be better to have the exception, safe the application, then fix the problem. – John Saunders Feb 12 '10 at 03:05
  • @John Saunders. Disagree that null value pattern doesn't improve your code. The null value pattern prevents pointless exceptions that really weren't necessary (e.g. log (account.Name) is NOT somewhere you want an Exception just because the user isn't logged in) AND it reveals more about your code because it differentiates between forgetting to initialize a variable and having a value that's deliberately not set yet: When PayInvoice() fails you can immediately see whether it's because the account value was never set OR because the user wasn't logged in. – Ian Mercer Feb 12 '10 at 06:15
  • Null object pattern in OO is very similar to `NULL` values in databases - it solves some problems, but brings with it a mountain of new ones. It prevents "fail fast", allowing a program to run incorrectly for a long time and fail mysteriously later on. What *would* help to ensure correctness in this scenario is non-nullable types, such as those implemented by Spec#, but the time to introduce extensions like that is when the code is stable, not crashing everywhere. – Aaronaught Feb 12 '10 at 14:19
0

AppDomain.UnHandledException and/or AppDomain.ThreadException are events you can subscribe to to catch unhandled exceptions. I don't think you can use this to continue execution where it left off (and this probably woudn't be a good idea anyway). They can be used swallow the error message or log it, etc.

Whether this is a good idea to do is up to you however!

Martin Booth
  • 8,485
  • 31
  • 31
  • 1
    One Caveat to this advice is that if the exception happened due to being in a bad/unrecoverable state, this will actually cause the app to continue to misbehave, and thoroughly frustrate a user. Sure it's frustrating to deal with an app that crashes, however it's more frustrating to deal with an app that is overly misbehaving. – Jason D Feb 12 '10 at 03:02
0

If it is a Winforms application add event handlers for for the following events in Main method like this

Application.ThreadException += Application_ThreadException;
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

You can then show a messagebox or other notification, loog the exeception etc and continue working without crashing the app.

softveda
  • 10,858
  • 6
  • 42
  • 50
  • One Caveat to this advice is that if the exception happened due to being in a bad/unrecoverable state, this will actually cause the app to continue to misbehave, and thoroughly frustrate a user. Sure it's frustrating to deal with an app that crashes, however it's more frustrating to deal with an app that is overly misbehaving. – Jason D Feb 12 '10 at 03:02
  • Agree with Jason. I was describing a LOB app that has errors due to individual bad data and not in general. So you can happily continue after catching the exception. Another set of data you work with may have no issue at all. – softveda Feb 12 '10 at 04:17
  • I wouldn't say that you can "happily" continue. At a *bare minimum* these errors need to be thoroughly logged, investigated, tracked, resolved in a sane and organized fashion. Simply skipping the file or outputting a cryptic debug message may allow the *application* to keep running but will bring down the *business* very fast. – Aaronaught Feb 12 '10 at 14:22
  • Aaronaught, you misunderstood the "you". You in this case is the end user. Of course the exception handler logs everything and the end user reports the error via some ticketing system. Each support ticket is analysed, fixed, reported etc. What I meant is that for the end user the app doesn't crash but he/she can continue using it after reporting the error. You the developer of course has the responsibility the fix the cause of the error and not happily shove it under the bench. – softveda Feb 12 '10 at 22:36