[Fixed]Bug #3993 - Memory leak in python3

I’ve noticed a memory leak in Path that caused my machine to go OOM overnight a couple times.
DeepSOIC showed that the leak shows up in Part-o-Magic as well if there’s a lot of objects in the tree.

I started disabling parts of Path to track it down and finally got it to where I can reproduce the leak entirely outside Path with a macro.
To reproduce,

  1. start FreeCAD clean
  2. Execute the macro. This will create a 1000 cubes in the tree, create a command and workbench.
  3. Watch memory usage for a minute or so and it should stabilize.
  4. Switch to the new workbench and the memory will start climbing and not stabilize.

The bug seems to be related to the isActive() code. I can disable this and the leak doesn’t happen. I think the leak is particularly noticable in Path because we use a use a lot of these isActive implementations.

The leak doesn’t seem to happen in the Sketcher WB or any other workbench that I tested. Perhaps it’s python related?

import FreeCAD,FreeCADGui

class JunkWorkbench (Workbench):
    "Junk workbench"

    def __init__(self):
        self.__class__.Icon = FreeCAD.getResourceDir() + "Mod/Path/Resources/icons/PathWorkbench.svg"
        self.__class__.MenuText = "Junk"
        self.__class__.ToolTip = "Junk workbench"

    def Initialize(self):
        self.appendToolbar("junk", ['MyCommand1'])

class MyTool:
    "My tool object"

    def Initialize(self):
        "This function is executed when FreeCAD starts"
        self.list = ["MyCommand1"] 
        self.appendToolbar("My Commands",self.list) 
        
    def GetResources(self):
        return {"MenuText": "My Command",
                "Accel": "Ctrl+M",
                "ToolTip": "My extraordinary command"}


    def IsActive(self):
        if FreeCAD.ActiveDocument is not None:
            for o in FreeCAD.ActiveDocument.Objects:
                if o.Name[:3] == "Job":
                        return True
        return False

    def Activated(self):
        pass
            # do something here...

# Add a bunch of objects to the tree
for i in range(1000):
  App.ActiveDocument.addObject('Part::Box')

# Create a command and a workbench and add the icon to a toolbar.
FreeCADGui.addCommand('MyCommand1',MyTool())
FreeCADGui.addWorkbench(JunkWorkbench())

The leak doesn’t seem to happen in the Sketcher WB or any other workbench that I tested. Perhaps it’s python related?

I can confirm this behaviour with a Python3 build while with Python2 it doesn’t happen.

The relevant part of the leak is this part of the IsActive() function: if o.Name[:3] == “Job”:
When I modify it to

    def IsActive(self):
        if FreeCAD.ActiveDocument is not None:
            for o in FreeCAD.ActiveDocument.Objects:
                if False:
                    return True
        #        if o.Name[:3] == "Job":
        #                return True
        return False

then I don’t see a leak.

0003993

Note to myself
Possible things to check:

  • Check Python API if behaviour of reference counting has changed
  • Check for possible problems in PyCXX

The tracking of attributes in PyObjectBase doesn’t seem to cause it because when disabling it the leak still occurs.

Further testing revealed that apparently the class Py::String is the problem. When I modify the function DocumentObjectPy::staticCallback_getName to e.g. do return PyUnicode_FromString(“Name”); then no leak can be observed.

Fixed with 3c205946e