Problem with rapid memory build-up

When the below code is run, if I check the memory usage of the python process, it is increasing very rapidly. This code is a simple example of what I am trying to do in my program; it isolates the problem. As you can see, I am initializing an OnscreenImage in a loop within a Task. I then do everything I can (more than should be necessary) to delete the object immediately after its creation, but somehow memory is still building up very fast, to the point where my program is running unacceptably slow after 30 seconds or less. Does anyone know what is causing this issue or how to avoid it?

def MemoryLeakTestTask(task):
    for i in range(100):
        image = OnscreenImage(MinimapImageFolder+'/LightGray.jpg', parent = render)
        image.destroy()
        image.removeNode()
        del image
    return Task.cont

taskMgr.add(MemoryLeakTestTask, "test")

run()

Thanks. I appreciate any help.

Hmm, interesting. I will try to reproduce.

David

try something like this, i havnt tried this out, but this way should work…


taskMgr.add(MemoryLeakTestTask, "test") 
def MemoryLeakTestTask(self,task): 
    i = 1
    self.image = [None for i in range(100)]
    for i in range(100): 
        self.image[i] = OnscreenImage(MinimapImageFolder+'/LightGray.jpg', parent = render) 
    self.remove()
    return Task.cont 
def remove(self):
    for i in range(100): 
        self.image[i].remove()


run()

Unfortunately that did not work

:open_mouth: , you are right, thats strange! sorry i cant figure out anything…
i deleted everything also the globals, but the task with onscreen image is still sucking memory.

im now very curious too, how to fix this.

The bug is in texture loader.
In OnscreenImage.py, find this line :

tex = loader.loadTexture(image)

After that line, do “print id(tex)”, and you’ll see that it advances, indicating you’re getting a new copy of the same texture object in RAM. It should return the already loaded texture instead, so somehow it’s broken.
I got around it by passing Filename(image) instead of plain string.

very cool! interesting, that you dont get the same result by passing, i thought the complete onscreen is bugy. are u by passing the file in the task or seperate to?

anyway if its srperate to, thx jo, i can reference my images seperate to the task. really, very cool, if it works. nice mind!

Passing Filename() to OsI constructor or to loadTexture() call in OnscreenImage.py doesn’t cover the real hole. To cover it completely (on python side) is by passing Filename() to all TexturePool.loadTexture() calls in Loader.py.

it works :slight_smile:

im doing it a bit different to you, im referencing my files in a “step in” procedere. and using the task only for render it.

but it was your mind, or you brought me on this mind, really cool :slight_smile:

so for your example (dont init the onscreenImage in the task):

        taskMgr.add(self.test1,"test")
        self.image = OnscreenImage()
        self.image.setImage('guiDUMMY.png')
    def test1(self,task):
       
            for i in range(100): 
                 self.image.reparentTo(render2d)
            
            self.image.ignore(self.image)
           # self.remove()
            
            return task.cont

hmmm, anything scream in me i allready figured this out years ago, damn again LOL

anyway, im using this procedere for my dynamic gui, i love this idea…

Ah, many thanks for researching the problem! It is indeed a serious bug in Panda; I’ll be checking in a fix shortly.

The nature of the bug is a Python reference-count leak. When the interrogate-generated code attempted to coerce a string object into a Filename object automatically for the call into TexturePool.loadTexture(), it was inadvertently leaking the created Filename object. (This despite my best efforts when I wrote the code in the first place. Foo!)

Note that this diagnosis:

is actually a little misleading. This is the normal behavior (you’ll notice that id(tex) advances even in the non-leaking case). In spite of the advancing id(tex) values, each call to loader.loadTexture() is still returning the same Texture object, and you can see this with tex.this, which reports the actual C++ “this” pointer. It is, however, returning a unique Python wrapper object each time, which is why id(tex) is always different. This is the correct behavior, though; and these wrapper objects aren’t leaking, so this wasn’t the fundamental problem. The actual leak was from an internal object that never got returned to the user.

David

Sweet, I got it working. Thank you everyone for all of your help. Below is a sample of code that does not leak memory, but still allows initialization of OnscreenImages within the Task.

class MemoryLeakTest(object):
    
    def __init__(self):
        taskMgr.add(self.MemoryLeakTestTask, "test")
        self.tex = loader.loadTexture(Filename(MinimapImageFolder+'/LightGray.jpg'))
    
    def MemoryLeakTestTask(self,task):
         for i in range(100):
            image = OnscreenImage(self.tex)
            image.removeNode()
         return Task.cont
    
MemoryLeakTest()
run()

In this process I discovered another memory leak problem, with the setPos() function, which I will start another thread about soon.

Sounds like it’s probably the same bug. The fundamental memory leak was with any code that used an implicit coercion, so if you pass a 3-tuple to setPos() instead of a Point3, it has to be implicitly coerced–and therefore leaks.

Note that I’ve fixed the leak on the cvs trunk, and the fix will be included in whatever version of Panda is next released.

David

Oh okay. I won’t start another thread then. Thanks for the help.

yes, this is the way which jo figured out, but it needs more bits.