seesharper/LightInject

High memory usage for unit tests

ArnaudB88 opened this issue · 3 comments

Hi,

We recently migrated from StructureMap to Lightinject for a big solution. This solution has +1500 unit tests and we use mstest as testing framework.

With structuremap, we used one single container for all unit tests and did the necessary overrides (mocks/stubs) before and after each test. Since Lightinject doesn't support this, we create and dispose a container for each test.
We have a dbcontext for a connection to the database, so this is registered as scoped. Therefore we create a scope on the container for each unit test and retrieve instances from that scope which are needed for the test.

Some (pseudo)code:

protected IServiceContainer Container;
protected Scope ServiceFactory;

[TestInitialize]
public void Initialize()
{
    Container = new ServiceContainer();
    // assembly scanning for registrations
    foreach(var dllFilePath in Directory.GetFiles(AppContext.BaseDirectory, "MyCompany.MySolution.*.dll"))
    {
        var assembly = Assembly.LoadFrom(dllFilePath);
        Container.RegisterAssembly(assembly);
    }
    //Create Scope
    ServiceFactory = Container.BeginScope();

    //Override default registrations with subbed/mocked variants
    Container.Register<IBar, BarStubbed>();
}
[TestCleanup]
public void CleanUp()
{
    ServiceFactory.Dispose();
    Container.Dispose()
}

[TestMethod]
public async Task ExecuteAsync_Should_Work()
{
    var dto = new FooDto();

    await ServiceFactory.GetInstance<IFoo>().ExecuteAsync(dto);
}

Problem:
When running all +1500 unit tests, the memory usage raised to 9GB. This gave errors in devops because the azure devops build agents only have 7GB of memory.

I began to investigate and came to following conclusions:

  • 1 container for all unit tests gave a constant memory usage (but tests failed because overrides after first test were not possible anymore)
  • 1 container per unit test resulted in a linear raising memory usage (up to 9GB)
  • limiting assembly scanning for container registrations to projects which only have a composition root (6) instead of all our projects in the solution (17) resulted in less memory usage (-38%)
  • not using a scope but only a container resulted in less memory usage (-85%). The scoped registrations were overridden as singleton since each unit test has a single scope.

Some timings (averages):

duration (min) memory (GB) explanation
1 container per test 11.5 9.2 each test: 1 container, 1 scope, assembly scanning 17 projects
limit regs 7.9 5.7 limit assembly scanning to 6 projects, each with composition root
no scope 8.9 1.4 no scope, only container, override scoped registration as singleton
no scope + limit regs 7.6 1.3 combination of both above
1 container for all 1.6 0.2 1 shared container for all tests, contant memory usage, failing tests

Is it possible to investigate this? Currently I'll change the code so we don't use a scope in our unit tests, but the linear increasing memory is still a concern for me.
Thanks!

Hi @ArnaudB88. This might suggest a memory leak related to the scope or services registered as scoped within the container.
Would it be possible for you to create a minimal repro "proving" the memory leak?

I will see if it's possible to make a solution which illustrates this behavior. It's not easy to reproduce problems of a big solution in a simple small solution.
If you want, I can always provide other info (eg. screenshot of memory snapshots)

This issue was solved by the same fix for issue #549 .
(Not using the static container for resolving instances)