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) stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Fri, 05 Jun 2009 13:21:31 +0200
branchstable
changeset 2053 fb156d69bfd9
parent 2052 0b9b0bdc93f5
child 2054 277e8d3b1154
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)
server/sources/extlite.py
server/test/unittest_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):
--- /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()