CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

Patrick Smacchia [MVP C#]


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;

...

}



Comments

Fabrice said:

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: www.code-magazine.com/article.aspx

# July 20, 2007 5:42 AM

Leave a Comment

(required)  
(optional)
(required)  

Enter the numbers above:
Add

About Patrick Smacchia

Patrick Smacchia is a Visual C# MVP involved in software development for over 15 years. After graduating in mathematics and computer science, he has worked on software in a variety of fields including stock exchange, airline ticket reservation system as well as a satellite base station at Alcatel. He's currently a software consultant and trainer on .NET technologies as well as the lead developer of the tool NDepend which provides numerous metrics and caveats on any compiled .NET application. He is the author of Practical .NET2 and C#2, a .NET book conceived from real world experience with 647 compilable code listings. Check out Devlicio.us!