Software Mechanics
Why do we even have that lever?

Writing Custom Lifetime Managers

July 15, 2008 11:00 by Chris

 

Note: I'm going to be doing a webcast on extending the Unity container on July 24th. I'm going to be doing the preparation here since I also need to work on the general extension docs anyway. Let's hear it for killing three birds with one stone! In any event, I'd love to get feedback on this doc before I do the webcast. Does it flow well? Make sense? Are you able to write custom lifetime managers after reading this? Thanks!

The Unity Dependency Injection Container has many extension points. One of the easiest to take advantage of is creating custom lifetime managers. A lifetime manager controls how instances are reused within the container. By default, the Unity container creates a new instance every time the Resolve method is called. This can be changed by registering a lifetime manager, like so:

container.RegisterType<IFoo, Foo>(new ContainerControlledLifetimeManager());

With this registration any time an instance of Foo is requested, you'll get back the same one as long as the container is alive.

Since you can pass in any instance of a lifetime manager, it's pretty easy to write your own to customize how object instances get reused. Let's take a look at the LifetimeManager class and how it works within Unity.

The LifetimeManager class

To implement a custom lifetime manager, you need to create a class that derives from Microsoft.Practices.Unity.LifetimeManager. The important methods are:

public abstract class LifetimeManager : ILifetimePolicy
{
    public abstract object GetValue();
    public abstract void SetValue(object newValue);
    public abstract void RemoveValue();
}

It's also important to preserve the semantics of the LifetimeManager. In particular:

  • There is one instance of a LifetimeManager per object that is resolved from the container. Don't try to write a lifetime manager that handles multiple objects at once. The base class and the container enforce this requirement at runtime.
  • Thread safety around your GetValue and SetValue call are your job. We'll see an example of this later.

The container uses the lifetime manager in a fairly straightforward way. If the object in question hasn't been created yet, the sequence looks like this:

Sequence diagram - No object exists 

(Why yes, my handwriting does stink, thanks for noticing :-))

Basically, when a resolve call happens, type mapping happens first (not in the diagram), and then the current lifetime manager is looked up (based on the type and name being resolved). The GetValue method is called, and it returns null. At that point the construction of the object happens, and at the end the SetValue method is called to store the created object for next time.

That next time looks like this:

Sequence - object exists

If GetValue returns something other than null, that becomes the return value of the Resolve call and the rest of the build up process is short-circuited.

Example: Implementing a PerThreadLifetimeManager

Let's look at a sample implementation. One thing that can be useful is a per-thread lifetime: each thread that requests a particular type gets its own instance, but multiple requests from the same thread get the same instance.

The first thing you need to do is decide how you're going to save the instance. In this case, we need some kind of thread local storage (TLS). Luckily, the .NET framework has support for TLS built in. All we need to do is create a static field in our class, and add the [ThreadStatic] attribute. Since this is a static, we'll need to store multiple objects in it (one per PerThreadLifetimeManager). A Dictionary will do the trick. I'm using a Guid as a key because they're an easy way to generate a key. Our class starts off like this:

public class PerThreadLifetimeManager : LifetimeManager
{
    [ThreadStatic]
    private static Dictionary<Guid, object> values;
    private Guid key;

    public PerThreadLifetimeManager()
    {
        key = Guid.NewGuid();
    }

...
}

Now we just need to implement the GetValue and SetValue methods. They're very straightfoward:

public override object GetValue()
{
    InitializeValues();
    if (values.ContainsKey(key))
    {
        return values[key];
    }
    return null;
}

public override void SetValue(object newValue)
{
    InitializeValues();
    values[key] = newValue;
}

private void InitializeValues()
{
    if (values == null)
    {
        values = new Dictionary<Guid, object>();
    }
}

