views:

279

answers:

4

The Unity dependency injection container has what seems to be a widely known issue where the SynchronizedLifetimeManager will often cause the Monitor.Exit method to throw a SynchronizationLockException which is then caught and ignored. This is a problem for me because I like to debug with Visual Studio set to break on any thrown exception, so every time my application starts up I'm breaking on this exception multiple times for no reason.

How can I prevent this exception from being thrown?

Wherever this issues is mentioned elsewhere on the web, the advice usually involves changing the debugger settings to ignore it. This is akin to going to the doctor and saying, "Doctor, Doctor, my arm hurts when I raise it," to be told, "Well, stop raising it." I'm looking for a solution that stops the exception being thrown in the first place.

The exception occurs in the SetValue method because it makes the assumption that GetValue will have been called first, where Monitor.Enter is called. However, the LifetimeStrategy and UnityDefaultBehaviorExtension classes both regularly call SetValue without calling GetValue.

I'd rather not have to change the source code and maintain my own version of Unity, so I'm hoping for a solution where I can add some combination of extensions, policies, or strategies to the container that will ensure that, if the lifetime manager is a SynchronizedLifetimeManager, GetValue is always called before anything else.

A: 

This might help you:

  • Go to Debug -> Exceptions...
  • Find the exceptions that really upset you like SynchronizationLockException

Voila.

Alex G
That's what I meant by, "the advice usually involves changing the debugger settings to ignore it." I don't want to squash any SynchronizationLockExceptions that bugs in my own code might be causing.
Rory MacLeod
A: 

Rory,

The answer to your question is unfortunately no. I followed up on this with the dev team and we had this as a bug to consider for EntLib 5.0. We did some investigation and came to the conclusion that this was caused by some unexpected interactions between our code and the debugger. We did consider a fix but this turned out to be more complex than the existing code. In the end this got prioritized below other things and didn't make the bar for 5.

Sorry I don't have a better answer for you. If it's any consolation I find it irritating too.

Thanks,

Ade Miller

Microsoft patterns & practices.

Ade Miller
Thanks for the answer from the horse's mouth! Happily, Unity is extensible enough for me to be able to work around the problem - see my [answer](http://stackoverflow.com/questions/2873767/can-unity-be-made-to-not-throw-synchronizationlockexception-all-the-time/3153585#3153585).
Rory MacLeod
+2  A: 

I'm sure there's a lot of ways code could call SynchronizaedLifetimeManager, or a descendant like ContainerControlledLifetimeManager, but there were two scenarios in particular that were causing me problems.

The first was my own fault - I was using constructor injection to supply a reference to the container, and in that constructor I was also adding the new instance of the class to the container for future use. This backwards approach had the effect of changing the lifetime manager from Transient to ContainerControlled so that the object Unity called GetValue on was not the same object it called SetValue on. The lesson learned is don't do anything during build-up that could change an object's lifetime manager.

The second scenario was that every time RegisterInstance is called, UnityDefaultBehaviorExtension calls SetValue without calling GetValue first. Luckily, Unity is extensible enough that, with enough bloody-mindedness, you can work around the problem.

Start with a new behavior extension like this:

/// <summary>
/// Replaces <see cref="UnityDefaultBehaviorExtension"/> to eliminate 
/// <see cref="SynchronizationLockException"/> exceptions that would otherwise occur
/// when using <c>RegisterInstance</c>.
/// </summary>
public class UnitySafeBehaviorExtension : UnityDefaultBehaviorExtension
{
    /// <summary>
    /// Adds this extension's behavior to the container.
    /// </summary>
    protected override void Initialize()
    {
        Context.RegisteringInstance += PreRegisteringInstance;

        base.Initialize();
    }

    /// <summary>
    /// Handles the <see cref="ExtensionContext.RegisteringInstance"/> event by
    /// ensuring that, if the lifetime manager is a 
    /// <see cref="SynchronizedLifetimeManager"/> that its 
    /// <see cref="SynchronizedLifetimeManager.GetValue"/> method has been called.
    /// </summary>
    /// <param name="sender">The object responsible for raising the event.</param>
    /// <param name="e">A <see cref="RegisterInstanceEventArgs"/> containing the
    /// event's data.</param>
    private void PreRegisteringInstance(object sender, RegisterInstanceEventArgs e)
    {
        if (e.LifetimeManager is SynchronizedLifetimeManager)
        {
            e.LifetimeManager.GetValue();
        }
    }
}

