DNN Blog

Oct 14

Posted by: Joe Brinkman
10/14/2005 7:00 AM  RssIcon

Have you ever written a piece of code that seems to perform correctly during testing, gets deployed and then 6 months later when people really start using it, you find out that there is a fundamental flaw that makes your whole design completely worthless?  That is the situation I find myself experiencing now. 

Module Installation

The module installation process is fairly mature by DotNetNuke standards and is based on earlier work by Jonathan de Halleux which was contributed to the core. The installer code was written as part of the DNN 2.0 implementation and was a major new feature provided in this release.  This installation process unzips the PA in memory, parses the .dnn manifest file and then moves any assemblies into the bin directory.  There are lots of other bits going on, but these are the key processes for the purposes of this discussion.  This has worked well for almost two years with very little change.

IUpgradeable

As part of the 3.0 development effort we decided that we would support module notification during this process so that the module could run any custom business logic to initialize the module for the current version.  This feature was desperately needed since many modules need to initialize directory structures, remove legacy files or folders, update internal configuration files or any other number of custom processes which falls outside of the ability to handle within the DataProvider scripts.

This new feature was implemented as an Interface.  Any module which needed this notification service would implement the IUpgradeable interface which defines the UpgradeModule method.  After all of the dlls have been saved to the \bin directory and the module has completed the registration process, then the core will call the upgrade method for every script that was installed as part of the installation process.  On a new install, this means every script up to and including the current version.  On upgrades, this method is only installed for the scripts that were executed to bring the installation up to the current version.

This is the point where the astute reader says "Hey!  I thought IIS performs an app recycle when a new dll is placed in the \bin directory.  How can this work?"  Sure, you say that now.  Where were you when I was developing this code?  This is a case where sometimes you can be too close to the code to see the big picture and notice potential problems.

Now, in my defense, I will say that it has taken just shy of one year to uncover a problem.  I did my usual unit testing and everything worked perfectly.  I could install a module and the method was called correctly.  This made it through internal testing and beta testing without any problems noted.  This has been used on production sites and things seemed to work ok.  But wait!  I thought we just said that when an assembly is added to the \bin directory that IIS recycles the worker process.  Shouldn't this have caused problems?  That is correct.  At least someone is paying attention.

But there are problems and there are problems.  Sometimes problems are so subtle that they can go a long time without detection.  If you don't believe me just try writing a multi-threaded application and see how many problems only show up under very limited situations.

So lets examine what exactly is going on and why it took so long to uncover this issue.

Application Recycle

The key to understanding why this problem occurs and why things often seem to work is to understand what exactly happens during an app recycle and to understand why the application is recycled. 

