Home Random Page


CATEGORIES:

BiologyChemistryConstructionCultureEcologyEconomyElectronicsFinanceGeographyHistoryInformaticsLawMathematicsMechanicsMedicineOtherPedagogyPhilosophyPhysicsPolicyPsychologySociologySportTourism






IL_006a: newobj instance

void [mscorlib]System.Func`2<char, bool>::.ctor(object, native int) IL_006f: call class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>

[System.Core]System.Linq.Enumerable::Where<char>(

class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>, class [mscorlib]System.Func`2<!!0, bool>)

IL_0074: call !!0[] [System.Core]System.Linq.Enumerable::ToArray<char> (class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>)

IL_0079: ret

}

 

As you can see, an OutOfMemoryException is possible when constructing the <>c Display­ Class1 class (a compiler-generated type), the Object[] array, the Func delegate, and boxing the char and Decimal. Memory is also allocated internally when Concat, Where, and ToArray are called. Constructing the Decimal instance could cause its type constructor to be invoked, resulting in a TypeInitializationException.5 And then, there are the implicit calls to Decimal’s op_Implicit operator and its op_Addition operator methods, which could do anything, including throwing an OverflowException.

Querying Stream’s Position property is interesting. First, it is a virtual property and so my One­ Statement method has no idea what code will actually execute which could throw any exception at all. Second, Stream is derived from MarshalByRefObject, so the stream argument could actually refer to a proxy object which itself refers to an object in another AppDomain. The other AppDomain could be unloaded, so an AppDomainUnloadedException could also be thrown here.

Of course, all the methods that are being called are methods that I personally have no control over because they are produced by Microsoft. And it’s entirely possible that Microsoft might change how these methods are implemented in the future, so they could throw new exception types that I could not possibly know about on the day I wrote the OneStatement method. How can I possibly write my OneStatement method to be completely robust against all possible failures? By the way, the opposite is also a problem: a catch block could catch an exception type derived from the specified exception type and now I’m executing recovery code for a different kind of failure.

So now that you have a sense of all the possible failures, you can probably see why it has become culturally acceptable to not write truly robust and reliable code: it is simply impractical. Moreover, one could argue that it is actually impossible. The fact that errors do not occur frequently is another reason why it has become culturally acceptable. Because errors (like OutOfMemoryException) occur very infrequently, the community has decided to trade truly reliable code for programmer productivity.

 

 
 

5 By the way, System.Char, System.String, System.Type, and System.IO.Stream all define class constructors, which

could all potentially cause a TypeInitializationException to be thrown at some point in this application.




One of the nice things about exceptions is that an unhandled one causes your application to termi- nate. This is nice because during testing, you will discover problems quickly and the information you get with an unhandled exception (error message and stack trace) are usually enough to allow you to fix your code. Of course, a lot of companies don’t want their application to just terminate after it has been tested and deployed, so a lot of developers insert code to catch System.Exception, the base class of all exception types. However, the problem with catching System.Exception and allowing the application to continue running is that state may be corrupted.

Earlier in this chapter, I showed an Account class that defines a Transfer method whose job is to transfer money from one account to another account. What if, when this Transfer method is called, it successfully subtracts money from the from account and then throws an exception before it adds money to the to account? If calling code catches System.Exception and continues running, then the state of the application is corrupted: both the from and to accounts have less money in them then they should. Because we are talking about money here, this state corruption wouldn’t just be considered a simple bug, it would definitely be considered a security bug. If the application continues running, it will attempt to perform more transfers to and from various accounts and now state cor- ruption is running rampant within the application.

One could say that the Transfer method itself should catch System.Exception and restore money back into the from account. And this might actually work out OK if the Transfer method is simple enough. But if the Transfer method produces an audit record of the withdrawn money or if other threads are manipulating the same account at the same time, then attempting to undo the operation could fail as well, producing yet another thrown exception. And now, state corruption is getting worse, not better.

       
   
 
 


There are several things you can do to help mitigate state corruption:

 

■ The CLR doesn’t allow a thread to be aborted when executing code inside a catch or finally

block. So, we could make the Transfer method more robust simply by doing the following.

 

public static void Transfer(Account from, Account to, Decimal amount) { try { /* do nothing in here */ }

finally {

from ­= amount;

// Now, a thread abort (due to Thread.Abort/AppDomain.Unload) can’t happen here to += amount;

}

}

 

However, it is absolutely not recommended that you write all your code in finally blocks! You should only use this technique for modifying extremely sensitive state.

■ You can use the System.Diagnostics.Contracts.Contract class to apply code contracts to your methods. Code contracts give you a way to validate arguments and other variables before you attempt to modify state by using these arguments/variables. If the arguments/ variables meet the contract, then the chance of corrupted state is minimized (not completely eliminated). If a contract fails, then an exception is thrown before any state has been modified. I will talk about code contracts later in this chapter.

