[Checkins] SVN: ZODB/trunk/s Fixed bug: The new option to drop the cache on verify didn't leave the

Jim Fulton jim at zope.com
Tue Nov 18 20:45:12 EST 2008


Log message for revision 93122:
  Fixed bug: The new option to drop the cache on verify didn't leave the
  cache or the client storage in a valid state.
  
  Also, changed the drop-cache feature to:
  
  - Log a critical message and to
  - Publish an event.  Some applications might want to captre the event
    and exit the process or take some drastic action when the cahe needs
    to be dropped.
  

Changed:
  U   ZODB/trunk/setup.py
  U   ZODB/trunk/src/ZEO/ClientStorage.py
  U   ZODB/trunk/src/ZEO/interfaces.py
  A   ZODB/trunk/src/ZEO/tests/drop_cache_rather_than_verify.txt
  U   ZODB/trunk/src/ZEO/tests/testZEO.py

-=-
Modified: ZODB/trunk/setup.py
===================================================================
--- ZODB/trunk/setup.py	2008-11-19 01:45:09 UTC (rev 93121)
+++ ZODB/trunk/setup.py	2008-11-19 01:45:11 UTC (rev 93122)
@@ -260,13 +260,14 @@
         'zdaemon',
         ],
       install_requires = [
+        'transaction',
+        'zc.lockfile',
+        'ZConfig',
+        'zdaemon',
+        'zope.event',
         'zope.interface',
         'zope.proxy',
         'zope.testing',
-        'ZConfig',
-        'zdaemon',
-        'transaction',
-        'zc.lockfile',
         ],
       zip_safe = False,
       entry_points = """

Modified: ZODB/trunk/src/ZEO/ClientStorage.py
===================================================================
--- ZODB/trunk/src/ZEO/ClientStorage.py	2008-11-19 01:45:09 UTC (rev 93121)
+++ ZODB/trunk/src/ZEO/ClientStorage.py	2008-11-19 01:45:11 UTC (rev 93122)
@@ -19,7 +19,19 @@
 
 """
 
+from persistent.TimeStamp import TimeStamp
+from ZEO.auth import get_module
+from ZEO.cache import ClientCache
+from ZEO.Exceptions import ClientStorageError, ClientDisconnected, AuthError
+from ZEO import ServerStub
+from ZEO.TransactionBuffer import TransactionBuffer
+from ZEO.zrpc.client import ConnectionManager
+from ZODB.blob import rename_or_copy_blob
+from ZODB import POSException
+from ZODB import utils
+from ZODB.loglevels import BLATHER
 import cPickle
+import logging
 import os
 import socket
 import stat
@@ -28,24 +40,13 @@
 import threading
 import time
 import types
-import logging
 import weakref
-
-import zope.interface
-from ZEO import ServerStub
-from ZEO.cache import ClientCache
-from ZEO.TransactionBuffer import TransactionBuffer
-from ZEO.Exceptions import ClientStorageError, ClientDisconnected, AuthError
-from ZEO.auth import get_module
-from ZEO.zrpc.client import ConnectionManager
-
-import ZODB.interfaces
 import zc.lockfile
+import ZEO.interfaces
 import ZODB.BaseStorage
-from ZODB import POSException
-from ZODB import utils
-from ZODB.blob import rename_or_copy_blob
-from persistent.TimeStamp import TimeStamp
+import ZODB.interfaces
+import zope.event
+import zope.interface
 
 logger = logging.getLogger(__name__)
 
