[Zope3-checkins] SVN: Zope3/trunk/src/zope/app/rdb/ Store database connections in a thread local instead of a volatile attribute.

Stuart Bishop stuart at stuartbishop.net
Wed Jun 8 03:34:58 EDT 2005


Log message for revision 30682:
  Store database connections in a thread local instead of a volatile attribute.
  This ensures that database adapters created using rdb:provideConnection
  do not share their connections between threads, giving the same behavior
  as these database adapters when they happen to be stored in the ZODB.
  

Changed:
  U   Zope3/trunk/src/zope/app/rdb/__init__.py
  U   Zope3/trunk/src/zope/app/rdb/tests/test_zopedatabaseadapter.py

-=-
Modified: Zope3/trunk/src/zope/app/rdb/__init__.py
===================================================================
--- Zope3/trunk/src/zope/app/rdb/__init__.py	2005-06-08 07:30:35 UTC (rev 30681)
+++ Zope3/trunk/src/zope/app/rdb/__init__.py	2005-06-08 07:34:56 UTC (rev 30682)
@@ -19,7 +19,7 @@
 
 $Id$
 """
-import types, string
+import types, string, time, random, thread
 from types import StringTypes
 from urllib import unquote_plus
 
@@ -39,6 +39,7 @@
 from zope.app.rdb.interfaces import ISQLCommand
 from zope.app.rdb.interfaces import IManageableZopeDatabaseAdapter
 from zope.app.rdb.interfaces import IZopeDatabaseAdapter
+from zope.thread import local
 
 
 def sqlquote(x):
@@ -109,11 +110,31 @@
 class ZopeDatabaseAdapter(Persistent, Contained):
 
     implements(IManageableZopeDatabaseAdapter)
-    _v_connection =  None
 
+    # We need to store our connections in a thread local to ensure that
+    # different threads do not accidently use the same connection. This
+    # is important when instantiating database adapters using
+    # rdb:provideConnection as the same ZopeDatabaseAdapter instance will
+    # be used by all threads.
+    _connections = local()
+
     def __init__(self, dsn):
         self.setDSN(dsn)
+        self._unique_id = '%s.%s.%s' % (
+                time.time(), random.random(), thread.get_ident()
+                )
 
+    def _get_v_connection(self):
+        """We used to store the ZopeConnection in a volatile attribute.
+           However this was not always thread safe.
+        """
+        return getattr(ZopeDatabaseAdapter._connections, self._unique_id, None)
+
+    def _set_v_connection(self, value):
+        setattr(ZopeDatabaseAdapter._connections, self._unique_id, value)
+
+    _v_connection = property(_get_v_connection, _set_v_connection)
+
     def _connection_factory(self):
         """This method should be overwritten by all subclasses"""
         conn_info = parseDSN(self.dsn)
@@ -137,15 +158,13 @@
             except Exception, error:
                 raise DatabaseException, str(error)
 
-
     def disconnect(self):
         if self.isConnected():
             self._v_connection.close()
             self._v_connection = None
 
     def isConnected(self):
-        return hasattr(self, '_v_connection') and \
-               self._v_connection is not None
+        return self._v_connection is not None
 
     def __call__(self):
         self.connect()

Modified: Zope3/trunk/src/zope/app/rdb/tests/test_zopedatabaseadapter.py
===================================================================
--- Zope3/trunk/src/zope/app/rdb/tests/test_zopedatabaseadapter.py	2005-06-08 07:30:35 UTC (rev 30681)
+++ Zope3/trunk/src/zope/app/rdb/tests/test_zopedatabaseadapter.py	2005-06-08 07:34:56 UTC (rev 30682)
@@ -18,6 +18,7 @@
 import unittest
 from zope.app.rdb import ZopeDatabaseAdapter
 from zope.app.rdb import ZopeConnection
+from threading import Thread
 
 class ConnectionStub(object):
 
@@ -51,6 +52,9 @@
         da = self._da
         da.disconnect()
         self.assertEqual(None, da._v_connection)
+        da.connect()
+        da.disconnect()
+        self.assertEqual(None, da._v_connection)
 
     def testIsConnected(self):
         da = self._da
@@ -70,7 +74,20 @@
         conv = da.getConverter('any')
         self.assert_(conv is identity, "default converter is wrong")
 
+    def testThreading(self):
+        # Ensure that different threads get distinct connections
+        cons = []
+        def get_con():
+            self._da.connect()
+            cons.append(self._da._v_connection)
+        get_con()
+        t = Thread(target=get_con)
+        t.start()
+        t.join()
+        self.failUnlessEqual(len(cons), 2)
+        self.failIfEqual(id(cons[0]), id(cons[1]))
 
+
 def test_suite():
     suite = unittest.TestSuite()
     suite.addTest(unittest.makeSuite(TestZopeDatabaseAdapter))



More information about the Zope3-Checkins mailing list