Creating scalable applications using the CmsApplicationContext

MCMS context objects are not really designed for long running scenarios. Usually long running application need to take care on releasing unused resources and do housekeeping. Unfortunatelly MCMS context objects do not have such features implemented. Allocated resources are only released when the MCMS context is destroyed.

From a historical point of view this is understandable: with MCMS 2001 the lifetime of a MCMS context object was very limited as it was created when an http request arrived at the MCMS server machine and was automatically destroyed when the response for the request was sent to the client. So housekeeping was not really necessary.

With the CmsHttpContext of MCMS 2002 this is very similar: the CmsHttpContext gets automatically created on first access of the CmsHttpContext.Current object. And the CmsEndRequestModule registered in the web.config is the component that ensures that the context is destroyed after the response got sent to the client.

Problems arrise when the CmsApplicatonContext is being used. This context is not automatically destroyed! You need to explicitly call the Dispose() method to destroy the context object. If the context was created in a template file and the Dispose() methods is not called at the end of the template code the object will live till the garbage collector cleans up old objects and implicitly calls Dispose(). A common results of not properly disposed CmsApplicationContext objects are Server ODBC error messages.

Another problem that can arise – especially with Console Applications – is that the virtual memory consumption will go up with any repository item being accessed using this context because allocated resources are not automatically destroyed. This affects applications enumerating the MCMS repository with a single CmsApplicationContext.

The memory required to store all enumerated channels, postings, placeholders and property will be summed up and kept allocated to the CmsApplicationContext object. For small repositories with only some hundred MB database size this is not really critical. But imagine the impact running the same tool against a MCMS repository with several Gigabyte in size. The address space of the application will use more and more memory. At a point when available memory becomes less and less most processing time will be used by Garbage Collector trying to clean up space. Actually only space not bound to the CmsApplicationContext will be free by the garbage collector as the CmsApplicationContext is still in use. At the end the application will die with an Out-Of-Memory exception.

Code that shows this kind of problem usually looks similar to this:

static private void CollectChannel(Channel channel)
{
    …
    foreach (Posting po in
channel.Postings)
    {
        CollectPosting(po);   // do some stuff with the posting object
   
}

    foreach (Channel ch in channel.Channels)
    {
        CollectChannel(ch);
    }
    …
}

[STAThread]
static void Main(string[] args)
{
    // Get the CMS application context
    CmsApplicationContext cmsContext = new CmsApplicationContext();
    cmsContext.AuthenticateAsCurrentUser(PublishingMode.Unpublished);

    CollectChannel(cmsContext.RootChannel);
}

As you can see in the sample above the same CmsApplicationContext object is used to access all channels and postings (and potentially all properties or placeholders) in the MCMS repository. So this type of application is not really scalable.

But how can we overcome this limiation if we really need to enumerate the whole MCMS repository? The answer is to keep the lifetime of the CmsApplicationContext as short as possible and to ensure that it gets properly disposed. To ensure that the CmsApplicationContext gets properly disposed there are two possible methods:

  1. ensure that the Dispose() method is explicitly called after you finished using it:

    CmsApplicationContext cmsContext = new CmsApplicationContext();
    cmsContext.AuthenticateAsCurrentUser(PublishingMode.Unpublished);
    … work with the context…
    cmsContext.Dispose();

  2. to use the using statement with the context. This will ensure that the Dispose method will be implicitly called at the end of the using statement:

    using (CmsApplicationContext cmsContext = new CmsApplicationContext())
    {
       cmsContext.AuthenticateAsCurrentUser(PublishingMode.Unpublished);
       … work with the context…
    }

The result of both methods is the same: the context is properly disposed at the end. But I personally prefer and would recommend to use the second method as it will ensure that it is not possible to forget to call the Dispose() method. In addition due to the structuring of the using statement the lifetime of the context can easily be seen by someone reading the source code.

Ok, but how will we now rewrite the enumerating code to ensure that the context is properly disposed, that the lifetime is small and that still all objects get enumerated?

Here is the solution (changed lines in bold):

// use a stack to store the GUIDs of all channels we have to recurse into
private
static Stack StackItems = new
Stack();

static private void CollectChannel()
{
    …
    while (StackItems.Count > 0)
    {
       
using (CmsApplicationContext cmsContext = new CmsApplicationContext())
        {
            cmsContext.AuthenticateAsCurrentUser(PublishingMode.Unpublished);
           
string channelGuid = (string
)StackItems.Pop();
            Channel channel = cmsContext.Searches.GetByGuid(channelGuid)
as
Channel;

            
foreach (Posting po in
channel.Postings)
            {
                CollectPosting(po);   // do some stuff with the posting object
           
}

            foreach (Channel ch in channel.Channels)
            {
                StackItems.Push(ch.Guid);
            }
            …
        }
        // lets explicitly call the garbage collector to keep memory consumption small
        System.GC.Collect();
    }
}

[STAThread]
static void Main(string[] args)
{
    using (CmsApplicationContext cmsContext = new CmsApplicationContext())
    {
        cmsContext.AuthenticateAsCurrentUser(PublishingMode.Unpublished);
       
string channelGuid = cmsContext.RootChannel.Guid;
        StackItems.Push(channelGuid);
    }


    CollectChannel();
}

As you can see in the sample above during the lifetime of the CmsApplicationContext object only the direct childs of the current channel are accessed. So the number of required resources is limited. As we call the garbage collector explicitly we can ensure that the allocated space is released before new memory is allocated by creating the next instance of the CmsApplicationContext. Beside that we are using a stack to collect the GUID values of the channels we have to look at which allows us to get rid of the recursion code.

11 Comments


  1. Fantastic article, it certainly appears more streamlined and would be much less intensive to memory.

    However, if I understand this code properly; it will not allow you to maintain the channel/posting hierarchy the way recursion does, or does it?

    Reply

  2. Actually it is more or less the same method as with iteration. The Stack simulates the recursion in an iterative algorithm.

    You could easily adapt any recursive algorithm using this approach.

    Reply

  3. I’ve been running into the memory issues with CmsApplicationContext myself, and switching to this approach worked great.

    Reply

  4. Hey Stefan,

    Due to the huge number of authors on our site we’re trying to write a script that will allow us to collect all the postings on a site who have a locking owner and release the ownership so that those pages can be edited by more than one author.

    We’ve adapted your code above to traverse the site and build a PostingCollection of all of the postings in question. However, eventhough we have the script authenticating as a site admin, we get insufficient rights errors when trying to pull individual postings put of the collection. Can you shed any light on this issue?

    Code snip:

    using (CmsApplicationContext ctxAdmin = new CmsApplicationContext()) {

    ctxAdmin.AuthenticateAsUser("WinNT://Domain/adminuser", "password", PublishingMode.Update);

    PostingCollection colPostings = GetPostings(bIsRecursive);

    foreach (Posting p in colPostings)

    {

    p.ReleaseOwnership();

    }

    }

    Reply

  5. This weeks are received two questions caused by the fact that the difference between CmsHttpContext.Current.Mode…

    Reply

  6. This week I received two questions on confusion caused by the fact that the difference between CmsHttpContext.Current.Mode…

    Reply

  7. This week I received two questions on confusion caused by the fact that the difference between CmsHttpContext.Current.Mode…

    Reply

  8. This week I received two questions on confusion caused by the fact that the difference between CmsHttpContext.Current.Mode…

    Reply

Leave a Reply to Anonymous Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.