Hi, I've started using sqlobject in a project and I found that it leaks memory after an object derived from SQLObject is destroyed and deleted.
The code is like: obj = SomeObject(parameters...) obj.destroySelf() del obj <at this point we have a mem leak caused by circular references> A simple infinite loop like this: while True: o = SomeObject(parameters...) o.destroySelf() del o will make the program go from 4 Mb to 50 Mb in a couple of minutes. I've found that the problem lies in 2 circular references: one is the instance attribute of sqlmeta which points back to the object and the other is the soObject attribute of SQLObjectState which also points back to the object. The attached patch fixes the issue (a test script with the above infinite loop stays still at 4Mb no matter for how long I run it). Also attached is a simple script to show the problem. It uses sqlite with in-memory tables. Run it before and after applying the patch to see the differences. -- Dan
diff -ur /usr/lib/python2.3/site-packages/sqlobject/main.py sqlobject/main.py --- /usr/lib/python2.3/site-packages/sqlobject/main.py 2005-10-02 01:59:35.000000000 +0300 +++ sqlobject/main.py 2006-05-27 04:24:26.000000000 +0300 @@ -32,6 +32,7 @@ USA. """ +import weakref import threading import sqlbuilder import dbconnection @@ -236,7 +237,7 @@ setattr(cls, attr, None) def __init__(self, instance): - self.instance = instance + self.instance = weakref.proxy(instance) def setClass(cls, soClass): cls.soClass = soClass @@ -1501,7 +1502,7 @@ class SQLObjectState(object): def __init__(self, soObject): - self.soObject = soObject + self.soObject = weakref.proxy(soObject) self.protocol = 'sql'
#!/usr/bin/env python import gc def memory_dump(): print "\nGARBAGE:" gc.collect() print "\nGARBAGE OBJECTS:" for x in gc.garbage: s = str(x) if len(s) > 80: s = s[:77] + '...' print type(x), "\n ", s gc.enable() gc.set_debug(gc.DEBUG_LEAK) from sqlobject import * connection = connectionForURI('sqlite:/:memory:') sqlhub.processConnection = connection class TestClass(SQLObject): class sqlmeta: cacheValues = True lazyUpdates = False name = StringCol(length=255, notNone=True, alternateID=True) value = PickleCol(default=None) ## indexes name_idx = DatabaseIndex('name') TestClass.createTable() obj = TestClass(name='foo', value='bar') obj.destroySelf() del obj memory_dump()