0

I'm developing my own simple IoC library which one i'd like to make thread-safe.

My typical unit test looks like this:

[TestMethod]
public void TestContainerUseExistingObjectFromLifetimeManagerWithFactoryMethod()
{
    // Arrange
    var container = new FsContainer();

    container
        .For<IRepository>()
        .Use(ctx => new Repository("sql_connection_string"), new ContainerControlledLifetimeManager());

    // Act
    var first = container.Resolve<IRepository>();
    var second = container.Resolve<IRepository>();

    // Arrange
    Assert.AreEqual(first.ConnectionString, "sql_connection_string");
    Assert.AreEqual(second.ConnectionString, "sql_connection_string");
    Assert.AreSame(first, second);
}

This works great before i tried to test it next manner:

[TestMethod]
public async Task TestMultiThreadContainerUseExistingObjectFromLifetimeManagerWithFactoryMethodAsync()
{
    // Arrange
    var container = new FsContainer();

    container
        .For<IRepository>()
        .Use(ctx => new Repository("sql_connection_string"), new ContainerControlledLifetimeManager());

    // Act
    var instances = await Task.WhenAll(
        Task.Run(() => container.Resolve<IRepository>()), 
        Task.Run(() => container.Resolve<IRepository>())
    );

    var first = instances[0];
    var second = instances[1];

    // Arrange
    Assert.AreEqual(first.ConnectionString, "sql_connection_string");
    Assert.AreEqual(second.ConnectionString, "sql_connection_string");
    Assert.AreSame(first, second);
}

This test show me, that i had issue with Assert.AreSame (my first & second instances were not same).

I've implemented lock statement at Resolve method and everything started to work fine.

Question: Is it the correct way to duplicate most of the functionality in single and multi-thread way to test thread-safity? Does it make sense?

Maxim Zhukov
  • 10,060
  • 5
  • 44
  • 88
  • I don't know what container are you using but to me it makes perfect sense that it creates different instances of a managed object for different threads. Are you sure that's not the expected behavior? – yorodm May 31 '17 at 15:30
  • In this case i use `ContainerControlledLifetimeManager` which one related to singleton pattern (similar to `UnityContainer`). – Maxim Zhukov May 31 '17 at 15:33

1 Answers1

1

Testing for thread safety is hard if not impossible in most cases.

Your second test case can expose some issues but does not guarantee that code behaves correctly. I.e. it could detect the code violated design by creating per-thread instance instead of one global instance if code does it consistently, but have very low chance to catch parallel access to shared dictionary (or whatever collection the code stores singletons in). You got lucky to actually detect a problem with the test - possibly code that ensures instance of singleton is slow enough to let two threads start and hit the problem. Test would unlikely to catch errors if code would be fast and somewhat correct (i.e. using double-checked locking without locking).

For writing thread-safe code you should start with known to be correct conservative code (i.e. just lock around all operations) and than make small changes where you can prove correctness by code review (and have tests that help validate functionality).

If you concerned about particular piece of code sometimes you can intentionally slow down the code (i.e. Sleep(1000) in constructor/callback) to force particular timings of the code.

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179