Skip to content

Cache-and-Collect Lifecycle Management in Ninject 2.0

March 6, 2009

Warning: unless you’re interested in the nuances of lifecycle management in inversion of control systems, this post might make your eyes glaze over. However, if you’re interested in new solutions to old, difficult problems, read on. :)

One of the most important features of any inversion of control framework is the re-use of instances within certain scopes. For example, you might define that a certain service is a singleton, that is, only a single instance of it may exist throughout your application, or you might mark it as one-per-request, meaning one instance of it would be created for each web request that your application receives.

As it is with most important features, lifecycle management is a complex problem, and one that’s very difficult to get correct. In Ninject 1, I assumed that this complexity was inevitable, and implemented a system of behaviors (SingletonBehavior, OnePerRequestBehavior, and so on). While this solution worked, it was difficult to customize and prone to errors.

Ninject 2 introduces a feature that I’ve been working on for awhile, which I’m calling cache-and-collect lifecycle management. Instead of creating a “manager” that controls the re-use of instances (like behaviors in Ninject 1), Ninject 2 associates all instances with a “scope” object. This object is just a POCO – that is, it doesn’t need to know anything about Ninject or that it’s involved in lifecycle management.

Ninject 2 ships with the same four standard scopes (transient, singleton, one-per-thread, and one-per-request) that were available in Ninject 1. When Ninject receives a request, it has a callback associated with it that returns the scoping object for the request. The following table shows the correlation between the binding syntax and the callback that is used:

Binding syntax Object returned from scope callback
InTransientScope() n/a
InSingletonScope() the kernel itself
InThreadScope() System.Threading.Thread.CurrentThread
InRequestScope() System.Web.HttpContext.Current
InScope(callback) Whatever callback returns (custom)

At this point, an example might help. Assume that you have a service IFoo, that is bound to request scope. When the first request occurs for IFoo, the scope callback returns the HttpContext for the current web request, and the resulting instance of IFoo is associated with it. If any subsequent requests for IFoo are made within the same web request, the same instance is returned. However, if a request for IFoo is received from a thread handing a different web request, the scope callback will return a different HttpContext object, resulting in a new instance of IFoo being activated and returned.

For each request that results in the creation of a new instance, the instance is associated with the object that is returned from the request’s scope callback. If the request has no scope callback (as in the case of transient services), no association takes place and Ninject will return a new instance for each request.

From there, the rules of cache-and-collect are simple:

  1. If the callback for a subsequent request returns an object that already has an instance of the requested type associated with it, it is re-used. (Assuming it was activated via the same binding.)
  2. When the scoping object is garbage collected, all instances associated with it are deactivated via Ninject.

Rule 2 was a kind of tricky to implement. The garbage collector doesn’t fire any events when it’s run, and while you can register for callbacks when it’s executed, you have to mess with the settings on the runtime itself to get it to work. As a result, I had to get a little creative, and use a timer and WeakReferences to poll the GC to see when it is run. If you’re interested in the solution, you can see it here. Basically, every second, Ninject checks to see if the GC has run, and if so, it prunes its internal cache, deactivating any instances whose scopes have been collected.

Remember that you can use any object as a scope through the custom InScope() binding syntax. This is intended to replace the use of container hierarchies, which is a common pattern in other dependency injection frameworks, but something I refuse to implement in Ninject because of the complexity that comes with it.

Note that with cache-and-collect, instances are not guaranteed to be deactivated immediately when the scope terminates. For example, instances activated in request scope will not be collected immediately at the end of web request, but when the HttpContext that was used to control the web request is garbage collected, the instances associated with it will be deactivated. However, they are guaranteed to eventually be disposed.

This just means that the normal rules apply to objects that hold scarce resources like file handles and database connections – either don’t couple the lifespan of the scarce resource to the lifespan of the object that holds it, or dispose of the holding object when you’re done with it!

Ninject does provide a way to get deterministic deactivation of your instances for custom scopes, if you’re willing to give up the POCO-ness if the scoping object. If your scope callback returns an object that implements INotifyWhenDisposed (an interface from Ninject), Ninject will immediately deactivate any instances associated with the object when you call object’s Dispose() method.

There’s one final way of handling scope in Ninject2, through activation blocks. Blocks are a way to override the scope that was declared on a binding, and instead associate the activated instances with the block itself. Since the activation block that is returned implements INotifyWhenDisposed, any instances activated via the block are immediately deactivated when the block is disposed. The block object implements all of the same methods available on the kernel itself – such as Get() – but instead of executing them, it simply delegates any requset it receives to the kernel that created it.

Here’s an example of using an activation block:

IKernel kernel = ...;

using (var block = kernel.BeginBlock())
{
  var foo = block.Get<IFoo>();
  var bar = block.Get<IBar>();
}