■ You can use constrained execution regions (CERs), which give you a way to take some CLR uncertainty out of the picture. For example, before entering a try block, you can have the CLR load any assemblies needed by code in any associated catch and finally blocks. In addition, the CLR will compile all the code in the catch and finally blocks including all the methods called from within those blocks. This will eliminate a bunch of potential excep- tions (including FileLoadException, BadImageFormatException, InvalidProgram­

Exception, FieldAccessException, MethodAccessException, MissingFieldException, and MissingMethodException) from occurring when trying to execute error recovery code (in catch blocks) or cleanup code (in the finally block). It will also reduce the potential for OutOfMemoryException and some other exceptions as well. I talk about CERs later in this chapter.

■ Depending on where the state lives, you can use transactions which ensure that all state is modified or no state is modified. If the data is in a database, for example, transactions work well. Windows also now supports transacted registry and file operations (on an NTFS volume only), so you might be able to use this; however, the .NET Framework doesn’t expose this functionality directly today. You will have to P/Invoke to native code to leverage it. See the System.Transactions.TransactionScope class for more details about this.


■ You can design your methods to be more explicit. For example, the Monitor class is typically used for taking/releasing a thread synchronization lock as follows.

 

public static class SomeType {

private static Object s_myLockObject = new Object();

 

public static void SomeMethod () {

Monitor.Enter(s_myLockObject); // If this throws, did the lock get taken or

// not? If it did, then it won't get released!

try {

// Do thread­safe operation here...

}

finally {

Monitor.Exit(s_myLockObject);

}

}

// ...

}

 

Due to the problem just shown, the overload of the preceding Monitor’s Enter method used is now discouraged, and it is recommended that you rewrite the preceding code as follows.

 

public static class SomeType {

private static Object s_myLockObject = new Object();

 

public static void SomeMethod () {

Boolean lockTaken = false; // Assume the lock was not taken try {

// This works whether an exception is thrown or not! Monitor.Enter(s_myLockObject, ref lockTaken);

 

// Do thread­safe operation here...

}

finally {

// If the lock was taken, release it

if (lockTaken) Monitor.Exit(s_myLockObject);

}

}

// ...

}

 

Although the explicitness in this code is an improvement, in the case of thread synchronization locks, the recommendation now is to not use them with exception handling at all. See Chapter 30, “Hybrid Thread Synchronization Constructs,” for more details about this.

If, in your code, you have determined that state has already been corrupted beyond repair, then you should destroy any corrupted state so that it can cause no additional harm. Then, restart your application so your state initializes itself to a good condition and hopefully, the state corruption will not happen again. Because managed state cannot leak outside of an AppDomain, you can destroy


any corrupted state that lives within an AppDomain by unloading the entire AppDomain by calling

AppDomain’s Unload method (see Chapter 22 for details).

 

And, if you feel that your state is so bad that the whole process should be terminated, then you can call Environment’s static FailFast method.

 

public static void FailFast(String message);

public static void FailFast(String message, Exception exception);

 

This method terminates the process without running any active try/finally blocks or Final­ ize methods. This is good because executing more code while state is corrupted could easily make matters worse. However, FailFast will allow any CriticalFinalizerObject-derived objects, discussed in Chapter 21, “The Managed Heap and Garbage Collection,” a chance to clean up. This is usually OK because they tend to just close native resources, and Windows state is probably fine even if the CLR’s state or your application’s state is corrupted. The FailFast method writes the message string and optional exception (usually the exception captured in a catch block) to the Windows Ap- plication event log, produces a Windows error report, creates a memory dump of your application, and then terminates the current process.

       
   
 
 

 

The point of this discussion is to make you aware of the potential problems related to using the CLR’s exception-handling mechanism. Most applications cannot tolerate running with a corrupted state because it leads to incorrect data and possible security holes. If you are writing an application that cannot tolerate terminating (like an operating system or database engine), then managed code is not a good technology to use. And although Microsoft Exchange Server is largely written in managed code, it uses a native database to store email messages. The native database is called the Extensible Storage Engine; it ships with Windows, and can usually be found at C:\Windows\System32\EseNT.dll. Your applications can also use this engine if you’d like; search for “Extensible Storage Engine” on the Microsoft MSDN website.

Managed code is a good choice for applications that can tolerate an application terminating when state corruption has possibly occurred. There are many applications that fall into this category. Also, it takes significantly more resources and skills to write a robust native class library or application;

for many applications, managed code is the better choice because it greatly enhances programmer productivity.



Date: 2016-03-03; view: 599


<== previous page | next page ==>
Nbsp;   Trading Reliability for Productivity | Don’tCatch Everything
doclecture.net - lectures - 2014-2024 year. Copyright infringement or personal data (0.01 sec.)