Rory Primrose

Learn from my mistakes, you don't have time to make them yourself

View project on GitHub

Beware of lifetime manager policy locks in Unity

I have created a caching dependency injection slice in order to squeeze more performance from the DAL in a workflow service. What I found was that the service always hit a timeout when the caching slice was put into the Unity configuration. I spent half a day working with AppFabric monitoring, event logs and all the information I could get out of diagnostic tracing for WCF, WF, WIF and custom sources. After not being able to get any answers along with futile debugging efforts, I realised that I could profile the service to see where the hold up was.

The profiler results told me exactly where to look as soon as I hit the service at got a timeout exception.image

All the time is getting consumed in a single call to Microsoft.Practices.Unity.SynchronizedLifetimeManager.GetValue(). The first idea that comes to mind is that there is a lock on an object that is not being released. Reflector proves that this is exactly the case. image

The GetValue method obtains a lock for the current thread and only releases it if a non-null value is held by the lifetime manager. This logic becomes a big issue if the lifetime manager holds a null value and two different threads call GetValue. I would like to know why this behaviour is there as it is intentional according to the documentation of the function.

This is what is happening in my service. In the profiling above you can see that the lifetime manager is getting called from my Unity extension for disposing build trees. While the extension is not doing anything wrong, it can handle this scenario by using a timeout on obtaining a value from the lifetime manager.

private static Object GetLifetimePolicyValue(ILifetimePolicy lifetimeManager)
{
    if (lifetimeManager is IRequiresRecovery)
    {
        // There may be a lock around this policy where a null value will result in an indefinite lock held by another thread
        // We need to use another thread to access this item so that we can get around the lock using a timeout
        Task<Object> readPolicyTask = new Task<Object>(lifetimeManager.GetValue);
    
        readPolicyTask.Start();
    
        Boolean taskCompleted = readPolicyTask.Wait(10);
    
        if (taskCompleted == false)
        {
            return null;
        }
    
        return readPolicyTask.Result;
    }
    
    return lifetimeManager.GetValue();
}

This implementation is not ideal, but it is unfortunately the only way to handle this case as there is no way to determine whether another thread has a lock on the lifetime manager.

Testing the service again with the profiler then identified a problem with this workaround.image

This workaround will consume threads that will be held on a lock and potentially never get released. This is going to be unacceptable as more and more threads attempt to look at values held in the lifetime manager policies, ultimately resulting in thread starvation.

The next solution is to use a lifetime manager that gets around this issue by never allowing the lifetime manager to be assigned a null value.

namespace Neovolve.Jabiru.Server.Services
{
    using System;
    using System.Diagnostics.Contracts;
    using Microsoft.Practices.Unity;
    
    public class SafeSingletonLifetimeManager : ContainerControlledLifetimeManager
    {
        public override void SetValue(Object newValue)
        {
            if (newValue == null)
            {
                throw new ArgumentNullException("newValue");
            }
    
            base.SetValue(newValue);
        }
    }
}

This idea fails to get around the locking issue when the lifetime manager is created but never has a value assigned. The next version of this SafeSingletonLifetimeManager solves this by managing its own locking logic around whether a non-null value has been assigned to the policy.

namespace Neovolve.Jabiru.Server.Services
{
    using System;
    using System.Threading;
    using Microsoft.Practices.Unity;
    using Neovolve.Toolkit.Threading;
    
    public class SafeSingletonLifetimeManager : ContainerControlledLifetimeManager
    {
        private readonly ReaderWriterLockSlim _syncLock = new ReaderWriterLockSlim();
    
        private Boolean _valueAssigned;
    
        public override Object GetValue()
        {
            using (new LockReader(_syncLock))
            {
                if (_valueAssigned == false)
                {
                    return null;
                }
    
                return base.GetValue();
            }
        }
    
        public override void SetValue(Object newValue)
        {
            using (new LockWriter(_syncLock))
            {
                if (newValue == null)
                {
                    _valueAssigned = false;
                }
                else
                {
                    _valueAssigned = true;
                }
    
                base.SetValue(newValue);
            }
        }
    }
}

Using this policy now avoids the locking problem described in this post. I would still like to know the reason for the locking logic as this SafeSingletonLifetimeManager is completely circumventing that logic.

Written on March 31, 2011