Sunday, January 29, 2012

AppDomains - Why and How ?

Finally I've found time for this post, which I was planning last year already.
So, where you may need an AppDomain in your application, how you'll treat it and what are the benefits of using it? I'll try to answer to those question. Read further.
Let's start from the theory: An Application Domain represents an isolated environment in a managed .net application process. Saying roughly those are sub-process layers, where the code will execute. The important features of Applicaiton Domains I'd like to mention here are the following:
1. An applicaiton domain has its own - isolated from others, memory
2. Those can be loaded and unloaded dynamically. Unloading an applicaiton domain will unload all the assemblies loaded in that applicaiton domain (there are still some cases when this is not true, although non-practical cases).
Now let's try to find a usage. Imagine a scenario, where the applicaiton needs to load a component from non-trusted source. There are several issues with this, and the most important for me are Security and Reliability.
Security: you never know what the code in that component is doing behind the scenes.
Reliability: the component can be non-well designed, leaving holes for exceptions to arise.
Let's first concentrate on the case, if we are going to handle such scenario without application domains. Of course for interaction with that third-party component there will be a well-defined interface, and every call of a method of that interface (actually an instnace of 3-rd party component, which I will call Component X further) will be wrapped in a try-catch block. But what if the component does something else behind the scenes ? For example it can has a background thread which does some other processing. In this case, your handlers won't be able to handle exceptions coming from that background processed code, because those are not on the stack of a thread you've just called a method in. So what will happen ? I won't go into details here, but will say that an exception coming out from such backgroun thread (which is not properly caught in Component X, will finally take your process down, and you'll have no chance to react, cause have no handlers at all.
Don't blame me here, cause I'm not going into some special cases here, like WinForms, where you can handle all the exceptions on the Application level by registering to ThreadException event.
On the reliability side - you don't know what the code is doing from data-accessing perspective. So there are limited choices for you to react here - limit your own application permissions to not allow the Component X to do something wrong. But still, you will share the same address space in memory with that component.
Ok, now let's jump to the usage of application domains for this scenario, and see how those will solve the above mentioned problems.
First, in FCL (Framework Class Library) there is a type called AppDomain, which is handling management and interaction with application domains. So let me from now on call application domains just AppDomain for the sake of simplicity only.
Well, the proper design for the above scenario will be the following:
1. Create an AppDomain

AppDomain tmpDomain = AppDomain.CreateDomain("ComponentX Domain");

2. Register for the UnhandledException event of that AppDomain

tmpDomain.UnhandledException += new UnhandledExceptionEventHandler(OnComponentDomain_UnhandledException);

3. Add proper permissions to that AppDomain (this is called sandboxing - limiting permissions)
// Leaving code for this section, because this is out of scope from this article
4. Load the Component X assembly into that AppDomain

Assembly tmpComponentAssembly = tmpDomain.Load(AssemblyName.GetAssemblyName("./ComponentX.dll"));

5. Create Component X instance in that AppDomain.
6. Create a proxy in our AppDomain to interact with Component X instance in the new-created domain(because the memory of each AppDomain is isolated, the process of passing data between AppDomain-s involves serialization and deserialization of that data, and whole the process is refered as Marshalling).

Type tmpComponentType = GetComponentTypeFromAssembly(tmpComponentAssembly, typeof(IComponentContract).Name);
IComponentContract tmpComponentProxy = tmpDomain.CreateInstanceAndUnwrap(tmpComponentAssembly.FullName, tmpComponentType.FullName) as IComponentContract;

7. After finishing working with Component X unload AppDomain of Component X to free up memory. This will also unload the assembly loaded in that AppDomain.

// This call is missing in the example solution attached to this article.
tmpDomain.Unload();

Great, this solves the security problem and what about the reliability? The AppDomain type (in Net Framework 4.0) defines an event called UnhandledException. Unfortunately there is no way to handle that exception and continue the normal flow of the application, but the good thing here is that you get your "last-chance" to save state, or log the exception, and even schedule a task in OS to start a new instance of your application. There are several important aspects you should be aware of regarding this event: "This event can be handled in any Applicaiton Domain, although its not necessarily raised in the application domain, where the exception occured. An exception is unhandled only if the entire stack for the thread has been unwound without finding an applicable exception handler, so the first place the event can be raised is in the application domain where the thread originated.
If the UnhandledException event is handled in the default application domain, it is raised there for any unhandled exception in any thread, no matter what application domain the thread started in. If the thread started in an application domain that has an event handler for UnhandledException, the event is raised in that application domain. If that application domain is not the default application domain, and there is also an event handler in the default application domain, the event is raised in both application domains."
The quoted part I've taken from MSDN documentation cause it very well explains that event propogation. I still recommend you to read the MSDN page.
In the section above I've specified the exact version of Net Framework because in the earlier versions the behavior was slightly different. Here is another section from MSDN for earlier versions: "In the .NET Framework versions 1.0 and 1.1, an unhandled exception that occurs in a thread other than the main application thread is caught by the runtime and therefore does not cause the application to terminate. Thus, it is possible for the UnhandledException event to be raised without the application terminating. Starting with the .NET Framework version 2.0, this backstop for unhandled exceptions in child threads was removed, because the cumulative effect of such silent failures included performance degradation, corrupted data, and lockups, all of which were difficult to debug."
I've also created a small solution with code representing the scenario described in this article. Click here to download the example codes.
Post a Comment