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()

Reply via email to