Beef icon indicating copy to clipboard operation
Beef copied to clipboard

GC detects leaks for items that are added to the List field of the base class, when the class scope allocated.

Open Zeon8 opened this issue 1 year ago • 2 comments

I have a base class that defines append list and derived class that have a method that allocates new items and adds it to the list. However, after allocating the class on stack and calling that method, after a few seconds GC detects memory leaks, even though the list field has a destructor that deletes all items.

Code example
class Program
{
	static int Main(String[] args)
	{
		var test = scope Test2();
		test.AddItems();
		while(true)
		{
			Thread.Sleep(500);
		}
	}
}

class Test1
{
	public append List<TestItem> Items ~ ClearAndDeleteItems!(_);
}

class Test2 : Test1
{
	public void AddItems()
	{
		Items.Add(new TestItem());
		Items.Add(new TestItem());
	}
}

class TestItem {}

Output: image

Example project

Zeon8 avatar Aug 06 '24 19:08 Zeon8

Think I've found another example of this problem:

public struct A : IDisposable
{
    public List<float> list = new .();

    public void Dispose()
    {
        delete this.list;
    }
}

public struct B : A
{
}

public class C
{
    public B b = .();

    public ~this()
    {
        this.b.Dispose();
    }
}

static
{
    public static void Main()
    {
        C c = new .();

        Console.Write("Sleeping... ");

        System.Threading.Thread.Sleep(3000);

        // Crashes with a memory leak (on A's list) before reaching this call.
        Console.WriteLine("done!");

        delete c;
    }
}

Some additional oddities:

  1. Sleeping for shorter periods of time avoids the crash. From experimentation, 2000 ms seems to be about the threshold where the memory leak stops.
  2. Removing the first Console.Write avoids the crash, even when sleeping for much longer periods of time (say, 10 seconds or longer).
  3. Using an A instance within C (rather than a B instance) avoids the crash:
public class C
{
    // Doesn't leak!
    public A a = .();

    public ~this()
    {
        this.a.Dispose();
    }
}
  1. Composing A within B rather than inheriting avoids the crash:
public struct B : IDisposable
{
    // Doesn't leak!
    public A a = .();

    public void Dispose()
    {
        this.a.Dispose();
    }
}

Grimelios avatar Sep 28 '24 15:09 Grimelios

4589e7ea0ea4121baea76e2a7bc488398a3279bd should fix the @Grimelios issue, which was a simpler and higher-priority fix, but the append object issue remains

bfiete avatar Sep 28 '24 17:09 bfiete

Fixed at d9ce23ac8e5e44c2952c33c5c835ecba9a1df094

bfiete avatar Jan 29 '25 14:01 bfiete