Domain-Neutral Assembly Architecture


As stated, the goal of domain-neutral code is to reduce the overall working set used by a process. To get a general idea of how this goal is accomplished, consider the scenario discussed earlier in which an extensible application loads several of the same .NET Framework assemblies and its host runtime assembly into every application domain. In this situation, only the add-in assemblies are specific to a particular application domain, as shown in Figure 9-1.

Figure 9-1. Extensible applications often load many of the same assemblies into each application domain.


As you can see, separate copies of all the "common" assemblies are loaded into every application domain. This approach results in significant duplication of the same runtime data. For example, each application domain has a copy of the native code that is compiled as the assembly is executed. Clearly, this could be optimized in scenarios in which this code is the same across all application domains. To reduce this duplication, the runtime data for an assembly is shared across all application domains if that assembly is loaded domain neutral. In Figure 9-2, you can see that only a single copy of the .NET Framework assemblies and the host runtime assembly are loaded into the process. The runtime data, including the jit-compiled native code and the CLR data structures used to represent the assembly in memory, is shared among all the application domains in the process.

Figure 9-2. Domain-neutral assemblies are shared among all the application domains in a process.


In many ways, this sharing of runtime data is analogous to the way the operating system shares static code pages for DLLs that are loaded by multiple processes.

It's important to remember that only the runtime data created by the CLR, such as the native code for the assembly, is shared between application domains, not the instances of the types created by the assembly itself. For example, say an assembly contains a method that creates a data structure that maintains a relationship between customer identifiers and instances of Customer objects. Clearly, sharing data such as this across multiple application domains would violate application domain isolation.

Implications of Using Domain-Neutral Code

At this point, domain-neutral code sounds like a panacea. After all, with all the working set savings, why aren't all assemblies loaded domain neutral? Not surprisingly, you should consider some implications of using domain-neutral code when deciding which (if any) assemblies to load domain neutral. These implications, which follow, are primarily related to performance, security, and flexibility:

  • Domain-neutral assemblies cannot be unloaded from a process.

  • Access to static member variables is slower.

  • Initialization of types is slower in some scenarios.

  • The set of security permissions granted to a domain-neutral assembly must be the same for every application domain.

  • If an assembly is loaded domain neutral, all of its dependencies must be as well.

These points are described in more detail in the following sections.

Domain-Neutral Code Cannot Be Unloaded

Because the jit-compiled code and other CLR runtime data for a domain-neutral assembly is used by all application domains within a process, that assembly cannot be unloaded without shutting down the entire process. In most scenarios, the inability to unload a domain-neutral assembly is the biggest downside to using domain-neutral code. The fact that a domain-neutral assembly cannot be unloaded is the primary reason why the add-in assemblies in extensible applications aren't typically loaded domain neutral. Instead, most extensible applications load only the .NET Framework assemblies and the assembly containing the application domain manager as domain neutral. These assemblies are used in every application domain anyway, so the inability to unload them doesn't affect the overall design of the application.

Access to Static Member Variables Is Slower

To preserve the isolation properties of application domains, each domain must have a copy of the assembly's static member variables. Otherwise, a change made to a static variable in one application domain would be visible in all other application domains. Because each application domain has its own copy of static variables, the CLR must maintain a lookup table that maps the application domain in which the static variable is accessed to the correct copy of the variable. Calling through this level of indirection is clearly slower than accessing the variable directly.

Initialization of Types Can Be Slower

When an assembly is loaded domain neutral, any aspect of its execution that might produce different results in different application domains must be factored out and isolated per domain. Access to static variables is an excellent example of this, as described in the previous section. Type initializers (also known as class constructors or static constructors) are another example. The CLR runs a type's initializer in every application domain.

The CLR supports two different semantics for type initializers: precise and relaxed. The semantics for precise type initialization require that the initializer is run before the first access to any of the type's static or instance fields, methods, or properties. In contrast, types that use relaxed initialization are only guaranteed to be initialized before the first access to one of the type's static fields. Implementing precise initialization requires more runtime checks and more levels of indirection to maintain application domain isolation for domain-neutral assemblies, so the initialization of types requiring precise semantics will be slower if an assembly is loaded domain neutral. Even so, it's unlikely that this difference in performance will have a substantial impact on your application unless you rely on type initializers to do significant amounts of work.

Note

The choice of whether a type requires precise or relaxed initialization semantics is up to the language compiler you are using. In general, if a type initializer does nothing but initialize static variables, the compiler can emit a type initializer with relaxed semantics (beforefieldinit in MSIL). However, if a type initializer does more than just initialize static variables, precise semantics is required.


Security Policy Must Be Consistent Across All Application Domains

