Memory leak from .NET plug-in

I recently started with C++ plug-in development using the debug Rhino exectuable and noticed that when I have a certain .NET plug-in loaded that I get memory leaks reported. The amount of bytes is 2208 (many times) and when using LookForLeaks with that number, I get:

   ON_UuidList::Array 
   ON_UuidList::Array 
   ON_UuidList::Array 
   ON_UuidList::Array 
   ON_UuidList::Array 
   ON_Xform_Scale 
   unavailable - (Load plug-ins before running LookForLeaks)
   unavailable - (Load plug-ins before running LookForLeaks)
   unavailable - (Load plug-ins before running LookForLeaks)
   unavailable - (Load plug-ins before running LookForLeaks)
   unavailable - (Load plug-ins before running LookForLeaks)

Even if I load the plug-in before I issue LookForLeaks, I do not get more information for the callstack.

Is there more that I can do to get to the bottom of this?

Another one (same stack trace for 192, 168, 120 and 96 byte leak). I am opening a panel and draw some curves in a DisplayConduit when this happens. I am very puzzled by the CRhinoView::OnWindowMeshRepairWizard in the stack trace - I don’t open the mesh repair wizard at all!

LEAK {124583} 0x02AB8AD0, 192 bytes.
   ON_UuidList::Array 
   ON_UuidList::Array 
   ON_UuidList::Array 
   ON_UuidList::Array 
   ON_ClassArray<ON_SimpleArray<TL_CrvSortData> >::AppendNew 
   ON_ClassArray<ON_SimpleArray<TL_CrvSortData> >::AppendNew 
   ON_UuidList::Array 
   ON_UuidList::Array 
   ON_UuidList::Array 
   ON_ClassArray<ON_SimpleArray<TL_CrvSortData> >::AppendNew 
   CRhinoView::OnWindowMeshRepairWizard 
   ON_Xform_Scale 
   ON_Xform_Scale 
   unavailable - (Load plug-ins before running LookForLeaks)
   unavailable - (Load plug-ins before running LookForLeaks)
   unavailable - (Load plug-ins before running LookForLeaks)

Ok, I’ve been doing some more testing with a reasonably complex command from a .NET plug-in.

Running it once gives a total memory leak of 3.5 Mb (650 leaks); running it twice increases this to 8.5 Mb (>4000 leaks).

I reported another memory issue last week and I think this is related.

Hi Menno,

Visual Studio is not capable of tracking some of Rhino’s memory, due to the memory manager we use. That said, it is possible that we’re leaking, or your plug-in is leaking. Can you provide the code to a sample command that you think is leaking when it shouldn’t?

Thanks,

– Dale

Well, I was kinda hoping that using C# would free me from memory leaks, esp. of this magnitude. I will try and get an example in which this behavior is manifested.
To be continued…

Ok, I am reasonably certain this has to do with garbage collection not being run, or not successful upon Rhino exit.

When I run this command and immediately exit Rhino, I get leak messages.

protected override Result RunCommand(RhinoDoc doc, RunMode mode)
{
    Torus t = new Torus(Plane.WorldXY, 20, 4);
            
    NurbsSurface s = t.ToNurbsSurface();
    doc.Objects.Add(s);
    for(int j = 0; j < 5; ++j)
    {
        s = t.ToNurbsSurface();
        Brep b = s.ToBrep();
        doc.Objects.AddBrep(b);
    }
            
    return Result.Success;
}

When I force GC.Collect prior to ending the command, there are no leak messages.

protected override Result RunCommand(RhinoDoc doc, RunMode mode)
{
    Torus t = new Torus(Plane.WorldXY, 20, 4);
            
    NurbsSurface s = t.ToNurbsSurface();
    doc.Objects.Add(s);
    for(int j = 0; j < 5; ++j)
    {
        s = t.ToNurbsSurface();
        Brep b = s.ToBrep();
        doc.Objects.AddBrep(b);
    }
    //FORCE GARBAGE COLLECTION
    GC.Collect();
    GC.WaitForPendingFinalizers();
            
    return Result.Success;
}

Also, if I call dispose on NurbsSurface and Brep instances, there are no leak messages.

protected override Result RunCommand(RhinoDoc doc, RunMode mode)
{
    Torus t = new Torus(Plane.WorldXY, 20, 4);
            
    NurbsSurface s = t.ToNurbsSurface();
    doc.Objects.Add(s);
    s.Dispose();
    for(int j = 0; j < 5; ++j)
    {
        s = t.ToNurbsSurface();
        Brep b = s.ToBrep();
        doc.Objects.AddBrep(b);
        s.Dispose();
        b.Dispose();
    }
            
    return Result.Success;
}

Yes, this seems to be the solution. I have significantly reduced the amount of memory leaked by actively reclaiming resources with Dispose(), and GC.Collect() after the command ends.

When doing the command that previously leaked 3.5 MB per time it was used, now this is down to a few kB.

@stevebaer, can you look into this?

It is not so much a true memory leak as it is a combination of

  1. late garbage collection and
  2. no garbage collection prior to Rhino exit

The second point leads to leak messages when using the debug version of Rhino. To me this makes sense as each geometry object being created (not structs) will allocate memory that is freed when the .NET finalizer is run, this is clear from the RhinoCommon code. If that finalizer is not run prior to exiting Rhino, the unfreed memory is reported as leaks.

I leak when Rhino shuts down on purpose. The classes that can hold userdata are not deleted when Rhino closes. This is because the userdata chains can include classes with information located in DLLs that have already been unloaded when the final .NET finalizer kicks in. We used to have some very difficult to track crashes on shutdown way back before I added the code to just leak and let the operating system reclaim the memory when the process dies.

Thanks @stevebaer for your insight, I can imagine that such crashes are very hard to pin down and that letting the OS reclaim memory is a much more robust solution.