VContainer
VContainer copied to clipboard
DiagnosticsCollector provokes memory leaks
I was having a strange behavior with some instances not destroyed and some memory leaks. Then I realized that this behavior was only occurring when VContainerSettings.DiagnosticsEnabled = true.
I have done a Test trying to isolate and simplify my case and indeed, aparrently the Diagnostics is creating some memory leak, not allowing the gc to destroy some instances
using NUnit.Framework;
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.TestTools;
using VContainer;
using VContainer.Diagnostics;
public class MemoryLeakTests
{
private class DestroyCount
{
public int Count { get; private set; }
public DestroyCount()
{
Count = 0;
}
public void IncreaseCount(string msg)
{
UnityEngine.Debug.Log(msg);
Count++;
}
}
private interface IFoo{}
private class FooA : IFoo
{
private readonly DestroyCount destroyCount;
public FooA(DestroyCount destroyCount)
{
this.destroyCount = destroyCount;
}
~FooA()
{
destroyCount.IncreaseCount("FooA destroyed");
}
}
private class FooB : IFoo
{
private readonly DestroyCount destroyCount;
public FooB(DestroyCount destroyCount)
{
this.destroyCount = destroyCount;
}
~FooB()
{
destroyCount.IncreaseCount("FooB destroyed");
}
}
private class FooFactory
{
public IFoo foo { get; set; }
}
static bool[] diagnosticsValues = new bool[] { true, false};
[UnityTest]
public IEnumerator MemoryLeakWithDiagnosisTest([ValueSource(nameof(diagnosticsValues))] bool useDiagnostics)
{
DestroyCount destroyCount = new DestroyCount();
var builder = new ContainerBuilder();
DiagnosticsCollector diagnosticsCollector = useDiagnostics ? DiagnositcsContext.GetCollector("test") : null;
builder.Diagnostics = diagnosticsCollector;
builder.Register<FooFactory>(Lifetime.Singleton);
builder.Register<IFoo>(container => container.Resolve<FooFactory>().foo, Lifetime.Transient);
var resolver = builder.Build();
{
FooFactory fooFactory = resolver.Resolve<FooFactory>();
fooFactory.foo = new FooA(destroyCount);
}
{
IFoo fooA = resolver.Resolve<IFoo>();
Assert.That(fooA, Is.InstanceOf<FooA>());
}
{
FooFactory fooFactory = resolver.Resolve<FooFactory>();
fooFactory.foo = new FooB(destroyCount);
}
{
IFoo fooB = resolver.Resolve<IFoo>();
Assert.That(fooB, Is.InstanceOf<FooB>());
}
//uncomment this line to pass both tests
//diagnosticsCollector?.Clear();
GC.Collect();
yield return new WaitForSeconds(20f);
Assert.That(destroyCount.Count, Is.EqualTo(1));
}
[UnityTest]
public IEnumerator MemoryLeakGroundTruth()
{
DestroyCount destroyCount = new DestroyCount();
FooFactory fooFactory = new FooFactory();
fooFactory.foo = CreateFooA(destroyCount);
fooFactory.foo = CreateFooB(destroyCount);
GC.Collect();
yield return new WaitForSeconds(10f);
Assert.That(destroyCount.Count, Is.EqualTo(1));
}
private IFoo CreateFooA(DestroyCount destroyCount)
{
return new FooA(destroyCount);
}
private IFoo CreateFooB(DestroyCount destroyCount)
{
return new FooB(destroyCount);
}
}