Hi Thomas

After much investigation today, I have now added a flag to GarbageCollecter to disable it for our use, as it really does not work as intended for relatively complex code (I now manually track which objects I create and need to call _kill_pointed for). There are two main issues that we run into with the current GarbageCollector:

1. As already discussed, classes like the BRepPrimAPI and BRepAlgoAPI have an internal "myShape" data member that gets destroyed when the parent object's _kill_pointed method is called, causing all kinds of issues. As an example, it makes utility functions like the one below unusable, as the local BRepPrimAPI object goes out of scope and is thus collected by GarbageCollector for purging, even though the caller of the function may want to use the resulting shape for some time to come, after the parent is "purged":

def MakeEdge(p1, p2):
    gp1,gp2 = [gp_Pnt(x,y,z) for x,y,z in (p1,p2)]
    mkedge = BRepBuilderAPI_MakeEdge(gp1, gp2)
    return mkedge.Shape()

2. Every time a C++ OCC object is retrieved from Python in any way, a brand new SWIG wrapper object is instantiated for it, very easily/quickly leading to multiple wrapper objects on the Python side for the same underlying C++ object (I see this especially when using OCC shape containers repeatedly). When any one of these wrapper objects goes out of scope and is collected and purged, the underlying C++ object is destroyed (via _kill_pointed), even though there may well be other Python wrapper objects still needing access to it. I guess the ideal solution to this issue would be to keep a mapping of C++ object -> SWIG wrapper object on the C++ side, so that there is always a 1-to-1 correspondence between the two, but I'm not sure how plausible this really is from an implementation point-of-view. As an example of what I mean, consider this: every time you call "mkedge.Shape()" in my example above, you get a new/different Python object, wrapping the same underlying myShape instance, which is totally unexpected and unintuitive from a Python perspective. A "pure" Python programmer would expect to get the exact same Python object back every time, and not different wrapper objects with just an "artificial" identical hash value.

I realize that this is a very difficult issue, and that as users of the PythonOCC wrapper library we will need to take responsibility for memory management issues, but I can see lots of confusion for Python programmers using the library and expecting "Python-like" behaviour out of the box.

I wish I new more about the SWIG wrapper object instantiation process so that I could make more concrete suggestions, instead of just complaining ;-) What is your gut-feeling about this Thomas - do you think these two issues can be addressed in the wrapper, or is the current way the only viable implementation? If you can give me a short description of the SWIG wrapper instantiation process, I may be able to delve into it myself.

Thanks for your help with this thus far,
Frank



On 16/03/2010 9:04 AM, Thomas Paviot wrote:
Hi Frank,

I also suggest you use the push_context() and pop_context() methods at the beginning and the end of your algorithm. garbage.push_context() creates a new collector. Objects collected will go to this new list. Then, at the end of your algorithm, just call .smart_purge() and then pop_context(). Otherwise, when calling the smart_purge() method, you'll erase all other objects that might have been stored in the gc thus causing strange things.

def my_high_memory_consuming_algorithm():
    GarbageCollector.garbage.push_context()

    ... do whatever you want ...

GarbageCollector.garbage.smart_purge() #only the objects created since the call to push_context are erased. Use MMGT_OPT=0 to make sure freeing #memory.
    GarbageCollector.pop_context()
    return data

There's also a garbage.prevent_object(object) method, that will take care to not delete an object that it's important to keep reference.

These three functions are quite new (but validated with a unittest). I'm curious to have your feedback!

Regards,

Thomas

