Products

Solutions

Resources

Partners

Community

About

New Community Website

Ordinarily, you'd be at the right spot, but we've recently launched a brand new community website... For the community, by the community.

Yay... Take Me to the Community!

The Community Blog is a personal opinion of community members and by no means the official standpoint of DNN Corp or DNN Platform. This is a place to express personal thoughts about DNNPlatform, the community and its ecosystem. Do you have useful information that you would like to share with the DNN Community in a featured article or blog? If so, please contact .

The use of the Community Blog is covered by our Community Blog Guidelines - please read before commenting or posting.


Module Installation, IUpgradeable and Application Recycling

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.

Comments

logpop
so is this fixed by now? and why did you keep it in if it isn't working?
logpop Tuesday, October 13, 2015 4:29 PM (link)

Comment Form

Only registered users may post comments.

NewsArchives


Aderson Oliveira (22)
Alec Whittington (11)
Alessandra Daniels (3)
Alex Shirley (10)
Andrew Hoefling (3)
Andrew Nurse (30)
Andy Tryba (1)
Anthony Glenwright (5)
Antonio Chagoury (28)
Ash Prasad (37)
Ben Schmidt (1)
Benjamin Hermann (25)
Benoit Sarton (9)
Beth Firebaugh (12)
Bill Walker (36)
Bob Kruger (5)
Bogdan Litescu (1)
Brian Dukes (2)
Brice Snow (1)
Bruce Chapman (20)
Bryan Andrews (1)
cathal connolly (55)
Charles Nurse (163)
Chris Hammond (213)
Chris Paterra (55)
Clint Patterson (108)
Cuong Dang (21)
Daniel Bartholomew (2)
Daniel Mettler (181)
Daniel Valadas (48)
Dave Buckner (2)
David Poindexter (12)
David Rodriguez (3)
Dennis Shiao (1)
Doug Howell (11)
Erik van Ballegoij (30)
Ernst Peter Tamminga (80)
Francisco Perez Andres (17)
Geoff Barlow (12)
George Alatrash (12)
Gifford Watkins (3)
Gilles Le Pigocher (3)
Ian Robinson (7)
Israel Martinez (17)
Jan Blomquist (2)
Jan Jonas (3)
Jaspreet Bhatia (1)
Jenni Merrifield (6)
Joe Brinkman (274)
John Mitchell (1)
Jon Henning (14)
Jonathan Sheely (4)
Jordan Coopersmith (1)
Joseph Craig (2)
Kan Ma (1)
Keivan Beigi (3)
Kelly Ford (4)
Ken Grierson (10)
Kevin Schreiner (6)
Leigh Pointer (31)
Lorraine Young (60)
Malik Khan (1)
Matt Rutledge (2)
Matthias Schlomann (16)
Mauricio Márquez (5)
Michael Doxsey (7)
Michael Tobisch (3)
Michael Washington (202)
Miguel Gatmaytan (3)
Mike Horton (19)
Mitchel Sellers (40)
Nathan Rover (3)
Navin V Nagiah (14)
Néstor Sánchez (31)
Nik Kalyani (14)
Oliver Hine (1)
Patricio F. Salinas (1)
Patrick Ryan (1)
Peter Donker (54)
Philip Beadle (135)
Philipp Becker (4)
Richard Dumas (22)
Robert J Collins (5)
Roger Selwyn (8)
Ruben Lopez (1)
Ryan Martinez (1)
Sacha Trauwaen (1)
Salar Golestanian (4)
Sanjay Mehrotra (9)
Scott McCulloch (1)
Scott Schlesier (11)
Scott Wilkinson (3)
Scott Willhite (97)
Sebastian Leupold (80)
Shaun Walker (237)
Shawn Mehaffie (17)
Stefan Cullmann (12)
Stefan Kamphuis (12)
Steve Fabian (31)
Steven Fisher (1)
Tony Henrich (3)
Torsten Weggen (3)
Tycho de Waard (4)
Vicenç Masanas (27)
Vincent Nguyen (3)
Vitaly Kozadayev (6)
Will Morgenweck (40)
Will Strohl (180)
William Severance (5)
What is Liquid Content?
Find Out
What is Liquid Content?
Find Out
What is Liquid Content?
Find Out