[Checkins] SVN: z3c.saconfig/trunk/src/z3c/saconfig/ Make sure engine factories never unnecisarily re-create engines.

Brian Sutherland jinty at web.de
Sat Jun 21 15:13:36 EDT 2008


Log message for revision 87636:
  Make sure engine factories never unnecisarily re-create engines.

Changed:
  U   z3c.saconfig/trunk/src/z3c/saconfig/README.txt
  U   z3c.saconfig/trunk/src/z3c/saconfig/interfaces.py
  U   z3c.saconfig/trunk/src/z3c/saconfig/utility.py

-=-
Modified: z3c.saconfig/trunk/src/z3c/saconfig/README.txt
===================================================================
--- z3c.saconfig/trunk/src/z3c/saconfig/README.txt	2008-06-21 17:34:20 UTC (rev 87635)
+++ z3c.saconfig/trunk/src/z3c/saconfig/README.txt	2008-06-21 19:13:34 UTC (rev 87636)
@@ -265,3 +265,40 @@
   1
   >>> users[0].name
   u'bob'
+
+Engines and Threading
+=====================
+
+  >>> engine = None
+  >>> def setEngine():
+  ...     global engine
+  ...     engine = engine_factory1()
+
+Engine factories must produce the same engine:
+ 
+  >>> setEngine()
+  >>> engine is engine_factory1()
+  True
+
+Even if you call it in a different thread:
+
+  >>> import threading
+  >>> engine = None
+  >>> t = threading.Thread(target=setEngine)
+  >>> t.start()
+  >>> t.join()
+
+  >>> engine is engine_factory1()
+  True
+
+Unless they are reset:
+  
+  >>> engine_factory1.reset()
+  >>> engine is engine_factory1()
+  False
+
+Even engine factories with the same parameters created at (almost) the same
+time should produce different engines:
+
+  >>> EngineFactory(TEST_DSN1)() is EngineFactory(TEST_DSN1)()
+  False

Modified: z3c.saconfig/trunk/src/z3c/saconfig/interfaces.py
===================================================================
--- z3c.saconfig/trunk/src/z3c/saconfig/interfaces.py	2008-06-21 17:34:20 UTC (rev 87635)
+++ z3c.saconfig/trunk/src/z3c/saconfig/interfaces.py	2008-06-21 19:13:34 UTC (rev 87636)
@@ -56,12 +56,3 @@
 
         This causes the engine to be recreated on next use.
         """
-
-    def getCached():
-        """Return the cached engine.
-        """
-
-    def cache(engine):
-        """Cache the engine.
-        """
-

Modified: z3c.saconfig/trunk/src/z3c/saconfig/utility.py
===================================================================
--- z3c.saconfig/trunk/src/z3c/saconfig/utility.py	2008-06-21 17:34:20 UTC (rev 87635)
+++ z3c.saconfig/trunk/src/z3c/saconfig/utility.py	2008-06-21 19:13:34 UTC (rev 87636)
@@ -2,7 +2,9 @@
 Some reusable, standard implementations of IScopedSession.
 """
 
+import time
 import thread
+import threading
 import sqlalchemy
 
 from zope.interface import implements
@@ -92,6 +94,13 @@
     def siteScopeFunc(self):
         raise NotImplementedError
 
+# Credits: This method of storing engines lifted from zope.app.cache.ram
+_COUNTER = 0
+_COUNTER_LOCK = threading.Lock()
+
+_ENGINES = {}
+_ENGINES_LOCK = threading.Lock()
+
 class EngineFactory(object):
     """An engine factory.
 
@@ -114,16 +123,33 @@
             kw['convert_unicode'] = True
         self._args = args
         self._kw = kw
-        
+        self._key = self._getKey()
+
+    def _getKey(self):
+        """Get a unique key"""
+        global _COUNTER
+        _COUNTER_LOCK.acquire()
+        try:
+            _COUNTER += 1
+            return  "%s_%f_%d" % (id(self), time.time(), _COUNTER)
+        finally:
+            _COUNTER_LOCK.release()
+    
     def __call__(self):
-        engine = self.getCached()
+        # optimistically try get without lock
+        engine = _ENGINES.get(self._key, None)
         if engine is not None:
             return engine
-        # no engine yet, so create a new one
-        args, kw = self.configuration()
-        engine = sqlalchemy.create_engine(*args, **kw)
-        self.cache(engine)
-        return engine
+        # no engine, lock and redo
+        _ENGINES_LOCK.acquire()
+        try:
+            # need to check, another thread may have got there first
+            if self._key not in _ENGINES:
+                args, kw = self.configuration()
+                _ENGINES[self._key] = sqlalchemy.create_engine(*args, **kw)
+            return _ENGINES[self._key]
+        finally:
+            _ENGINES_LOCK.release()
 
     def configuration(self):
         """Returns engine parameters.
@@ -134,17 +160,12 @@
         return self._args, self._kw
 
     def reset(self):
-        engine = self.getCached()
-        if engine is None:
-            return
-        # XXX is disposing the right thing to do?
-        engine.dispose()
-        self.cache(None)
-
-    # XXX what happens if EngineFactory were to be evicted from the ZODB
-    # cache?
-    def getCached(self):
-        return getattr(self, '_v_engine', None)
-
-    def cache(self, engine):
-        self._v_engine = engine
+        _ENGINES_LOCK.acquire()
+        try:
+            if self._key not in _ENGINES:
+                return
+            # XXX is disposing the right thing to do?
+            _ENGINES[self._key].dispose()
+            del _ENGINES[self._key]
+        finally:
+            _ENGINES_LOCK.release()



More information about the Checkins mailing list