Automated Coder

Exploring the Code of CruiseControl.Net

Some ASP.NET Magic

Posted by Craig Sutherland on 10 March, 2009

Why ASP.NET?

ASP.NET is kind of special. We write an ASP.NET application and set everything up. Then, later on we write some more code, compile it and re-deploy it. Now the magic comes in with the way it is deployed – we can just copy the new DLLs over the old DLLs. And that’s it – no problems, no worries.

Now, try this with a Windows application – ERROR! What happens is we get an error message saying the files are locked and we can’t overwrite them. So what is different, and can we use it in CruiseControl.Net?

The simple answer is YES! :-)

Introducing AppDomains

In a Windows environment the smallest unit of execution is a process (yes, there are threads, but they still require a process to contain them). When a Windows application starts up, it starts a process, which then loads all the code and is away running. Native Win32 applications do have the potential to load and unload libraries, but not managed applications (yes, there are good reasons for it, just don’t ask me).

.NET introduces a new, smaller unit of execution – an AppDomain. An AppDomain is included inside a process, but there can be multiple AppDomains per process. An AppDomain is very similar to a process – the application starts an AppDomain, loads the code and then runs. But, there is one major difference – it is possible to unload an AppDomain without terminating the process.

Unbeknownst to a lot of people, every .NET application uses an AppDomain. When the application starts up, the first thing it does (other than the Win32 start-up stuff) is create an AppDomain and load the starting point (usually Main() or something similar). So, we don’t need to make any changes to use an AppDomain in our code!

The Magic of ASP.NET

Now I’m not an expert on the inner workings of ASP.NET, but here is what I understand of it. When an ASP.NET application starts up, the ASP.NET ISAPI filter starts a new AppDomain for it. This AppDomain has a special property set to allow shadow copying (more on this in a minute). Once the AppDomain is started, it loads the required DLLs (after any compilation) and then starts the ASP.NET framework within the AppDomain.

It also attaches a file watcher to the folder as part of the start-up. When the file watcher triggers because some important files have been changed (I’m not exactly sure what is covered), it fires up a new AppDomain and routes all new requests to that AppDomain. Once all requests on the old AppDomain are finished, it unloads the AppDomain. This provides a nice continuation of service to any clients.

Now, the reason why the libraries are not locked is because of the shadow copy property. When this property is set, the AppDomain doesn’t directly use the DLLs. Instead it first copies them to a cache location, and then uses the copied DLLs. Thus the original DLLs are not locked – the copied ones are instead.

Bringing the Magic to CruiseControl.Net

It would be nice to add “hot swapping” to CruiseControl.Net. This would simplify any deployments and open up a few possibilities for improvements. And using AppDomains it is very easy to do.

This is how I’ve done it.

First, there are two applications in CruiseControl.Net that need this functionality – Console and Service (ccnet.exe and ccservice.exe). These are the hosting applications for Core, Remote and the ancillary libraries (e.g. NetReflector, Log4Net, etc.)

Currently, when these applications start, they directly load the required classes and run them. What I’ve done is separate this functionality and put it into a new class called AppRunner. AppRunner inherits from MarshalByRefObject, so it can make cross-boundary calls (e.g. between AppDomains).

The start-up points in the applications start a new AppDomain and create an instance of AppRunner within the new AppDomain. The new domain has the shadow copy property set, so any assemblies loaded into it will be cached first. The create call returns a reference to the instance (which is actually a proxy reference), so we can call the new instance, in the new AppDomain, from the old AppDomain. The instance in the new AppDomain does all the calls to Core and Remote, so these libraries are only ever loaded into the new AppDomain.

Next, I added a FileSystemWatcher, and set it to monitor changes to ThoughtWorks.CruiseControl.Core.dll (this is the main DLL). When this file changes, it calls Stop() on AppRunner and then unloads the AppDomain. Then it loops back to starting a new AppDomain, creating a new instance of AppRunner, etc.

So, long story short – the Core, Remote and ancillary DLLs are no longer locked. When Core is replaced it will shut down the old AppDomain and start up a new one, thus loading the new DLLs.

Simple :-)

Possibilities

This now opens some future possibilities.

First, it is no longer necessary to shut down the service to deploy a new version of CruiseControl.Net. Instead, just copy over the new DLLs, making sure that ThoughtWorks.CruiseControl.Core.dll is the last one copied. This also allows us to add an automatic deployment process in a farm scenario. In this scenario one server would be the master copy – changing this copy would push the new DLLs out to the other instances (hopefully automatically).

Secondly, we can add some more robustness to the application. If a fatal error occurs in an AppDomain (i.e. something completely unexpected), then we can unload the old AppDomain and fire up a new one. Of course we’d need to be careful we don’t enter an endless loop (e.g. crash, restart, same error occurs and the cycle begins again).

Finally we can add some AppDomain recycling. This is something like the application pool recycling in IIS, where an AppDomain can be unloaded every certain amount of time and then restarted. Again, needs a little bit of planning to make sure we do this in a safe way.

Anyway, these are some ideas I’m thinking of for the future, feel free to let me know if there is any other possibilities I haven’t thought of :-)

One Response to “Some ASP.NET Magic”

  1. [...] there. The reason this works, is the DLLs are shadow-copied (more details on this are available here). This is where the fun starts – in order to make this work, we needed to make some cross-domain [...]

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <pre> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>