@@ -112,7 +113,7 @@
 
     def __init__(self, addr, storage='1', cache_size=20 * MB,
                  name='', client=None, debug=0, var=None,
-                 min_disconnect_poll=5, max_disconnect_poll=300,
+                 min_disconnect_poll=1, max_disconnect_poll=30,
                  wait_for_server_on_startup=None, # deprecated alias for wait
                  wait=None, wait_timeout=None,
                  read_only=0, read_only_fallback=0,
@@ -223,12 +224,6 @@
             logger.warning(
                 "%s ClientStorage(): debug argument is no longer used",
                 self.__name__)
-            
-        # Remember some parameters for "_setupCache"
-        self._var_ = var
-        self._storage_ = storage
-        self._client_ = client
-        self._cache_size_ = cache_size
 
         self._drop_cache_rather_verify = drop_cache_rather_verify
 
@@ -357,8 +352,14 @@
         else:
             self.fshelper = None
 
-        self._setupCache()
+        if client is not None:
+            dir = var or os.getcwd()
+            cache_path = os.path.join(dir, "%s-%s.zec" % (client, storage))
+        else:
+            cache_path = None
 
+        self._cache = self.ClientCacheClass(cache_path, size=cache_size)
+
         self._rpc_mgr = self.ConnectionManagerClass(addr, self,
                                                     tmin=min_disconnect_poll,
                                                     tmax=max_disconnect_poll)
@@ -372,19 +373,6 @@
             if not self._rpc_mgr.attempt_connect():
                 self._rpc_mgr.connect()
 
-    def _setupCache(self):
-        '''create and open the cache.'''
-        # Decide whether to use non-temporary files
-        storage = self._storage_
-        client = self._client_
-        cache_size = self._cache_size_
-        if client is not None:
-            dir = self._var_ or os.getcwd()
-            cache_path = os.path.join(dir, "%s-%s.zec" % (client, storage))
-        else:
-            cache_path = None
-        self._cache = self.ClientCacheClass(cache_path, size=cache_size)
-
     def _wait(self, timeout=None):
         if timeout is not None:
             deadline = time.time() + timeout
@@ -1276,11 +1264,12 @@
             self._db.invalidateCache()
 
         if self._cache and self._drop_cache_rather_verify:
-            logger.info("%s dropping cache", self.__name__)
-            self._cache.close()
-            self._setupCache() # creates a new cache
-            self._server = server
-            self._ready.set()
+            logger.critical("%s dropping stale cache", self.__name__)
+            zope.event.notify(ZEO.interfaces.CacheDroppedEvent())
+            self._cache.clear()
+            if ltid:
+                self._cache.setLastTid(ltid)
+            self.finish_verification()
             return "cache dropped"
 
         logger.info("%s Verifying cache", self.__name__)

Modified: ZODB/trunk/src/ZEO/interfaces.py
===================================================================
--- ZODB/trunk/src/ZEO/interfaces.py	2008-11-19 01:45:09 UTC (rev 93121)
+++ ZODB/trunk/src/ZEO/interfaces.py	2008-11-19 01:45:11 UTC (rev 93122)
@@ -14,6 +14,10 @@
 
 import zope.interface
 
+class CacheDroppedEvent(object):
+    """A ZEO Cache file was dropped to avoid verification 
+    """
+
 class IServeable(zope.interface.Interface):
     """Interface provided by storages that can be served by ZEO
     """

Added: ZODB/trunk/src/ZEO/tests/drop_cache_rather_than_verify.txt
===================================================================
--- ZODB/trunk/src/ZEO/tests/drop_cache_rather_than_verify.txt	                        (rev 0)
+++ ZODB/trunk/src/ZEO/tests/drop_cache_rather_than_verify.txt	2008-11-19 01:45:11 UTC (rev 93122)
@@ -0,0 +1,153 @@
+Avoiding cache verifification
+=============================
+
+For large databases it is common to also use very large ZEO cache
+files.  If a client has beed disconnected for too long, cache verification
+might be necessary, but cache verification can be very hard on the
+storage server.
+
+ClientStorage provides an option to drop it's cache rather than doing
+verification.  When this option is used, and verification would be
+necessary, ClientStorage:
+
+- Invalidates all object caches
+
+- Drops or clears it's client cache. (The end result is that the cache
+  is working but empty.)
+
+- Logs a CRITICAL message.
+
+- Publishes a ZEO.interfaces.CacheDroppedEvent event.
+
+Here's an example that shows that this is actually what happens.
+
+Start a server, create a cient to it and commit some data
+
+    >>> addr, admin = start_server(keep=1)
+    >>> import ZEO, transaction
+    >>> db = ZEO.DB(addr, drop_cache_rather_verify=True, client='cache',
+    ...             name='test')
+    >>> wait_connected(db.storage)
+    >>> conn = db.open()
+    >>> conn.root()[1] = conn.root().__class__()
+    >>> conn.root()[1].x = 1
+    >>> transaction.commit()
+    >>> len(db.storage._cache)
+    3
+
+Now, we'll stop the server and restart with a different address:
+
+    >>> stop_server(admin)
+    >>> addr2, admin = start_server(keep=1)
+
+And create another client and write some data to it:
+
+    >>> db2 = ZEO.DB(addr2)
+    >>> wait_connected(db2.storage)
+    >>> conn2 = db2.open()
+    >>> for i in range(5):
+    ...     conn2.root()[1].x += 1
+    ...     transaction.commit()
+    >>> db2.close()
+    >>> stop_server(admin)
+
+Now, we'll restart the server.  Before we do that, we'll capture
+logging and event data:
+
+    >>> import logging, zope.testing.loggingsupport, zope.event
+    >>> handler = zope.testing.loggingsupport.InstalledHandler(
+    ...     'ZEO.ClientStorage', level=logging.ERROR)
+    >>> events = []
+    >>> zope.event.subscribers.append(events.append)
+
+Now, we'll restart the server on the original address:
+
+    >>> _, admin = start_server(zeo_conf=dict(invalidation_queue_size=1),
+    ...                         addr=addr, keep=1)
+    >>> wait_connected(db.storage)
+
+Now, let's verify our assertions above:
+  
+- Drops or clears it's client cache. (The end result is that the cache
+  is working but empty.)
+
+    >>> len(db.storage._cache)
+    0
+
+- Invalidates all object caches
+
+    >>> transaction.abort()
+    >>> conn.root()._p_changed
+
+- Logs a CRITICAL message.
+
+    >>> print handler
+    ZEO.ClientStorage CRITICAL
+      test dropping stale cache
+
+    >>> handler.clear()
+
+- Publishes a cache-dropped event.
+
+    >>> for e in events:
+    ...     print e.__class__.__name__
+    CacheDroppedEvent
+
+    >>> del events[:]
+
+If we access the root object, it'll be loaded from the server:
+
+    >>> conn.root()[1].x
+    6
+
+    >>> len(db.storage._cache)
+    2
+
+Similarly, if we simply disconnect the client, and write data from
+another client:
+
+    >>> db.close()
+
+    >>> db2 = ZEO.DB(addr)
+    >>> wait_connected(db2.storage)
+    >>> conn2 = db2.open()
+    >>> for i in range(5):
+    ...     conn2.root()[1].x += 1
+    ...     transaction.commit()
+    >>> db2.close()
+
+    >>> db = ZEO.DB(addr, drop_cache_rather_verify=True, client='cache',
+    ...             name='test')
+    >>> wait_connected(db.storage)
+    
+  
+- Drops or clears it's client cache. (The end result is that the cache
+  is working but empty.)
+
+    >>> len(db.storage._cache)
+    1
+
+(When a database is created, it checks to make sure the root object is
+in the database, which is why we get 1, rather than 0 objects in the cache.)
+
+- Logs a CRITICAL message.
+
+    >>> print handler
+    ZEO.ClientStorage CRITICAL
+      test dropping stale cache
+
+    >>> handler.clear()
+
+- Publishes a cache-dropped event.
+
+    >>> for e in events:
+    ...     print e.__class__.__name__
+    CacheDroppedEvent
+
+If we access the root object, it'll be loaded from the server:
+
+    >>> conn = db.open()
+    >>> conn.root()[1].x
+    11
+
+    >>> db.close()


