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