As I describe in Chapter 10, an application domain might define security policy that affects the set of security permissions granted to assemblies loaded in the domain. When an assembly is loaded domain neutral, the set of security permissions granted to the assembly must be the same for all application domains in the process. Ideally, the CLR would proactively check to make sure the grant set is consistent across application domains and fail to load the assembly if an inconsistency is found. Unfortunately, this is not the case. Instead, differences in the set of granted permissions show up as runtime errors, so it's best to design your security policy at the same time you're thinking about which assemblies to load domain neutral. In Chapter 10, I discuss the details of how to define security policy, including how to make sure your domainneutral assemblies are granted the same set of permissions in all domains.

The Set of Domain-Neutral Assemblies Must Form a Closure

The set of assemblies loaded domain neutral must form a complete closure. That is, all assemblies referenced by a domain-neutral assembly must also be loaded domain neutral. In theory, the CLR could enforce this restriction when an assembly is loaded by proactively locating all of its dependencies and checking whether you've specified they should be loaded domain neutral as well. However, the performance impact of proactively checking an assembly's references in this fashion is prohibitive. Instead, the CLR enforces that the set of domain-neutral assemblies forms a closure at run time by checking each assembly as it is referenced. If a domain-neutral assembly makes a reference to an assembly that is not in the set of domainneutral assemblies, the CLR throws a FileLoadException, so it's up to you to make sure that the assemblies you load domain neutral form a closed set. For this reason, it's generally best to use domain-neutral loading only on assemblies that you can statically analyze ahead of timethat is, the set of assemblies you know you'll load into your application versus the set of add-in assemblies that will be loaded into your application dynamically.

Domain-Neutral Code and Assembly Dependencies

The native code that the JIT compiler produces for an assembly can be shared across application domains only if that code is the same as the code that would be produced if the assembly were compiled in each application domain separately. An assembly's references influence the native code that is generated because the JIT compiler must emit code to call from one assembly to another. The native code for a given assembly can be shared across application domains only if that assembly has exactly the same set of assembly references in every application domain. An assembly's dependencies are statically recorded in metadata when the assembly is compiled. Those references will obviously be consistent in all application domains, but remember that an application domain might contain version policy that can cause a different version of a dependency to be loaded at run time, so any version policy that can affect the dependencies of a domain-neutral assembly must be the same in all application domains.

Each time an assembly you've specified as domain neutral is loaded into an application domain, the CLR checks to make sure that its set of dependencies is consistent with the other application domains already in the process. If an inconsistency is found, the native code for that assembly cannot be shared with the application domain in which the assembly is loaded. Instead, the CLR will generate a new copy of the native code specifically for that application domain. For example, consider a scenario in which you've specified that an assembly named Statistics should be loaded domain neutral. Statistics depends on an assembly called Probability. Statistics and version 5.0.0.0 of Probability have already been loaded by two application domains, as shown in Figure 9-3.

Figure 9-3. Native code can be shared only when an assembly's dependencies are consistent.


In this case, the native code for Statistics can be shared because Statistics references the same version of Probability in both application domains. (I'm assuming all of the other dependencies for Statistics are the same as well.) Now add another application domain to the scenario. This new application domain has version policy that redirects all references to Probability from version 5.0.0.0 to version 6.0.0.0. Because Statistics has a different set of dependencies in this new application domain, the native code that was previously generated in the process cannot be used. This situation is shown in Figure 9-4.

Figure 9-4. Native code cannot be shared if an assembly has a different set of dependencies.


In Figure 9-4, you can see that a separate copy of the native code for Statistics has been created for use by the new application domain. As a result, you've lost the working set savings associated with sharing native code across application domains. However, there's another downside to this situation as well. Because Statistics was specified to be domain neutral, it has all the properties of a domain-neutral assembly except the code sharing. That is, static variable access and type initialization will still be slower, security policy must be consistent with the other domains in the process, and so on. You can see in Figure 9-4 that even though the native code cannot be shared, a separate copy of the static variables of Statistics has been made in the new domain. In short, you've ended up with a worst-case scenario: not only have you lost the working set savings that you hoped to gain by loading Statistics domain neutral, but you are still left with the limitations associated with domain-neutral code.

Fortunately, you can avoid this situation by making sure that the version policy associated with the application domains in your process is consistent for those assemblies you wish to load domain neutral. The easiest way to ensure your version policy is consistent is to use an application domain manager to hook all calls to AppDomain.CreateDomain, as discussed in Chapter 6.



    Customizing the Microsoft  .NET Framework Common Language Runtime
    Customizing the Microsoft .NET Framework Common Language Runtime
    ISBN: 735619883
    EAN: N/A
    Year: 2005
    Pages: 119

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net