Property changes on: ZODB/trunk/src/ZEO/tests/drop_cache_rather_than_verify.txt
___________________________________________________________________
Added: svn:eol-style
   + native

Modified: ZODB/trunk/src/ZEO/tests/testZEO.py
===================================================================
--- ZODB/trunk/src/ZEO/tests/testZEO.py	2008-11-19 01:45:09 UTC (rev 93121)
+++ ZODB/trunk/src/ZEO/tests/testZEO.py	2008-11-19 01:45:11 UTC (rev 93122)
@@ -148,35 +148,8 @@
         self.assertNotEquals(ZODB.utils.z64, storage3.lastTransaction())
         storage3.close()
 
-    def checkDropCacheRatherVerifyImplementation(self):
-        # As it is quite difficult to set things up such that the verification
-        # optimizations do not step in, we emulate both the cache
-        # as well as the server.
-        from ZODB.TimeStamp import TimeStamp
-        class CacheEmulator(object):
-            # the settings below would be inconsitent for a normal cache
-            # but they are sufficient for our test setup
-            def __len__(self): return 1 # claim not to be empty
-            def contents(self): return () # do not invalidate anything
-            def getLastTid(self): return
-            def close(self): pass
-        class ServerEmulator(object):
-            def verify(*unused): pass
-            def endZeoVerify(*unused): pass
-            def lastTransaction(*unused): pass
-        storage = self._storage
-        storage._cache = cache = CacheEmulator()
-        server = ServerEmulator()
-        # test the standard behaviour
-        self.assertEqual(storage.verify_cache(server), "full verification")
-        # test the "drop cache rather verify" behaviour
-        storage._drop_cache_rather_verify = True
-        self.assertEqual(storage.verify_cache(server), "cache dropped")
-        # verify that we got a new cache
-        self.assert_(cache != storage._cache)
-
-
 class ConfigurationTests(unittest.TestCase):
+
     def checkDropCacheRatherVerifyConfiguration(self):
         from ZODB.config import storageFromString
         # the default is to do verification and not drop the cache
@@ -1118,6 +1091,7 @@
     zeo.addTest(
         doctest.DocFileSuite(
             'zeo-fan-out.test', 'zdoptions.test',
+            'drop_cache_rather_than_verify.txt',
             setUp=forker.setUp, tearDown=zope.testing.setupstack.tearDown,
             ),
         )



More information about the Checkins mailing list