Code defensively: Continuously check for corrupted installation

A common
problem when an application goes to production comes from deployment issues.
Concretely, your client’ admin, your installation script or any other actor
that can access the production machine can potentially mess up and corrupt the installation. If you are lucky, your code will likely crash by raising an
explicit exception such as FileNotFoundException (because some assemblies
are missing or have a wrong version) or such as
MissingMethodException (because there is a versioning issue). If you are
unlucky, your code will crash because of a versioning issue with a random
exception that has nothing to do with deployment. Hence, you won’t likely think
to check for deployment versioning issue and will spend precious time in vain, trying
to exhibit a bug that doesn’t exist.

 

The first
defensive step to prevent such problem is to sign your assemblies. When an assembly is
signed, it gets a strong name that will contain the version of the assembly.
The version of an assembly is set by tagging your assembly with the
System.Reflection.AssemblyVersionAttribute such as:

 

[assembly: AssemblyVersion("2.3.0.1092")]

 

Let’s say that
A is an assembly signed and versioned. If the assembly B references A, the
strong name of A is hard coded in the assembly manifest of B (no matter B is
signed or not). At runtime when the execution of B needs A, the CLR will look
for A taking account of the A strong name. In other words, if B references the
version 1 of A, the CLR won’t load another version of A than version 1, even if
A version 2 can be found. If A version 1 cannot be found, the CLR will then
raise a
FileNotFoundException. This safe behavior strongly advocates for
signing your assemblies to detect corrupted installation. Unfortunatly, I often see teams only signing assemblies that must be installed in the GAC because the GAC don’t accept unsigned assembly.

 

While
developing and supporting NDepend, we experienced the hard way that this
cool CLR behavior was not enough to anticipate thoroughly the problem of corrupted
installation.

  • First, not
    all of our executable assemblies use all our library assemblies, meaning that a
    corrupted installation can run seamlessly as long as the wrong versioned library
    assemblies are not involved.
  • Second, as
    the CLR loads the referenced assemblies on demand, the
    FileNotFoundException
    can be raised at any time. Because of the Murphy law, anytime often mean when
    it will highly disturb the user, provoking a loss of data.
  • Third, our
    installation is spawned on 2 folders one for the executable assembly and one
    for the library assemblies. It allows our users to focus only on executable
    assemblies. We implemented a mechanism with the AppDomain.AssemblyResolve event and the
    Assembly.LoadFrom() method to load manually our library assemblies. The method
    Assembly.LoadFrom() takes a file path as parameter and not a strong name. Thus,
    a wrong versioned assembly library could be loaded even if it is signed.

We then did
some code that checks that all assemblies are present in their respective
folders with the correct version. This check is triggered anytime an executable
assembly is started. If the check fails, a popup window appears, describing
in plain english the corrupted installation problem and advice the user to
download the latest available version.

 

We didn’t
use System.Reflection to check for assemblies’ versions because
System.Reflection
forces to load the entire assembly in-memory in order to get its version and
once loaded into an appdomain, an assembly cannot be unloaded. That’s quite a
high price to pay to just get the version, especially taking account of the
fact that not all exes depends on all assemblies. Instead we rely on the open-source Mono.Cecil
framework (developed by Jb Evain). Here is the piece of code that just loads an
assembly manifest and gets its version:

 

 

Mono.Cecil.AssemblyDefinition
assemblyCecil = Mono.Cecil.AssemblyFactory.GetAssemblyManifest(“C:\MyDeploymentPath\MyAssembly.dll”);

System.Version version = assemblyCecil.Name.Version;

 

 

Internally,
this code triggers the load of the entire assembly’ module(s)’ image(s) in memory because
Cecil checks for the assembly correctness. However there is no JIT compilation
and fusion checks. More importantly, the module(s)’ image(s) raw data is garbage
collected once the AssemblyDefinition corresponding object gets garbage
collected. Practically, the loading time remains acceptable and there is no
wasted memory.

 

As a bonus,
we also continuously check that the assembly strong names hardcoded
in the assembly manifests are correct. Indeed, we also experienced buggy build
process that references wrong version of referenced assemblies (like Professional
version that references Community version, sounds familiar?). Here is the code
skeleton to get assemblies references:

 

Mono.Cecil.AssemblyNameReferenceCollection
assembliesReferenced =

            assemblyCecil.MainModule.AssemblyReferences;

 

foreach (Mono.Cecil.AssemblyNameReference
assemblyNameReference in assembliesReferenced)
{             

  
System.Version version = assemblyNameReference.Version;

}

This entry was posted in Uncategorized. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://weblogs.asp.net/fmarguerie Fabrice

    It’s related to build-time and not run-time, but the “Specific Version” property on references in Visual Studio can be used to ensure that VS builds with the exact version of an assembly that you expect.
    See “Specific Reference Version” in this article: http://www.code-magazine.com/article.aspx?quickid=0507041&page=3