When your code hits the end of the using() block, the activation block is disposed, causing foo and bar to be deactivated. Note that you aren’t required to use activation blocks within using() constructs; as long as you hold an instance of the block and don’t dispose it, you can continue to activate instances through it and they will be associated with the block. Then when you’re done, dispose of the block, and your instances will be deactivated.

This is a problem that has been nagging me for a long time, and I’m pretty excited about this solution. If you have any feedback, or want to poke holes in the idea, by all means let me know!

About these ads

From → miscellaneous

10 Comments
  1. Levi permalink

    Interesting approach. I have two questions about it.

    1) What about object pools? For instance, say you want to recycle object instances, instead of creating new instances every time. There’s not an obvious object to use as scope. You can’t use the pool instance, since that would result in a singleton model. I suppose you could create a pool of “scope” instances instead of objects. But managing that seems pretty difficult. Perhaps pooling would be better managed else where in the framework.

    2) Why not use a finalizer? Using a thread to monitor an instance seems expensive. Instead, you could create an object which implements a finalizer which notifies the engine when called. The finalizer is called on a different thread, you’d probably want to use a different thread to prune the scope cache to allow the finalizer thread to continue to execute. You might also want to create a new “listener” object each time instead of resurrecting the same object. I’m honestly not sure which is more performance expensive, but it might be worth exploring.

  2. @Levi: Good points.

    Pools could probably be implemented by adding a maximum number of instances per scoping object. I’ve never had that much use for the pooled lifecycle though.

    As far as threads go, the cache only uses one thread to watch the garbage collector for *all* activated instances — not one thread per instance. I do agree that I might be able to implement it using a finalizer though. I’ll have to think about that.

  3. Owain Cleaver permalink

    Nice idea.

    Like Levi i’m not sure i’m so keen on the background thread idea; can’t you just check the scoping reference when an object is requested or am i missing something fundamental here? It seems more likely folks will go down the deterministic route in most cases anyway, as with your HttpContext example, having objects returned scoped to some previous request until it happens to be collected doesn’t seem too useful to me.

    The finalization idea could be interesting too though i wonder how frequent the disposal of these short-lived callback objects would be on a concurrent GC. Also while i think about it, if you’re using a WeakReference to monitor the scope object, i was always under the impression that weak referenced object collection was contingent on memory constraints, not necessarily “normal” pruning rules – i might be wrong though and i guess this could depend on the implementation.

    Finally, maybe for deterministic expiration of (POCO) scope objects you could do something like :-

    public class EventDispatchDisposer : INotifyWhenDisposed
    {
    public EventDispatchDisposer(Action registerEventCallback)
    {
    registerEventCallback(this);
    }

    #region INotifyWhenDisposed …
    }

    then:

    scope = new EventDispatchDisposer(ed => pcoObj.SomeEvent += (() => ed.Dispose()));

    Not sure which event you could attach to for the HttpContext example using this method.

  4. @Owain: In order to assure that every object is eventually deactivated, the objects have to remain in the cache until the scope object is collected. This is really no different than how the CLR garbage collector works — objects remain in the heap until the GC recognizes that they can be collected.

    Also, if there was any way I could make deactivation deterministic without requiring interaction from client code, I would do it. There’s no event that you can subscribe to on HttpContext or Thread that will let you know when it’s disposed. As far as I can tell, there’s no way to do this effectively without polling on a background thread.

  5. Levi permalink

    @Owain – In most cases, you could use the HttpApplication.EndRequest event to know when the request is complete. Of course, if the application was using the EndRequest event and needed to access the resources associated with the context lifetime, this wouldn’t work since you can’t ensure the order the handlers are raised. Also the HttpApplication is a pooled object, so you’d have to be careful not to wire up to the event more than once.

    I don’t know of a way to do it with Treads. I agree with Nate about making deactivation deterministic. However, the idea of having a dedicated thread devoted to polling the lifetime container seems like a lot of overhead.

  6. The problem with the EndRequest event on HttpApplication is that you can’t subscribe to it from outside the HttpApplication itself. That leaves us with the same solution that exists in Ninject 1.x (the OnePerRequestModule).

    Honestly, I don’t understand the concern with having a thread that frees the cached items. You don’t mind the garbage collector running, so why would you worry about a thread that watches the GC and prunes the cache? Remember, we’re talking about one thread for the entire container, not one thread per service or anything like that.

  7. Owain Cleaver permalink

    I’m not too concerned, it’s just an interesting problem to think about :-). Looking at the current implementation it’s the locking I have more concern about. Also the weak reference collection rules, is it specified they are treated as complete non-refs – otherwise the whole idea falls over.

    It’s definitely a cool idea, just a difficult one to pull off well given the tools available.

  8. http://msdn.microsoft.com/en-us/library/cc713687.aspx

    ^ Do objects get finalized other than in a Full GC? Could you not use these hooks?

  9. hey, Nate. You forgot to put some labels on this post.

Trackbacks & Pingbacks

  1. Good read on the life and times of a Ninject object | fzysqr.com

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: