StructureMap returns a disposed nHibenrate session object from thread local scope
Asked Answered
C

2

7

[OR] How to define a StructureMap life cycle for UoW to be consumed by http requests and quartz jobs

I have this web application which uses SM for IoC. I am using HybridHttpOrThreadLocalScoped scope to store my nHibernate ISession objects. This works ok in a session per request fashion for my web requests.

But I also have quartz.net that schedules couple of jobs. The job uses the same unit of work to get the ISession object. In this scenario when the scheduler start the job, everything works fine at first and the job runs fine for couple of times UNTIL the job thread id gets repeated.

Imagine that when the job is scheduled it start to run in threads with ids 11, 12, 13, and then with thread id 11 again. At this point structuremap returns a session object which is already disposed and I get "System.ObjectDisposedException: Session is closed!" error.

So from what I can see, the session is kept in thread local storage and after I dispose the session at the end of my unit of work, the session object is still kept in the thread local storage. It seems that after the thread terminates its local storage is not cleared and somehow when a new thread with the same id is created, structuremap looks up the session in the old thread local storage (which is supposed to be cleared for the new thread I believe) and returns the session object which is already disposed.

Questions:

  1. Is there a way to clear the thread local storage (on termination)?
  2. Is there an equivalent of "ReleaseAndDisposeAllHttpScopedObjects" for thread-scoped objects?
  3. Is there a way to nullify (or eject) the disposed object so even if SM looks for it then it wouldn't find any and has to create a new instance?

I hope I made my question clear. This has taken couple of hours of my time and still I haven't found a way around it. I appreciate any hint :>

Update: I added my own solution to make a UoW served by StructureMap work with both http requests and quartz jobs. Let me know if you have a better/easier/simpler solution.

Coly answered 3/7, 2010 at 1:43 Comment(5)
Are you managing your quartz IJobs with StructureMap?Savell
@Mauricio: I am using StructureMap in my application. I am not sure what you mean by managing quartz jobs by StructeMap though : >Coly
are your Quartz IJob instances managed by StructureMap? In other words: do you register your jobs in the container?Savell
@Mauricio: No I don't register my jobs in the StructureMap container. I am using StructureMap to get a UoW for each job that is fired.Coly
I'd let the container manage the jobs... it will probably make things easier.Savell
C
4

I was revisiting what I did to make StructureMap work with UoW per Http and UoW per quartz job and I decided to share my solution here.

So the idea was that I wanted to use StructureMap Hybrid scope to get an instance of UoW when there is a http context and also get a different instance of UoW per thread when there is no http context (like when a quartz job fires). Like this:

For<IUnitOfWork>().HybridHttpOrThreadLocalScoped().Use<UnitOfWork>();

The UoW for http worked fine. The problem was UoW per thread.

Here is what happens. When a quratz job fires it pulls a thread from the thread pool and starts executing the job using that thread. When the job starts I request a UoW. StructureMap looks under the local storage for that thread to return the UoW, but because it can't find any it instantiates one and saves it under thread's local storage.I get the UoW, then perfom Begin, Commit, Dispose and everything is fine.

The problem happens when a thread is pulled from thread pool which was used before to fire a job (and used a UoW). Here when you request a UoW, StructureMap looks in the cache (thread local storage) and finds a UoW and returns it to you. But the problem is the UoW is disposed!

So we cannot really use UoW per thread for quartz jobs because the threads themselves are not disposed and they hold the old cached disposed UoWs. Basically life cycle of a thread does not match the life cycle of a quartz job. That's why I created my own life cycle for a quartz job.

First I created my own http-quartz hybrid life cycle class:

public class HybridHttpQuartzLifecycle : HttpLifecycleBase<HttpContextLifecycle, QuartzLifecycle>
{
    public override string Scope { get { return "HybridHttpQuartzLifecycle"; } }
}

Then I created my QuartzLifecyle class:

public class QuartzLifecycle : ILifecycle
{

    public void EjectAll()
    {
        FindCache().DisposeAndClear();
    }

    public IObjectCache FindCache()
    {
        return QuartzContext.Cache;
    }

    public string Scope { get { return "QuartzLifecycle"; } }
}

Then I need to create some context class like HttpContext for Quartz to hold the context related info. So I created QuartzContext class. When a quartz job is fired, the JobExecutionContext for that job should be registered in QuartzContext. Then the actual cache (MainObjectCache) for StructureMap instances will be created under that specific JobExecutionContext. So this way after the job execution finishes the cache will go away too and we won't have problem of disposed UoW in cache.

Also since _jobExecutionContext is ThreadStatic, when ever we request the cache from QuartzContext, it will return the cache from the JobExecutionContext that is saved for the same thread. So when multiple jobs are running at the same time, their JobExecutionContexts are saved separately and we will have separate caches for each running job.

public class QuartzContext
{

    private static readonly string _cacheKey = "STRUCTUREMAP-INSTANCES";

    [ThreadStatic]
    private static JobExecutionContext _jobExecutionContext;

    protected static void Register(JobExecutionContext jobExecutionContext)
    {
        _jobExecutionContext = jobExecutionContext;
        _jobExecutionContext.Put(_cacheKey, new MainObjectCache());
    }

    public static IObjectCache Cache 
    { 
        get 
        {
            return (IObjectCache)_jobExecutionContext.Get(_cacheKey);
        } 
    }
}  

I have an abstract class called BaseJobSingleSession that other jobs derive from. This class extends the QuartzContext class. You can see that I register the JobExecutionContext when the job is fired.

abstract class BaseJobSingleSession : QuartzContext, IStatefulJob
{
    public override void Execute(JobExecutionContext context)
    {
        Register(context);
        IUnitOfWork unitOfWork = ObjectFactory.GetInstance<IUnitOfWork>();

        try
        {
            unitOfWork.Begin();

            // do stuff ....

            unitOfWork.Commit();
        }
        catch (Exception exception)
        {
            unitOfWork.RollBack();

        }
        finally
        {
            unitOfWork.Dispose();
        }
    }
}

Finally I defined the life cycle for UoW:

For<IUnitOfWork>().LifecycleIs(new HybridHttpQuartzLifecycle()).Use<UnitOfWork>();

(For life cycle and context classes I looked into the StructureMap source code to get the idea.)

Please share your ideas, comments and suggestions : >

Coly answered 12/7, 2011 at 22:53 Comment(0)
A
1

Why not create a new session for the quartz jobs? A unit of work is typically a transactional operation on the db. I can't imagine the quartz jobs being transactionally related to the web request/responses. Creating new sessions is not expensive. Is this a possibility?

Affixation answered 6/5, 2011 at 0:47 Comment(2)
Yes. I want to create a new session for each quartz job. But since I am using the idea of UoW, I want it to be consistent in my application. So I do not want to create sessions directly for quartz jobs. I want to create a UoW instance. But at the same time I want to use StructureMap to get an instance of UoW. That's why I ended up defining a hybrid life cycle in StructeMap for my UoW such that it would return the appropriate UoW for each http request or each quartz job execution.Coly
@kaptan: I agree with @rcravens. What I've done is get an instance of the ISessionFactory (ObjectFactory.GetInstance<ISessionFactory>()) and open a new session when my job fires.Elspet

© 2022 - 2024 — McMap. All rights reserved.