Open CASCADE, the 3D modelling kernel
3D modeling & numerical simulation

Search the Forums
See All Topics
 

NaroCAD wrapper memory problem

NaroCAD wrapper memory problem
Massimiliano Olivieri 2009/12/09 21:47
I'm just started to use NaroCAD wrapper. The wrapper seems
wery good, but i'm think that there is some problem with memory management.
I see that the memory of the application always increase and
isn't never deallocated. I quite sure that this is due to the
OCWrapper.

I made some test with a simple test application using OCWrapper
which make only simple cycles like this:

for (n = 0; n < 1000; n++)
{
OCBRepPrimAPI_MakeBox b1 = new OCBRepPrimAPI_MakeBox(100.0, 200.0, 300.0);
OCTopoDS_Shape sh = b1.Shape();
}

or this:

OCgp_Pnt p1 = new OCgp_Pnt(10, 15, 20);
OCgp_Pnt p2 = new OCgp_Pnt(30, 40, 50);
for (n = 0; n < 1000; n++)
{
OCGC_MakeSegment mkSegment = new OCGC_MakeSegment(p1, p2);
if (mkSegment.IsDone() == true)
{
OCGeom_TrimmedCurve segmentToWrite = mkSegment.Value();
}
}

In fact also in this sample code i see that the memory always increases and is neve deallocated. The same cycles done without OCWrapper in native code does not have the problem.

I made some more test by using Dispose() method to call destructor provided by OCWrappers in the same sample
application like this:

for (n = 0; n < 1000; n++)
{
OCBRepPrimAPI_MakeBox b1 = new OCBRepPrimAPI_MakeBox(100.0, 200.0, 300.0);
OCTopoDS_Shape sh = b1.Shape();
b1.Dispose();
sh.Dispose();
}

OCgp_Pnt p1 = new OCgp_Pnt(10, 15, 20);
OCgp_Pnt p2 = new OCgp_Pnt(30, 40, 50);
for (n = 0; n < 1000; n++)
{
OCGC_MakeSegment mkSegment = new OCGC_MakeSegment(p1, p2);
if (mkSegment.IsDone() == true)
{
OCGeom_TrimmedCurve segmentToWrite = mkSegment.Value();
segmentToWrite.Dispose();
}
mkSegment.Dispose();
}

I both cases the call generates a crach in the application.
Do you have some suggestions to give me?

Thank you
Ciprian 2009/12/10 05:32
Hi Massimiliano,

Yes, I'm aware that NaroCAD have leaks. There is an attached bug report for it on sourceforge page: http://sourceforge.net/tracker/?func=detail&aid=2891858&group_id=198020&atid=963798 that describe the problem.

The building of wrappers in themselves take a lot of time and if you will wanna get assistance how to build wrappers and to contribute back fixes on that part (of targeting memory leaks) please to notice me or bxtrx.

Thank you for feedback,
Ciprian
Massimiliano Olivieri 2009/12/15 19:19
I made some more tests to try to understand memory problems described.
Destructors into the OC wrapper code (called when Dispose() method is used
into C# application) have two different type of solution:

1) Delete of nativeHandle only like this:

~OCTopoDS_Shape()
{
delete nativeHandle;
}

2) Call Nullify on nativeHandle and then delete nativeHandle like this:

~OCGeom_TrimmedCurve()
{
nativeHandle->Nullify();
delete nativeHandle;
}

First of all i cannot understand when Nullify should be used. I suppose
that
OCC transient class can be deallocated automatically by OCC memory
management strategy based on smart pointer and reference counters
even if Nullify is not called.
Second, after delete, nativeHandle is not assigned to a null value, so in
a case, for example, an OCGeom_TrimmedCurve is deleted, more than one
destructor is called by the system:
~OCGeom_TrimmedCurve(), ~OCGeom_BoundedCurve() from which
OCGeom_TrimmedCurve() is derived and so on, up to the base class ....
All these desctructor call a delete on the same nativeHandle pointer and
more a Nullify().
I suppose that this could be the cause of the crash I see when I call
Dispose() on a OCWrapper class into my C# application.
So I have modified all OC Wrapper destructor in the following way:

~classeName
{
if (nativeHandle != 0)
{
delete nativeHandle;
nativeHandle = 0;
}
}

This modification solve the crash problems I described. In the sample
application i mentioned before, i see that i can call Dispose() without
any crash and furthemore I noticed that in a cycle like the following one
memory does't increase:

for (n = 0; n < 1000; n++)
{
OCBRepPrimAPI_MakeBox b1 = new OCBRepPrimAPI_MakeBox(100.0, 200.0,
300.0);
OCTopoDS_Shape sh = b1.Shape();
b1.Dispose();
sh.Dispose();
}

Unfortunately, in the second case below I noticed no crashes but the
memory
still increase and is never deallocated.

OCgp_Pnt p1 = new OCgp_Pnt(10, 15, 20);
OCgp_Pnt p2 = new OCgp_Pnt(30, 40, 50);
for (n = 0; n < 1000; n++)
{
OCGC_MakeSegment mkSegment = new OCGC_MakeSegment(p1, p2);
if (mkSegment.IsDone() == true)
{
OCGeom_TrimmedCurve segmentToWrite = mkSegment.Value();
segmentToWrite.Dispose();
}
mkSegment.Dispose();
}