These two functions are quite new,
2010/3/16 Frank Conradie <fr...@qfin.net <mailto:fr...@qfin.net>>

    Hi Thomas

    Well, I'm glad it's not a bug then! I can think of a few ways to
    handle this situation in my algorithm, so it should not be a
    problem. I'm just wondering how many other classes behave in this
    "unexpected" way :O

    Cheers,
    Frank


    On 15/03/2010 10:26 PM, Thomas Paviot wrote:
    Hi Frank,

    This behaviour does not come from SWIG but from OpenCASCADE
    itself. Let me explain you in a few words what happens here.

    The BRepPrimAPI_MakeBox class inherits from the
    BRepBuilderAPI_MakeShape one. This class provides the Shape()
    method you can find in BRepPrimAPI_Make* basic geometry
    constructors. If you have a look at  the Shape() method in the
    file BRepBuilderAPI_MakeShape.cxx, you will read:

    """
    //=======================================================================
    //function : Shape
    //purpose  :
    //=======================================================================

    const TopoDS_Shape&  BRepBuilderAPI_MakeShape::Shape() const
    {
      if (!IsDone()) {
        // the following is const cast away
        ((BRepBuilderAPI_MakeShape*) (void*) this)->Build();
        Check();
      }
      return myShape;
    }
    """

    The Shape() method then returns a reference to the protected
    attribute 'myShape' of the class BRepBuilderAPI_MakeBox. In other
    words, if you delete the BRepPrimAPI_MakeBox instance, you loose
    the reference to the shape.

    For instance:

    >>> from OCC.BRepPrimAPI_MakeBox import *
    >>> from OCC.TopoDS import *
    >>> box = BRepPrimAPI_MakeBox(10,10,10)
    >>> shp  = box.Shape()
    >>> print shp.IsNull()
    0
    >>> del box #object is garbage collected, not deleted yet
    >>> print shp.IsNull()
    0
    >>> GarbageCollector.garbage.smart_purge() # all objects are
    deleted, loose the reference to myShape attribute
    >>> print shp.IsNull()
    1

    Then force objects deletion only if you're sure you don't need
    the shapes anymore. I don't know what you're trying to do
    exactly, so it's difficult to suggest any best practice.

    Regards,

    Thomas

    2010/3/15 Frank Conradie <fr...@qfin.net <mailto:fr...@qfin.net>>

        Hi Thomas

        I am getting crash behaviour in the interim 0.5 PythonOCC
        release,
        related to the new GarbageCollector implementation. The code
        below
        crashes on my PC:

        ----------------------------------------
        from OCC.gp import *
        from OCC.BRepPrimAPI import *
        from OCC.BRepBuilderAPI import *
        from OCC.TopExp import *
        from OCC.TopoDS import *

        o = 0.0
        w = 0.1
        fp1 = (o,o,o)
        fp2 = (w,w,w)

        mkbox = BRepPrimAPI_MakeBox(gp_Pnt(fp1[0],fp1[1],fp1[2]),
        gp_Pnt(fp2[0],fp2[1],fp2[2]))
        s = mkbox.Shape()
        del mkbox

        print s.ShapeType()
        GarbageCollector.garbage.smart_purge()
        # The next line crashes my system
        print s.ShapeType()
        ----------------------------------------

        It is almost as if calling _kill_pointed for the mkbox causes
        shape "s"
        to become "corrupted" in some way. I dug through the SWIG
        code a bit,
        but cannot see anything obviously wrong, so I was hoping you
        could shed
        some light on the issue.

        By the way, I did get the SVN compiled myself as well, and
        get the same
        crash with my compiled lib as well as the one you made
        available on your
        website.

        Thanks,
        Frank


        _______________________________________________
        Pythonocc-users mailing list
        Pythonocc-users@gna.org <mailto:Pythonocc-users@gna.org>
        https://mail.gna.org/listinfo/pythonocc-users



    _______________________________________________
    Pythonocc-users mailing list
    Pythonocc-users@gna.org  <mailto:Pythonocc-users@gna.org>
    https://mail.gna.org/listinfo/pythonocc-users

    _______________________________________________
    Pythonocc-users mailing list
    Pythonocc-users@gna.org <mailto:Pythonocc-users@gna.org>
    https://mail.gna.org/listinfo/pythonocc-users



_______________________________________________
Pythonocc-users mailing list
Pythonocc-users@gna.org
https://mail.gna.org/listinfo/pythonocc-users
_______________________________________________
Pythonocc-users mailing list
Pythonocc-users@gna.org
https://mail.gna.org/listinfo/pythonocc-users

Reply via email to