In IIS (we'll ignore the differences between IIS 5 and 6 for now) every ASP.Net application is loaded into an application domain.  This application domain is the context under which your application will run.  Any assemblies that are accessed by the application are loaded into the app domain as they are called.  If an assembly is never called, then it will not be loaded, even it is in the \bin directory.  But if IIS loaded these assemblies directly then the dlls in the \bin directory would be locked and you would not be able to install new versions without first shutting down the process that loaded the dll, thereby unlocking the dll.  The .Net team implemented a feature called shadow copying which allows a dll to first be copied to a temporary location and then the copy is loaded and executed from the temp directory.  ASP.Net makes use of this feature to allow assemblies to be updated without stopping the worker process.

When a new assembly is placed in the \bin directory IIS receives an event which says that an assembly was changed.  Since .Net does not allow you to unload assemblies you must unload the entire app domain in which the assembly is loaded.  This means shutting down your application.  Then a new app domain can be created which could then load the new dlls if needed.  But wouldn't that abort your current request?  Boy, you are pretty sharp!  But then again, so was the ASP.Net development team.  That is why they allow the existing app domain to finishing servicing all of the requests that are currently in processing.  A new app domain is loaded to handle any new requests from that point forward.

What this means is that when we install the new assemblies into the \bin directory, the current app domain will be marked for destruction and all new requests will be given to a new app domain once it has finished being initialized.  The current request which is still processing the module installation request will be allowed to continue through to completion.  So where is the problem?  Your installation process gets to finish so everything should work.

Lets look at a couple of different scenarios to see why this only fails part of the time.

The first case is a new installation.  In this scenario, your module assembly did not exist previously.  When the module installation code gets to the step to see you implement IUpgradeable, it will not find the assembly in memory and will be forced to load the assembly from disk.  You guessed it.  It will load the only assembly that is available.  The one that was just installed to the \bin directory.  If you implemented IUpgradeable the code will see that your BusinessControllerClass (as indicated in the .dnn manifest) is a Type Of IUpgradeable and will call your UpgradeModule method.  Everything works as you would expect. 

The second case is an upgraded installation.  In this scenario you are running a low volume site or you have recently had an app recycle.  The module being upgraded has not been used since the last app recycle and therefore the assembly is not in memory.  Just like in the first scenario, the installation proceeds as expected and the upgrade works correctly.

The third case is also an upgraded installation.  In this scenario your module implemented IUpgradeable in the earlier version, and is adding some new business logic to the UpgradeModule method for the new version.  Because you have a really popular site with thousands of users visiting you every day, your site never reaches the 20 minute idle point which might trigger an app recycle.  Also, the module you are upgrading is on one of the most popular pages on the site.  As you probably guessed by now (you are the astute reader after all), a shadow copy of your assembly is already loaded into memory.  This is the original version from before the installation process placed a new copy in the \bin directory.  When the installer checks, it finds that your module does in fact implement the IUpgradeable interface.  Like a good application, the installer calls your UpgradeModule method.  Unfortunately you forgot to include your new business logic in the old assembly.  Pretty shortsighted of you.  I try to include code for anything I think I might someday need.  (Okay.  You're right.  That is just plain dumb, but you can't blame a guy for trying.)  So when the UpgradeModule method is called, your new business logic for the new module version does not exist and nothing happens.  The process functions without any "errors" but the code does not get executed as you expected.  Not good.

The las case is a variation of the previous scenario.  In this instance your original module did not implement IUpgradeable.  But people really love the module and it is always in memory.  When the installation occurs the installer tries to determine if your module implements IUpgradeable.  Because it will look at the version that is in memory it will not find the interface implemented and therefore it will not call the UpgradeModule method.  Again, not what you wanted.

The Solution

So all in all, not too bad.  The code works half of the time.  As long as you are running a site that no one visits then it will almost always work correctly.  If you are running a really popular site you have a couple options.  You could try handing out bogus URLs in the hopes that all of your users will go somewhere else and allow you to upgrade in peace.  Some of you I am sure are pretty stubborn and will not choose to use this perfectly acceptable solution.  In that case I guess we just might need to implement a different mechanism for providing this key service.

This is going to take a pretty substantial effort to try and work around the app recycle issue (are you sure you don't want to hand out some other URLs?  I have a couple spares handy.).  For now, I would recommend that all module developers cease relying on the IUpgradeable interface until we get it re-architected.

Once again it just proves that software developers wouldn't have any bugs if we could just get rid of those pesky customers.

Tags:
Categories:
Location: Blogs Parent Separator Joe Brinkman

12 comment(s) so far...


Re: Module Installation, IUpgradeable and Application Recycling

Outstanding explanation of this issue Joe! The whole concept of shadow copies and app domains is really not well understood by most .NET developers. I would like to throw out one more wrinkle which jumped to mind as I read your post...

Shadow copy is an interesting feature because it allows worker processes to complete their work using the old assembly - while new requests are serviced by the new assembly. But what if the logic within the assembly is dependent on other services? For example a database. A database certainly does not have the concept of a shadow copy. So lets say that the new assembly changed the database schema in some way when it was installed. Meanwhile the old assembly is busily trying to complete the old requests - assuming the database schema is in the old format. I see a problem emerging here. The old assembly may encounter errors in trying to access the database. A database is certainly not the only service which could encounter this scenario - but it is the most common in a business application. So what is the likelihood of this happening? Probably not very high... in fact, I can only see an issue with long running processes like scheduled jobs ( where the old assembly may be executing for quite some time before the job completes ). This is just something else to ponder...

By sbwalker on   10/14/2005 1:15 PM

Re: Module Installation, IUpgradeable and Application Recycling

Nicely put. In a similar vein I'm reminded of some code I was debugging as a junior programmer. I could not work out why a piece of code was no longer working when it had been live and unchanged for more than two years. Of course!! ... that particular little section had never been executed before ... it was probably my first lesson in "never assume".

By forest on   10/14/2005 1:18 PM

Re: Module Installation, IUpgradeable and Application Recycling

In response to If you are running a really popular site you have a couple options. You could try handing out bogus URLs in the hopes that all of your users will go somewhere else and allow you to upgrade in peace looks like ASP.NET 2.0 will have another option to let you upgrade in peace.

By jhenning@solpart.com on   10/14/2005 10:02 PM

Re: Module Installation, IUpgradeable and Application Recycling

That was a great read Joe. Informative but with just enough humor so as to not be overly dry.

John, I really like that 2.0 option. Great idea by MS.

By mikeh on   10/15/2005 1:29 PM

Re: Module Installation, IUpgradeable and Application Recycling

I would suggest to build the installation into 2 steps.



The first step would copy all dlls into the \bin folder, and then disply the result back with an >META http-equiv="REFRESH" CONTENT="5; URL=default.aspx?phase=2"< or an AJAX callback to initiate the second pahse of the installation which now will be handled by the newly loaded app domain and garantiues that a new call to IUpgradable is handled by the newly uplaoded module controller.

By fatoot on   10/17/2005 5:34 AM

Re: Module Installation, IUpgradeable and Application Recycling

A meta refresh or AJAX style call-back may work, but I will need to do some research. One thing to note is that a new app domain is created for every assembly copied to the directory. So depending on timing, the call-back could catch one of the intermediate app domains. All in all a very sticky puzzle.

By jbrinkman on   10/17/2005 7:11 PM

Re: Module Installation, IUpgradeable and Application Recycling

Jon,
I don't think the app offline solution will work because it short circuits all ASP.Net processing. What we need is to short circuit all non-admin processing in most cases, because the admin is still using DNN (an ASP.Net application) to perform the maintenance/upgrade.

By jbrinkman on   10/17/2005 7:15 PM

Re: Module Installation, IUpgradeable and Application Recycling

Another, related issue is the scheduler in DNN. I believe that if an alternative Windows .NET threaded Service that could be installed Web Server were developed, we could avoid many of these issues. The threaded service would actually be performing the scheduling and module upgrade invoke.

By favance on   1/17/2006 11:20 AM

Re: Module Installation, IUpgradeable and Application Recycling

Site Sentinel is a windows service, external to DNN, that will ping your site regularly to reduce the negative effects of the IIS recycle problem.
http://www.coderocks.com/dnn/Products/SiteSentinel/tabid/107/Default.aspx

By coderocks on   1/18/2006 2:23 PM

Re: Module Installation, IUpgradeable and Application Recycling

Great article Joe! It explains a problem I am having while trying to upgrade the existing Events module with the new AVCalendar version (renamed to DNN Events). I am running into the problem you describe, since the previous Events module (3.1) did not implement the IUpgradeable Interface...Any work-arounds or suggestions? What's the best practice for module developers? thanks!

By favance on   3/15/2006 8:59 AM

Re: Module Installation, IUpgradeable and Application Recycling

Hmmm. Is there a way to leverage DNN's scheduler by having a one-time scheduled call to the module's IUpgradable on re-start of the application? Perhaps this can be combined with the App_Offline.htm.

The processing could go like this:
0) Module installation (as "normal")
1) Add a scheduled event to occur on app-start that will call the specific IUpgradable method(s) on newly installed module(s)
2) Lastly, do a file rename of "App_Offline.htm.resources" to "App_Offline.htm"
3a) Then, a manual change is made to re-name "App_Offline.htm" back to "App_Offline.htm.resources"
OR
3b) App_Offline.htm could have AJAX-style code to call a server-side routine to do the rename of "App_Offline.htm" back to "App_Offline.htm.resources"


