Application Domains

Operating systems typically provide some form of isolation between different applications running on the same system. This isolation is necessary to ensure that code running in one application does not adversely affect other (most likely unrelated) applications.

Historically, most OSs, including Windows, achieve this isolation using process boundaries. Under this model, there is one process per executing application and a crash in one application cannot affect any other executing application.

The common language runtime has similar needs for the isolation. However, there are many scenarios in which isolation at the process boundary is too expensive in terms of performance: A process switch involves a thread switch, saving and restoring call stack, and so on. For this reason, .NET advocates running multiple applications within the same process.

Although multiple .NET applications can run in the same process, the need for isolation is still there. You do not want one errant application to bring down the whole process. This isolation is achieved by means of application domains.

An application domain (or AppDomain for short) is the common language runtime's equivalent of an OS process in many respects. User code and data are isolated to the AppDomain in which they are loaded. In other words, the user code from one AppDomain cannot be called from the user code from another AppDomain directly and data cannot be shared directly between application domains.

A process can have multiple application domains. However, an application domain cannot span multiple processes, just as a process cannot span multiple machines. This relationship is illustrated in Figure 6.1.

Figure 6.1. Application domains.


Under .NET, assemblies can be loaded and the user code can be executed only within the context of an application domain. When the common language runtime is first loaded within a process, it automatically creates a default application domain to execute the user code. However, more application domains can be created (within the same process) either by the common language runtime host or by the user code.

An application domain has a friendly name, assigned to it at the time of its creation. The default application domain gets its name from the filename of the first assembly it loads.

The following code excerpt demonstrates obtaining the friendly name of an application domain:

// Project AppDomain/AppDomainName

class MyApp {
     public static void Main()      {

Console.WriteLine(AppDomain.CurrentDomain.FriendlyName);
     }
}

The BCL provides a class, AppDomain (namespace System), to create and manage application domains. This class has a static property, CurrentDomain, that returns an AppDomain object representing the application domain where the method is called. The preceding code displays the FriendlyName property for the current application domain.

It is possible to programmatically create new application domains. This is typically done by applications hosting the common language runtime. For example, ASP.NET hosts all the ASP.NET applications on the machine within a single process; each application is hosted in a separate application domain. However, .NET provides the necessary isolation among the application domains.

Listing Application Domains

Is there a way to list all the application domains within a process?

Here is the trick. Just run the command-line debugger (cordbg.exe) that comes with the SDK and enter the command pro at the prompt. This command displays all the managed processes running on the system along with the list of application domains for each process.

You can also get the list of loaded assemblies within an application domain. Again, using cordbg, attach to the process that you are interested in by typing the command a <pid>, where pid is the process identifier. Then you can type ap to dump a list of all the application domains within the process along with their loaded assemblies. Remember to detach the process (command de) before you quit the debugger. Otherwise, the attached process terminates prematurely.


The fact that application domains are isolated from each other also makes it possible to unload an application domain without causing the process to become unstable. You can use static method AppDomain.Unload for this purpose. If you are writing a custom runtime host, you can also use ICorRuntimeHost::UnloadDomain to unload an application domain. Check the SDK documentation for more information on these APIs.

So, why would you unload an application domain? Well, sometimes you may wish to unload an assembly, perhaps for the purpose of upgrading it. However, an assembly, once loaded, cannot be unloaded directly. A type from one loaded assembly could be using a type from another loaded assembly. It is not possible to unload an assembly without unloading all other assemblies it is interacting with. However, as assemblies are loaded in the context of an application domain, unloading the application domain can unload all the assemblies within the application domain. An exception to this case is domain-neutral assemblies (covered shortly). Domain-neutral assemblies are not unloaded until either the process is shut down or the host unloads the common language runtime itself by using ICorRuntimeHost::Stop method.

It should be noted that the default domain will not be unloaded until the process is shut down or the host unloads the common language runtime.

Global Exception Handler

We know that under .NET, an exception thrown from a method, if not caught by any of the callers in the call chain, will result in terminating the application abruptly. Wouldn't it be a boon for forgetful programmers to have a mechanism that will let you catch any uncaught exceptions? You can then either deal with the exception or do some housekeeping work and quit the program more gracefully. Fortunately, .NET provides such a mechanism. You can define an exception handler at the application domain level. Any uncaught exception within the application domain will get caught by this handler. The following code snippet illustrates this mechanism:

