Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NSubstitute keeps reference to GC collectable objects while detached from events #759

Closed
Jekops opened this issue Dec 19, 2023 · 1 comment

Comments

@Jekops
Copy link

Jekops commented Dec 19, 2023

Describe the bug

In an IOC environment, a child object exposes some events, the parent object start listening to these events, and stops when disposed.
When the parent is disposed an event detach is performed, but NSubstitute keeps some reference and do not allow the parent object to be collected from the GC, leading in memory leaks.

To Reproduce
Here you can find an example.
NSubstituteTester.zip

Expected behaviour
When a concrete class detach the events from a substitute child class, the concrete class should be able to be collected by the GC.

Environment:

  • NSubstitute version: 5.1.0
  • NSubstitute.Analyzers version: 1.0.16
  • Platform: net6.0 on Windows
alexandrnikitin added a commit that referenced this issue Dec 23, 2023
@alexandrnikitin
Copy link
Member

Hi @Jekops,
Thank you for the great repro! I copied the relevant parts below

public class Parent : IDisposable
{
    private IChild m_Child;

    public Parent(IChild child)
    {
        m_Child = child;
        m_Child.Changed += Child_Changed;
    }

    public void Dispose()
    {
        m_Child.Changed -= Child_Changed;
        m_Child = null;
    }

    private void Child_Changed(object sender, EventArgs args)
    {
    }
}

public class ParentTest : IDisposable
{
    // this field holds a reference to the substitute created below
   // the object is still available in the Dispose method and not reclaimed by GC yet
    // because this instance of ParentTest is still alive
    private IChild m_Child;
    private Parent m_SUT;

    public ParentTest()
    {
        m_Child = Substitute.For<IChild>();
        m_SUT = new Parent(m_Child);
    }

    public void Dispose()
    {
        m_SUT.Dispose();
        m_SUT = null;
        // 
    }
}

NSubstitute keeps the state and setup in its instances. It's an instance of IChild substitute in this case. When you subscribe to the event with m_Child.Changed += Child_Changed; it also keeps a reference to the current instance of the Parent class.

The observed behavior happens because an instance of the ParentTest test class keep a reference to the substitute and its state in its field m_Child. So it looks like this xUnit -> ParentTest -> Substitute.For<IChild> -> Parent. The field will be reclaimed by GC after the ParentTest (note: oversimplified, GC is a complex beast).

Or you can assign the field to null in your Dispose method then you will get what you want:

public void Dispose()
{
    m_Child = null;
    m_SUT.Dispose();
    m_SUT = null;
}

I hope it helps.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants