VContainer icon indicating copy to clipboard operation
VContainer copied to clipboard

DiagnosticsCollector provokes memory leaks

Open Maesla opened this issue 11 months ago • 4 comments

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);
    }
}

image

Maesla avatar Mar 15 '24 15:03 Maesla