(The InitializeValues method is used to guarantee that each thread gets it's copy of the dictionary properly initialized, see MSDN for more details).

What About RemoveValue?

There's one more method that we have to implement: RemoveValue. This is a little embarrassing, but there's no reason to implement this method. Nothing in the Unity pipeline ever calls RemoveValue. It's there for those doing more advanced extensions, but out of the box it does nothing. The sample code with this article implements it as an empty function that does nothing.

Example: CachedLifetimeManager and Multithreading

Ironically, the PerThreadLifetimeManager class doesn't actually deal with multithreading. The use of TLS meant that we could pretty much ignore concurrency issues. However, as I'm sure you can imagine, proper threading is a major concern in most other lifetime managers. As an example, we'll build one that provides access to a value stored in a cache. This could be the ASP.NET cache, the Enterprise Library caching block, or anything else.

(Sidebar: The ASP.NET cache works just fine in non-ASP.NET applications. Don't let the System.Web namespace prevent you from using this great piece of technology!)

To insulate our code from the details of caching, I've defined a simple interface that does the bare minimum we need - get and retrieve items:

public interface IStorage
{
    object GetObject(string key);
    void StoreObject(string key, object value);
}

It's named IStorage rather than ICache because this is useful for more than just caching. I'm sure you can easily imagine an implementation that maps to the ASP.NET cache; see the code sample for this article for a working one.

When dealing with multithreaded access, the initial approach is usually to do something like this:

public override object GetValue()
{
    lock (lockObj)
    {
        return storage.GetObject(key);
    }
}

Unfortunately, this doesn't work. Look again at the sequence diagram above. If the object isn't in storage, we want to return null. But the lock is released as soon as GetValue returns, and now we're still open to race conditions. What we need to do is take a lock, and if the value is not in storage, hold the lock until SetValue completes. This serializes the object creation process so that we're guaranteed not to new up more objects than we need.

So, we need to hold onto the lock after GetValue returns. This will, I admit, feel a little weird at first. The basic idea is that if there's no object in storage, return without releasing the lock, and then release the lock in the corresponding call to SetValue. Of course, you can't use the lock() statement anymore to do this, so we need to drop down to the lower-level Monitor.Enter and Exit calls. The resulting methods look like this:

public override object GetValue()
{
    Monitor.Enter(lockObj);
    object result = storage.GetObject(key);
    if (result != null)
    {
        Monitor.Exit(lockObj);
    }
    return result;
}

public override void SetValue(object newValue)
{
    storage.StoreObject(key, newValue);
    TryExit();
}

private void TryExit()
{
    try
    {
        Monitor.Exit(lockObj);
    }
    catch (SynchronizationLockException)
    {
        // This is ok, just means we don't hold the lock
    }
}

The TryExit helper method is there because there are circumstances (particularly when you're calling container.RegisterInstance) where you'll call SetValue directly without having gone through GetValue first. The TryExit method avoids throwing an exception if we're not actually holding the lock.

Experienced multithreaded programmers reading this are probably a little worried right now. We exit GetValue holding a lock. What happens if an exception happens and SetValue never gets called? Isn't the lock abandoned at this point? The answer is yes, but there's a safety net.

There's a new interface defined in ObjectBuilder2 called IRequiresRecovery:

public interface IRequiresRecovery
{
    void Recover();
}

The Recover method will be called by ObjectBuilder if an exception happens during the build up process. So, to make sure the lock gets released, we just implement IRequiresRecovery on our lifetime manager:

public class CachedLifetimeManager : LifetimeManager, IRequiresRecovery
{
    ...

    #region IRequiresRecovery Members

    public void Recover()
    {
        TryExit();
    }

    #endregion
}

... and we're all set.

Odds and Ends

We've spent a lot of time talking about how lifetime managers handle creation and retrieval of instances. What about the end of an instance's lifetime? There's a pretty simple hook here: implement IDisposable on your lifetime manager class, and when the container is Disposed, the lifetime manager will be too. This is your opportunity to do whatever cleanup you need. Take a look at the code for the ContainerControlledLifetimeManager for an example.

Our final detail has to do with the container's open generic support. As I said above, there should be one lifetime manager instance per instance being managed. What happens when you do:

container.RegisterType<IRepository<>, CustomRepository<>>(new ContainerControlledLifetimeManager());

You've actually got many different instances at this point, one for each generic type argument: CustomRepository<User>, CustomRepository<Account>, CustomRepository<Flamethrower>, etc. This causes havoc with the semantics of the lifetime manager, and will result in all sorts of weird exceptions down the line.

The solution to this is generally transparent, but is useful to know about. When you register a lifetime manager for an open generic type, the lifetime manager instance to pass in isn't actually used. Instead, the container holds on to the type of the lifetime manager. When creating a closed generic type, it uses the container itself to resolve a new instance of that lifetime manager type.

Normally, you don't care. However, if for some reason you need a lifetime manager that takes constructor parameters, be aware that for open generics the container will be used to create the lifetime manager, and will need to be configured accordingly.

Wrap-up

That's it for lifetime managers. A complete copy of the sample code is available for download below. The unit tests make use of the Moq mock object framework; you'll need to download that separately to compile and run the tests.

TavaresStudios.Unity.zip (11.52 kb)


Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Tags:
Categories: p&p | Unity
Actions: E-mail | Permalink | Comments (9) | Comment RSSRSS comment feed

Comments

Comments are closed