As another "out-there" kind of thought, what about making the Install folder an IIS app in itself? Could that be a way to isolate the whole install process from the regular workings of the DNN application?



Just some thots (with flaws, I'm sure) ...

By dagilleland on   5/23/2006 6:34 PM
Gravatar

Re: Module Installation, IUpgradeable and Application Recycling

Hi Joe,

I was just about to use the IUpdatable interface for the first time in one of my applications, and I stumbled upon this blog entry. It's now 2008, 3 years after your original posting. Is this still an issue in DotNetNuke, it would be great to post if there has or has not been a resolution so that others like me that stumble on this thread know where things stand...

Thanks!

Matt Christenson

By Matt Christenson on   3/6/2010 9:08 AM
Attend A Webinar
Free Demo Site
Download DotNetNuke Professional Edition Trial
Have Someone Contact Me
Have Someone Contact Me
DotNetNuke Store

Like Us on Facebook Join our Network on LinkedIn Follow DNN Corporate on Twitter Follow DNN on Twitter

Advertisers

PowerDNN
r2integrated
Telerik JustCode Free

Sponsors

DotNetNuke Corporation

DotNetNuke Corp. is the steward of the DotNetNuke open source project, the most widely adopted Web Content Management Platform for building web sites and web applications on Microsoft. Organizations use DotNetNuke to quickly develop and deploy interactive and dynamic web sites, intranets, extranets and web applications. The DotNetNuke platform is available in a free Community and subscription-based Professional and Enterprise Editions with an Elite Support option. DotNetNuke Corp. also operates the DotNetNuke Store where users purchase third party apps for the platform.