Then you need a way to replace the default behavior. Unity doesn't have a method to remove a specific extension, so you have to remove everything and put the other extensions back in again:

public static IUnityContainer InstallCoreExtensions(this IUnityContainer container)
{
    container.RemoveAllExtensions();
    container.AddExtension(new UnityClearBuildPlanStrategies());
    container.AddExtension(new UnitySafeBehaviorExtension());

#pragma warning disable 612,618 // Marked as obsolete, but Unity still uses it internally.
    container.AddExtension(new InjectedMembers());
#pragma warning restore 612,618

    container.AddExtension(new UnityDefaultStrategiesExtension());

    return container;
}

Notice that UnityClearBuildPlanStrategies? RemoveAllExtensions clears out all of the container's internal lists of policies and strategies except for one, so I had to use another extension to avoid inserting duplicates when I restored the default extensions:

/// <summary>
/// Implements a <see cref="UnityContainerExtension"/> that clears the list of 
/// build plan strategies held by the container.
/// </summary>
public class UnityClearBuildPlanStrategies : UnityContainerExtension
{
    protected override void Initialize()
    {
        Context.BuildPlanStrategies.Clear();
    }
}

Now you can safely use RegisterInstance without fear of being driven to the brink of madness. Just to be sure, here's some tests:

[TestClass]
public class UnitySafeBehaviorExtensionTests : ITest
{
    private IUnityContainer Container;
    private List<Exception> FirstChanceExceptions;

    [TestInitialize]
    public void TestInitialize()
    {
        Container = new UnityContainer();
        FirstChanceExceptions = new List<Exception>();
        AppDomain.CurrentDomain.FirstChanceException += FirstChanceExceptionRaised;
    }

    [TestCleanup]
    public void TestCleanup()
    {
        AppDomain.CurrentDomain.FirstChanceException -= FirstChanceExceptionRaised;
    }

    private void FirstChanceExceptionRaised(object sender, FirstChanceExceptionEventArgs e)
    {
        FirstChanceExceptions.Add(e.Exception);
    }

    /// <summary>
    /// Tests that the default behavior of <c>UnityContainer</c> leads to a <c>SynchronizationLockException</c>
    /// being throw on <c>RegisterInstance</c>.
    /// </summary>
    [TestMethod]
    public void UnityDefaultBehaviorRaisesExceptionOnRegisterInstance()
    {
        Container.RegisterInstance<ITest>(this);

        Assert.AreEqual(1, FirstChanceExceptions.Count);
        Assert.IsInstanceOfType(FirstChanceExceptions[0], typeof(SynchronizationLockException));
    }

    /// <summary>
    /// Tests that <c>UnitySafeBehaviorExtension</c> protects against <c>SynchronizationLockException</c>s being
    /// thrown during calls to <c>RegisterInstance</c>.
    /// </summary>
    [TestMethod]
    public void SafeBehaviorPreventsExceptionOnRegisterInstance()
    {
        Container.RemoveAllExtensions();
        Container.AddExtension(new UnitySafeBehaviorExtension());
        Container.AddExtension(new InjectedMembers());
        Container.AddExtension(new UnityDefaultStrategiesExtension());

        Container.RegisterInstance<ITest>(this);

        Assert.AreEqual(0, FirstChanceExceptions.Count);
    }
}

public interface ITest { }
Rory MacLeod
A: 

Rory's solution is great - thanks. Solved an issue that annoys me every day! I made some minor tweaks to Rory's solution so that it handles whatever extensions are registered (in my case i had a WPF Prism/Composite extension)..

    public static void ReplaceBehaviourExtensionsWithSafeExtension(IUnityContainer container)
    {
        var extensionsField = container.GetType().GetField("extensions", BindingFlags.Instance | BindingFlags.NonPublic);
        var extensionsList = (List<UnityContainerExtension>)extensionsField.GetValue(container);
        var existingExtensions = extensionsList.ToArray();
        container.RemoveAllExtensions();
        container.AddExtension(new UnitySafeBehaviorExtension());
        foreach (var extension in existingExtensions)
        {
            if (!(extension is UnityDefaultBehaviorExtension))
            {
                container.AddExtension(extension);
            }
        }
    }
Zubin Appoo