# HG changeset patch # User Sylvain Thénault # Date 1244200891 -7200 # Node ID fb156d69bfd9ba7cdf99ceb16f1391d8e7012a5f # Parent 0b9b0bdc93f5726f6111e8a8f5f405278fdc2cfe change extlite connection handling: connection may not be shared among threads but it's fine to have multiple connections open in several threads (as demonstrated by unittest_extlite) diff -r 0b9b0bdc93f5 -r fb156d69bfd9 server/sources/extlite.py --- a/server/sources/extlite.py Fri Jun 05 10:40:54 2009 +0200 +++ b/server/sources/extlite.py Fri Jun 05 13:21:31 2009 +0200 @@ -8,8 +8,6 @@ __docformat__ = "restructuredtext en" -import time -import threading from os.path import join, exists from cubicweb import server @@ -17,25 +15,11 @@ from cubicweb.server.sources import AbstractSource, native from cubicweb.server.sources.rql2sql import SQLGenerator -def timeout_acquire(lock, timeout): - while not lock.acquire(False): - time.sleep(0.2) - timeout -= 0.2 - if timeout <= 0: - raise RuntimeError("svn source is busy, can't acquire connection lock") - class ConnectionWrapper(object): def __init__(self, source=None): self.source = source self._cnx = None - @property - def cnx(self): - if self._cnx is None: - timeout_acquire(self.source._cnxlock, 5) - self._cnx = self.source._sqlcnx - return self._cnx - def commit(self): if self._cnx is not None: self._cnx.commit() @@ -45,7 +29,9 @@ self._cnx.rollback() def cursor(self): - return self.cnx.cursor() + if self._cnx is None: + self._cnx = self.source._sqlcnx + return self._cnx.cursor() class SQLiteAbstractSource(AbstractSource): @@ -87,11 +73,6 @@ self._need_full_import = self._need_sql_create AbstractSource.__init__(self, repo, appschema, source_config, *args, **kwargs) - # sql database can only be accessed by one connection at a time, and a - # connection can only be used by the thread which created it so: - # * create the connection when needed - # * use a lock to be sure only one connection is used - self._cnxlock = threading.Lock() @property def _sqlcnx(self): @@ -164,11 +145,10 @@ has a connection set """ if cnx._cnx is not None: - try: - cnx._cnx.close() - cnx._cnx = None - finally: - self._cnxlock.release() + cnx._cnx.close() + # reset _cnx to ensure next thread using cnx will get a new + # connection + cnx._cnx = None def syntax_tree_search(self, session, union, args=None, cachekey=None, varmap=None, debug=0): diff -r 0b9b0bdc93f5 -r fb156d69bfd9 server/test/unittest_extlite.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/server/test/unittest_extlite.py Fri Jun 05 13:21:31 2009 +0200 @@ -0,0 +1,62 @@ +import threading, os, time + +from logilab.common.testlib import TestCase, unittest_main +from logilab.common.db import get_connection + +class SQLiteTC(TestCase): + sqlite_file = '_extlite_test.sqlite' + def setUp(self): + cnx1 = get_connection('sqlite', database=self.sqlite_file) + print 'SET IP' + cu = cnx1.cursor() + cu.execute('CREATE TABLE toto(name integer);') + cnx1.commit() + cnx1.close() + + def tearDown(self): + try: + os.remove(self.sqlite_file) + except: + pass + def test(self): + lock = threading.Lock() + + def run_thread(): + print 'run_thread' + cnx2 = get_connection('sqlite', database=self.sqlite_file) + lock.acquire() + print 't2 sel1' + cu = cnx2.cursor() + cu.execute('SELECT name FROM toto') + self.failIf(cu.fetchall()) + cnx2.commit() + print 'done' + lock.release() + time.sleep(0.1) + lock.acquire() + print 't2 sel2' + cu.execute('SELECT name FROM toto') + self.failUnless(cu.fetchall()) + print 'done' + lock.release() + + cnx1 = get_connection('sqlite', database=self.sqlite_file) + lock.acquire() + thread = threading.Thread(target=run_thread) + thread.start() + cu = cnx1.cursor() + print 't1 sel' + cu.execute('SELECT name FROM toto') + print 'done' + lock.release() + time.sleep(0.1) + cnx1.commit() + lock.acquire() + print 't1 insert' + cu.execute("INSERT INTO toto(name) VALUES ('toto')") + cnx1.commit() + print 'done' + lock.release() + +if __name__ == '__main__': + unittest_main()