// Project AppDomain/ExceptionHandler

class MyApp {
     static void MyExceptionHandler(Object sender,
       UnhandledExceptionEventArgs e) {
       Console.WriteLine(e.ExceptionObject.ToString());
     }

     public static void Main()
     {
       // add an exception handler to the current appdomain
       AppDomain ad = AppDomain.CurrentDomain;
       ad.UnhandledException += new
         UnhandledExceptionEventHandler(MyExceptionHandler);

       // throw an exception
       throw new Exception("Houston! We have a problem.");
     }
}

This program throws an exception that is not caught by any caller on the call chain. However, the program passes the exception to the exception handler, which gracefully writes a message to the console and returns.

Note that the exceptions are caught at the application domain level. If you have more than one application domain within your process, you may wish to register the exception handler for each application domain.

Domain-Neutral Assemblies

Assemblies are loaded within the context of an application domain. If a single application is used by several applications in the same process, by default the common language runtime will load multiple copies of the assembly, one for each domain in which the assembly is used. To maintain isolation, each domain gets its own copy of the user's code and data.

When the common language runtime is being hosted, it is possible to configure the runtime such that the assembly's code (but not its data) can be shared by all domains referencing the assembly. This reduces the amount of memory used at runtime. An assembly whose code is being shared by all domains in the process is said to be domain-neutral.

A host can specify the runtime startup configuration via startupFlags parameter to CorBindToRuntimeEx (Chapter 4). The choices are:

  • STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN: Do not load any assembly as domain-neutral. This setting is commonly used when the host is running just a single application in the process.

  • STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN: Load all the assemblies as domain-neutral. This setting is useful when multiple domains within the process are likely to run the same code.

  • STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN_HOST: Load only the strong-named assemblies as domain-neutral. This setting is useful if the host intends to run a different application in each of the domains. For example, ASP.NET runs many different applications. However, most of these applications are likely to use some common strong-named assemblies such as System.WebForms and System.Data. By using this setting, ASP.NET can optimize the use of shared assemblies.

To decide whether or not to load assemblies as domain-neutral, you must make a tradeoff between reducing memory use and performance. Although domain-neutral code consumes less memory, it runs a bit more slowly. The slower performance is related to the way in which the assembly's static variables and methods are accessed. The common language runtime has to ensure that only the user-code gets shared but not the user-data. To make sure that data doesn't leak across domains, the common language runtime maintains a separate copy of static variables per domain. The common language runtime also maintains tables that map a given caller to the appropriate copy of the static variables. The indirection through these lookup tables causes the code to run more slowly.

It should be noted that the above optimization settings do not affect non-static data and methods. For these fields, performance is affected by how the objects are marshaled across the domains, which we will cover later.

MsCorLib.DLL is Special

In chapter 4, I mentioned that the Base Class Library (BCL) is spread over two assemblies—MSCorLib.DLL and System.DLL. It is interesting to know why the BCL was not placed in just a single assembly.

The common language runtime always loads MSCorLib.DLL as domain-neutral, irrespective of the loader optimization settings. Moreover, the execution engine (MsCorWks.DLL and MsCorSvr.DLL) caches all the entry-points and offsets into MsCorLib's code and metadata. So Microsoft wanted to keep MsCorLib as small as possible. As a result, MSCorLib is not allowed to have references to any assemblies. Any type present in MsCorLib has all its transitive closures present in MsCorLib. For example, MsCorLib implemented type System.String refers to type CultureInfo and Encoding. Therefore, both these types are also defined in MsCorLib. All other BCL types that end up accessing external assemblies, directly or indirectly, were put in System.DLL.


Let's recap what we have learned so far about application domains. A process contains one or more application domains. The application domains can be loaded and unloaded dynamically. Assemblies are loaded and objects are housed within the context of an application domain. Objects from one application domain cannot interact directly with objects from another application domain.

Under the .NET Framework, it turns out that application domains are not the lowest level of isolation. There is yet a finer level of isolation within an application domain. This isolation is provided by an entity called the context.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.126.5