With some more test I found that not all OCC classes provide virtual
destructor.
This could be the cause of the problem of memory increasing noticed in a
cycle like this

OCgp_Pnt p1 = new OCgp_Pnt(10, 15, 20);
OCgp_Pnt p2 = new OCgp_Pnt(30, 40, 50);
for (n = 0; n < 1000; n++)
{
OCGC_MakeSegment mkSegment = new OCGC_MakeSegment(p1, p2);
if (mkSegment.IsDone() == true)
{
OCGeom_TrimmedCurve segmentToWrite = mkSegment.Value();
segmentToWrite.Dispose();
}
mkSegment.Dispose();
}

Here, OCGC_MakeSegment uses a nativeHandle on type GC_Root *.
This class hasn't a virtual destructor into OCC so when OC wrapper
in the ~OCGC_MakeSegment call delete nativeHandle, the destructor
of the base class GC_Root is called and not the destructor of
GC_MakeSegment derived class as should be. I think that this problem
can be solved by using an explicit cast when calling delete like this:

delete ((GC_MakeSegment*)nativeHandle);

instead of

delete nativeHandle;

I saw that at least in the case of my example this kind of delete call
solve the problem of memory increasing.

I would like to ask to the OC wrapper developers what they think about
these notes. If the solution that a I found and tested on a specific case
may be valid in general, I ask if it would be possible to take them into
account in the wrapper generator code in order to create destructors
that provide check against 0 (nullptr) and cast as I described above.
What do you think about?
Ciprian Mustiata 2009/12/16 13:05
Hi Massimiliano ,

I put as a start note: I've updated a tool that I wanted to use for
investigation of the problem and I hope it will help you a bit in fixing
wrappers.
The tool is commited under: narocad's repository
\branches\CipTests\FinalizerReplacer\
This project will scann mostly for ~<className> and replace the destructor
sequence with a finalizer/destructor pair.

There are three memory models working in NaroCAD wrappers/OpenCascade
world.
1st. There is the C++ ordinary memory model: Constructor/destructor block.
All wrapper C# class have a member nativeHandle that are in fact C++
OpenCascade classes that "obey" to this memory model
2nd. The OpenCascade Handle mecanism. Handle_ <className> or
Handle(<className>) macro creates a smart pointer. Just in case you're not
used with them, a smart pointer is a class that stores the reference
counter to the contained class. Every time other handle is asigned to a
previous handle, the reference count is increased, every time this handle
is destroyed in a context, the reference counter is decreased. Based on
this, the destructor is called invisibly when "no one use this variable"
and the reference count is zero.
3rd model is the .NET VM one. Is like in any Garbage Collected object
model: a object exists in memory and runs until garbage collector happen
and this object have no other object pointing to it. This third model apply
to ALL OCWrappers wrapping classes.

So how destruction happen:
- NaroCAD (or any OCWrapper user) uses an OpenCascade class. For this
creates a .NET object that creates internally an internal handle named
nativeHandle and all calls are proxed to it. After NaroCAD uses that class,
the object is dangling in memory until garbage collection occurs. When GC
occurs, the finalizer should be called. As for now because are not
implemented finalizers, nothing is called and is a leak. If the finalizers
(defined in C++.NET as !<ClassName>() instead ~<ClassName>() as in C++) are
called they should destruct the object using C++ memory models of 1 or 2.

When happen the 1st memory model is clear. The native object under is
destroyed.
As of today anyway, the wrappers code was a bit broken and instead to be
called {delete nativeHandle;} only in base class, was called all over the
hierarchy. The first fix on wrappers that will fix the 1st type of C++
alloc model is just to create nativeHandle in base of the hierarchy and do
the delete nativeHandle; just in the base class. In this way will not
happen a crash because there will be twice a delete nativeHandle: both in
derived (subclassed) class and in the base class.

For the 2nd memory model: it must be assured that handles are destroyed
just right and this is the part I did not get enough time to investigate.
The Nullify call will simply reduce the reference count to zero and was
used as delete nativeHandle; in a handle class will not remove the
contained object, but will only reduce the reference count by one. I think
that Nullify call is not needed, and only in the base class
(OCStandard_Transient!?) to happen the delete nativeHandle; and that's all.


Hope it will clear a bit how and why it works in this way, if not, please
at any given time to feedback here.
Thomas Paviot 2009/12/10 13:07
Hi Massimiliano,

Thanks for asking this so essential question! I've been working for more than 18 months on a python wrapper to the OCC C++ API, know as pythonOCC. I don't know anything in C#, and never used NaroCAD, but I however face exactly the same memory issues. According to me, this problem is not just a bug, but actually the big deal. To overcome these leaks, a garbage collector was implemented and will be available in the upcoming pythonOCC release.

Best Regards,

Thomas
 
 
Latest news
  • OCCT Applications
  • Open CASCADE Technology 6.8.0 is available for download!
  • New features to enhance the development process

  • © OPEN CASCADE 2000 - 2014  |  Search  |  Contacts   |  Site map