Using singletons in a multithreaded environment

Dec 27, 2006 at 8:23 PM
Hi,

We're using ObjectBuilder as a Dependency Injection Framework in a system we're developing and we have encountered with the following problem:

The scenario:
Since most of the time we don’t wish to run thru the entire strategy chain (as the Builder does) we are using BuilderContext in with per type Strategy Chain and policies.

For Example:

class BuilderHelper
{
private IHConfigurationCache _configCache;
private Locator _locator = null;

private Locator GetLocator()
{
if (_locator == null)
{
_locator = new Locator();
LifetimeContainer lifetime = new LifetimeContainer();
_locator.Add(typeof (ILifetimeContainer), lifetime);
}
return _locator;
}

private T Build<T>()
{
PolicyList policies = _configCache.GetTypePolicyList(typeof(T));

StrategyList<BuilderStage> strategies = _configCache.GetTypeStrategyList(typeof(T));

BuilderContext builderContext =
new BuilderContext(strategies.MakeStrategyChain(), GetLocator(), policies);

T result;
result = (T)builderContext.HeadOfChain.BuildUp(builderContext, typeof(T), null, null);
return result;
}
}

Since we are using singletons in the system, from start I've noticed that when the object builder receives a new locator for each call, the singleton object is not "saved" in memory and the ObjectBuilder creates the object again in each call.

So as you can see I've implemented a workaround in which the builder receives the same locator every time. There lies the problem: under heavy load in a multithreaded environment the WeakRefDictionary throws the following exception: "An item with the given key is already present in the dictionary." At the "Add" function.

We've also simulated this situation in the following example:

TestClass
public class SingletonStrategyFixture
{
private int count = 1000;
TestMethod
public void CreatingASingletonTwiceReturnsSameInstance()
{

ManualResetEvent wait = new ManualResetEvent(false);
IReadWriteLocator locator = new Locator();
WaitCallback callback = delegate
{

wait.WaitOne();

MockBuilderContext ctx = BuildContext(locator);

ctx.Policies.Set<ISingletonPolicy>(new SingletonPolicy(true), typeof(Something), null);

try
{
Something i1 = (Something)ctx.HeadOfChain.BuildUp(ctx, typeof(Something), null, null);

if(count%7==0)
GC.Collect(2);
count--;
}
catch (Exception e)
{
string txt = e.ToString();
Console.WriteLine(txt);
System.Diagnostics.Debugger.Break();
}
};

for (int i = 0; i < count; i++)
{
ThreadPool.QueueUserWorkItem(callback);
}

wait.Set();
while (count > 0)
Thread.Sleep(15);
}

private static MockBuilderContext BuildContext(IReadWriteLocator locator)
{
MockBuilderContext ctx = new MockBuilderContext(locator);
ctx.InnerChain.Add(new SingletonStrategy());
ctx.InnerChain.Add(new CreationStrategy());

ctx.Policies.SetDefault<ICreationPolicy>(new DefaultCreationPolicy());
return ctx;
}

private class Something
{
}
}

What are we doing wrong? Are we misusing the locator? Is there another way we should work with singletons?

We are in a critical point in developing the application and the entire system lies down on the ObjectBuilder framework; please advise us what to do.

Thanks,

Bernie
Coordinator
Dec 28, 2006 at 5:52 AM
Locators are not thread-safe.

There is a very crude form of thread safety in the BuilderBase class which locks on locator instances to prevent building twice simultaneously using the same locator.

Additionally, your GetLocator() method is not thread safe.
Dec 28, 2006 at 7:44 AM
Ignoring the GetLocator() thead-safety issues for a moment, what is the correct way to have application global singleton services?
Coordinator
Dec 28, 2006 at 5:49 PM
Most of the classes in ObjectBuilder are NOT thread-safe. The exception is Builder.BuildUp and Builder.TearDown, which are thread-safe on a per-locator basis (i.e., they lock the thread based on the locator instance you provide, so that all calls for each locator are serialized).

If your usage follows that pattern (only ever calling BuildUp or TearDown on multiple threads, and none of the other methods of any of the other classes), then it will be thread safe.

If you require additional levels of thread safety, then you will either need to change ObjectBuilder or wrap it to provide the thread safety yourself.