The Darkside

Shedding light on things and stuff

 
  Home :: Contact :: Syndication  :: Login
  70 Posts :: 0 Stories :: 47 Comments :: 2 Trackbacks

Ads

Archives

Post Categories

Open Source Projects

Other Blogs

This is a follow-up post on an article from a few months back located here and hopefully expands on how to overcome the error "failed to lazily initialize a collection, no session or session was closed" which I get very often in WinForms development. This error generally gets thrown when the object you are trying to lazily fetch a list for has become disconnected. The example and workaround I discussed previously was around instantiating a SessionScope object in the windows form, which stayed in scope for the duration of the form. While trying to improve on the UI design, I moved the lazy fetching, etc, to run asynchronously. All of a sudden, the lazy load errors were back and quite a stack of debugging ensued.

Initially, I borrowed a test WinForms application that loaded a master-detail set of data asynchronously from FryHard (his POC for Castle/AR/Asynchronous UI) to test my lazy loading trial-and-error solutions. I then tracked down the area of the code that I thought was the culprit (in the ActiveRecord source, file ThreadScopeInfo.cs):

[ThreadStatic] static Stack stack;

The above line was the internal stack variable that held instances of the SessionScope class and, more importantly, the [ThreadStatic] attribute indicated to me that this was on a per-thread basis, meaning that the "static-ness" of the variable was confined to the thread it was on. This was easy enough for me to change, but I certainly didn't want to have my own modified copy of ActiveRecord to maintain. Castle ActiveRecord has many places to add your own hooks into, so the search for where to implement my own ThreadScopeInfo class was pretty short. I added a handler to the "SessionFactorHolderCreated" event on the ActiveRecordStarter class and in the handler, hook up my new ThreadScopeInfo class.

private static void Main()
{
    ActiveRecordStarter.SessionFactoryHolderCreated += ActiveRecordStarter_SessionFactoryHolderCreated;
    ActiveRecordStarter.Initialize(Assembly.GetExecutingAssembly(), ActiveRecordSectionHandler.Instance);
}
 
static void ActiveRecordStarter_SessionFactoryHolderCreated(Castle.ActiveRecord.Framework.ISessionFactoryHolder holder)
{
    holder.ThreadScopeInfo = new ThreadScopeInfo();
} 

It's important to note that the event needs to be hooked up before initialize is called. My first-try implementation of the new ThreadScopeInfo was pretty simple: Implement the new stack, and make sure the initialisation is thread-safe.

 Expand Code
public sealed class ThreadScopeInfo : AbstractThreadScopeInfo
{
    private static Stack stack;
    private static object _SyncLock = new object();
    /// <summary>
    /// Gets the current stack.
    /// </summary>
    /// <value>The current stack.</value>
    public override Stack CurrentStack
    {
        get
        {
            if (stack == null)
            {
                lock (_SyncLock)
                {
                    if (stack == null)
                    {
                        stack = new Stack();
                    }
                }
            }
            return stack;
        }
    }
}

This solution worked well on the test project, and all the test cases I had added passed and the dreaded lazy-load error had dissapeared. However, when I moved it over into the project it was originally intended for, it was back again. For me, this meant going back to the drawing board. The reason I've posted this initial solution, albeit that it doesn't work properly in all cases, is that it is a suitable solution for light-weight WinForm applications.

Back to the problem: what was happening in the application was that many async operations were running at once (aside: Yes, my load testing in the test app was lacking. Or shocking. Or Both ;) ) Delving into the ActiveRecord code again, it was clear that the problem was happening in the AbstractThreadScopeInfo class, in the GetRegisteredScope() method. When this method is called by the mechanism doing the lazy load, it returns a null value, which in turn maps back to the "no session" part of the message.

I then decided to craft my own ThreadScopeInfo from scratch, implementing the IThreadScopeInfo interface rather than inheriting the AbstractThreadScopeInfo class as before. In actual fact, very little ended up being from scratch; I used the innards from the AbstractThreadScopeInfo, modifying the portions I deemed necessary. The resultant class looks like this:

 Expand Code
public class ThreadScopeInfo : IThreadScopeInfo
{
    private readonly object _SyncLock;
    private readonly Stack _CurrentStack;
 
    public ThreadScopeInfo()
    {
        _CurrentStack = new Stack();
        _SyncLock = new object();
    }
 
    public Stack CurrentStack {get { return (_CurrentStack); }}
 
    public void RegisterScope(ISessionScope scope)
    {
        CurrentStack.Push(scope);
    }
 
 
    public ISessionScope GetRegisteredScope()
    {
        if (CurrentStack.Count == 0)
        {
            //Instead of returning a "null" stack as is in the original ActiveRecord code, 
            //instantiate a new one which adds itself to the stack immediately
            lock (_SyncLock)
            {
                if (CurrentStack.Count == 0)
                {
                    new SessionScope();
                }
            }
        }
        return CurrentStack.Peek() as ISessionScope;
    }
 
    public void UnRegisterScope(ISessionScope scope)
    {
        if (GetRegisteredScope() != scope)
        {
            throw new ScopeMachineryException("Tried to unregister a scope that is not the active one");
        }
        CurrentStack.Pop();
    }
 
    public bool HasInitializedScope
    {
        get { return GetRegisteredScope() != null; }
    }
}

The crux of the change is the modification to the GetRegisteredScope() method, which, if in the event of the stack being empty, instantiates a new SessionScope object; This object automatically registers itself, which in turn causes it to be pushed onto the stack.

This is a hack, and I have no doubt about it. It does, however, cause all issues surrounding lazy loading to dissapear, with no side-efects that I've noticed at all.

posted on Tuesday, September 09, 2008 10:11 AM
Comments have been closed on this topic.