[Zodb-checkins] SVN: ZODB/branches/tseaver-python_picklecache-2/src/persistent/ Document 'new_ghost' as part of IPickleCache, and implement it.

Tres Seaver tseaver at palladion.com
Wed Feb 16 21:32:13 EST 2011


Log message for revision 120401:
  Document 'new_ghost' as part of IPickleCache, and implement it.
  
  Add missing coverage.
  

Changed:
  U   ZODB/branches/tseaver-python_picklecache-2/src/persistent/interfaces.py
  U   ZODB/branches/tseaver-python_picklecache-2/src/persistent/picklecache.py
  U   ZODB/branches/tseaver-python_picklecache-2/src/persistent/tests/test_picklecache.py

-=-
Modified: ZODB/branches/tseaver-python_picklecache-2/src/persistent/interfaces.py
===================================================================
--- ZODB/branches/tseaver-python_picklecache-2/src/persistent/interfaces.py	2011-02-17 02:31:46 UTC (rev 120400)
+++ ZODB/branches/tseaver-python_picklecache-2/src/persistent/interfaces.py	2011-02-17 02:32:13 UTC (rev 120401)
@@ -489,6 +489,21 @@
         o XXX?
         """
 
+    def new_ghost(oid, obj):
+        """ Add the given (ghost) object to the cache.
+
+        Also, set its _p_jar and _p_oid, and ensure it is in the
+        GHOST state.
+
+        If the object doesn't define '_p_oid' / '_p_jar', raise.
+
+        If the object's '_p_oid' is not None, raise.
+
+        If the object's '_p_jar' is not None, raise.
+
+        If 'oid' is already in the cache, raise. 
+        """
+
     def reify(to_reify):
         """ Reify the indicated objects.
 
@@ -498,7 +513,7 @@
 
         o For each OID, if present in 'data' and in GHOST state:
 
-            o Call '_p_unghostify' on the object.
+            o Call '_p_activate' on the object.
 
             o Add it to the ring.
 

Modified: ZODB/branches/tseaver-python_picklecache-2/src/persistent/picklecache.py
===================================================================
--- ZODB/branches/tseaver-python_picklecache-2/src/persistent/picklecache.py	2011-02-17 02:31:46 UTC (rev 120400)
+++ ZODB/branches/tseaver-python_picklecache-2/src/persistent/picklecache.py	2011-02-17 02:32:13 UTC (rev 120401)
@@ -11,6 +11,7 @@
 # FOR A PARTICULAR PURPOSE.
 #
 ##############################################################################
+import gc
 import weakref
 
 from zope.interface import implements
@@ -60,7 +61,7 @@
     def __setitem__(self, oid, value):
         """ See IPickleCache.
         """
-        if not isinstance(oid, str):
+        if not isinstance(oid, str): # XXX bytes
             raise ValueError('OID must be string: %s' % oid)
         # XXX
         if oid in self.persistent_classes or oid in self.data:
@@ -170,11 +171,36 @@
 
     minimize = full_sweep
 
-    def reify(self, oid):
+    def new_ghost(self, oid, obj):
         """ See IPickleCache.
         """
-        pass
+        if obj._p_oid is not None:
+            raise ValueError('Object already has oid')
+        if obj._p_jar is not None:
+            raise ValueError('Object already has jar')
+        if oid in self.persistent_classes or oid in self.data:
+            raise KeyError('Duplicate OID: %s' % oid)
+        obj._p_oid = oid
+        obj._p_jar = self.jar
+        if type(obj) is not type:
+            if obj._p_state != GHOST:
+                obj._p_invalidate()
+        self[oid] = obj
 
+    def reify(self, to_reify):
+        """ See IPickleCache.
+        """
+        if isinstance(to_reify, str): #bytes
+            to_reify = [to_reify]
+        for oid in to_reify:
+            value = self[oid]
+            if value._p_state == GHOST:
+                value._p_activate()
+                self.non_ghost_count += 1
+                mru = self.ring.prev
+                self.ring.prev = node = RingNode(value, self.ring, mru)
+                mru.next = node
+
     def invalidate(self, to_invalidate):
         """ See IPickleCache.
         """
@@ -185,20 +211,20 @@
                 self._invalidate(oid)
 
     def debug_info(self):
-        """ See IPickleCache.
-        """
         result = []
         for oid, klass in self.persistent_classes.items():
             result.append((oid,
-                            len(gc.getreferents(value)),
-                            type(value).__name__,
-                            value._p_state,
+                            len(gc.getreferents(klass)),
+                            type(klass).__name__,
+                            klass._p_state,
                             ))
         for oid, value in self.data.items():
             result.append((oid,
                             len(gc.getreferents(value)),
                             type(value).__name__,
+                            value._p_state,
                             ))
+        return result
 
     cache_size = property(lambda self: self.target_size)
     cache_drain_resistance = property(lambda self: self.drain_resistance)
@@ -220,8 +246,7 @@
     def _invalidate(self, oid):
         value = self.data.get(oid)
         if value is not None and value._p_state != GHOST:
-            # value._p_invalidate() # NOOOO, we'll do it ourselves.
-            value._p_ghostify() # TBD
+            value._p_invalidate()
             node = self.ring.next
             while node is not self.ring:
                 if node.object is value:

Modified: ZODB/branches/tseaver-python_picklecache-2/src/persistent/tests/test_picklecache.py
===================================================================
--- ZODB/branches/tseaver-python_picklecache-2/src/persistent/tests/test_picklecache.py	2011-02-17 02:31:46 UTC (rev 120400)
+++ ZODB/branches/tseaver-python_picklecache-2/src/persistent/tests/test_picklecache.py	2011-02-17 02:32:13 UTC (rev 120401)
@@ -13,6 +13,8 @@
 ##############################################################################
 import unittest
 
+_marker = object()
+
 class PickleCacheTests(unittest.TestCase):
 
     def _getTargetClass(self):
@@ -24,11 +26,11 @@
             jar = DummyConnection()
         return self._getTargetClass()(jar, target_size)
 
-    def _makePersist(self, state=None, oid='foo', jar=None):
+    def _makePersist(self, state=None, oid='foo', jar=_marker):
+        from persistent.interfaces import GHOST
         if state is None:
-            from persistent.interfaces import GHOST
             state = GHOST
-        if jar is None:
+        if jar is _marker:
             jar = DummyConnection()
         persist = DummyPersistent()
         persist._p_state = state
@@ -272,6 +274,7 @@
         self.assertEqual(items[2][0], 'three')
 
     def test_incrgc_simple(self):
+        import gc
         from persistent.interfaces import UPTODATE
         cache = self._makeOne()
         oids = []
@@ -282,6 +285,7 @@
         self.assertEqual(cache.cache_non_ghost_count, 100)
 
         cache.incrgc()
+        gc.collect() # banish the ghosts who are no longer in the ring
 
         self.assertEqual(cache.cache_non_ghost_count, 10)
         items = cache.lru_items()
@@ -303,14 +307,257 @@
         for oid in oids[90:]:
             self.failIf(cache.get(oid) is None)
 
+    def test_incrgc_w_smaller_drain_resistance(self):
+        from persistent.interfaces import UPTODATE
+        cache = self._makeOne()
+        cache.drain_resistance = 2
+        oids = []
+        for i in range(100):
+            oid = 'oid_%04d' % i
+            oids.append(oid)
+            cache[oid] = self._makePersist(oid=oid, state=UPTODATE)
+        self.assertEqual(cache.cache_non_ghost_count, 100)
 
+        cache.incrgc()
+
+        self.assertEqual(cache.cache_non_ghost_count, 10)
+
+    def test_incrgc_w_larger_drain_resistance(self):
+        from persistent.interfaces import UPTODATE
+        cache = self._makeOne()
+        cache.drain_resistance = 2
+        cache.target_size = 90
+        oids = []
+        for i in range(100):
+            oid = 'oid_%04d' % i
+            oids.append(oid)
+            cache[oid] = self._makePersist(oid=oid, state=UPTODATE)
+        self.assertEqual(cache.cache_non_ghost_count, 100)
+
+        cache.incrgc()
+
+        self.assertEqual(cache.cache_non_ghost_count, 49)
+
+    def test_full_sweep(self):
+        import gc
+        from persistent.interfaces import UPTODATE
+        cache = self._makeOne()
+        oids = []
+        for i in range(100):
+            oid = 'oid_%04d' % i
+            oids.append(oid)
+            cache[oid] = self._makePersist(oid=oid, state=UPTODATE)
+        self.assertEqual(cache.cache_non_ghost_count, 100)
+
+        cache.full_sweep()
+        gc.collect() # banish the ghosts who are no longer in the ring
+
+        self.assertEqual(cache.cache_non_ghost_count, 0)
+        self.failUnless(cache.ring.next is cache.ring)
+
+        for oid in oids:
+            self.failUnless(cache.get(oid) is None)
+
+    def test_minimize(self):
+        import gc
+        from persistent.interfaces import UPTODATE
+        cache = self._makeOne()
+        oids = []
+        for i in range(100):
+            oid = 'oid_%04d' % i
+            oids.append(oid)
+            cache[oid] = self._makePersist(oid=oid, state=UPTODATE)
+        self.assertEqual(cache.cache_non_ghost_count, 100)
+
+        cache.minimize()
+        gc.collect() # banish the ghosts who are no longer in the ring
+
+        self.assertEqual(cache.cache_non_ghost_count, 0)
+
+        for oid in oids:
+            self.failUnless(cache.get(oid) is None)
+
+    def test_new_ghost_non_persistent_object(self):
+        cache = self._makeOne()
+        self.assertRaises(AttributeError, cache.new_ghost, '123', object())
+
+    def test_new_ghost_obj_already_has_oid(self):
+        from persistent.interfaces import GHOST
+        candidate = self._makePersist(oid='123', state=GHOST)
+        cache = self._makeOne()
+        self.assertRaises(ValueError, cache.new_ghost, '123', candidate)
+
+    def test_new_ghost_obj_already_has_jar(self):
+        class Dummy(object):
+            _p_oid = None
+            _p_jar = object()
+        cache = self._makeOne()
+        candidate = self._makePersist(oid=None, jar=object())
+        self.assertRaises(ValueError, cache.new_ghost, '123', candidate)
+
+    def test_new_ghost_obj_already_in_cache(self):
+        cache = self._makeOne()
+        candidate = self._makePersist(oid=None, jar=None)
+        cache['123'] = candidate
+        self.assertRaises(KeyError, cache.new_ghost, '123', candidate)
+
+    def test_new_ghost_success_already_ghost(self):
+        from persistent.interfaces import GHOST
+        cache = self._makeOne()
+        candidate = self._makePersist(oid=None, jar=None)
+        cache.new_ghost('123', candidate)
+        self.failUnless(cache.get('123') is candidate)
+        self.assertEqual(candidate._p_oid, '123')
+        self.assertEqual(candidate._p_jar, cache.jar)
+        self.assertEqual(candidate._p_state, GHOST)
+
+    def test_new_ghost_success_not_already_ghost(self):
+        from persistent.interfaces import GHOST
+        from persistent.interfaces import UPTODATE
+        cache = self._makeOne()
+        candidate = self._makePersist(oid=None, jar=None, state=UPTODATE)
+        cache.new_ghost('123', candidate)
+        self.failUnless(cache.get('123') is candidate)
+        self.assertEqual(candidate._p_oid, '123')
+        self.assertEqual(candidate._p_jar, cache.jar)
+        self.assertEqual(candidate._p_state, GHOST)
+
+    def test_new_ghost_w_pclass_non_ghost(self):
+        class Pclass(object):
+            _p_oid = None
+            _p_jar = None
+        cache = self._makeOne()
+        cache.new_ghost('123', Pclass)
+        self.failUnless(cache.get('123') is Pclass)
+        self.failUnless(cache.persistent_classes['123'] is Pclass)
+        self.assertEqual(Pclass._p_oid, '123')
+        self.assertEqual(Pclass._p_jar, cache.jar)
+
+    def test_new_ghost_w_pclass_ghost(self):
+        class Pclass(object):
+            _p_oid = None
+            _p_jar = None
+        cache = self._makeOne()
+        cache.new_ghost('123', Pclass)
+        self.failUnless(cache.get('123') is Pclass)
+        self.failUnless(cache.persistent_classes['123'] is Pclass)
+        self.assertEqual(Pclass._p_oid, '123')
+        self.assertEqual(Pclass._p_jar, cache.jar)
+
+    def test_reify_miss_single(self):
+        cache = self._makeOne()
+        self.assertRaises(KeyError, cache.reify, '123')
+
+    def test_reify_miss_multiple(self):
+        cache = self._makeOne()
+        self.assertRaises(KeyError, cache.reify, ['123', '456'])
+
+    def test_reify_hit_single_ghost(self):
+        from persistent.interfaces import GHOST
+        from persistent.interfaces import UPTODATE
+        cache = self._makeOne()
+        candidate = self._makePersist(oid='123', jar=cache.jar, state=GHOST)
+        cache['123'] = candidate
+        self.assertEqual(cache.ringlen(), 0)
+        cache.reify('123')
+        self.assertEqual(cache.ringlen(), 1)
+        items = cache.lru_items()
+        self.assertEqual(items[0][0], '123')
+        self.failUnless(items[0][1] is candidate)
+        self.assertEqual(candidate._p_state, UPTODATE)
+
+    def test_reify_hit_single_non_ghost(self):
+        from persistent.interfaces import UPTODATE
+        cache = self._makeOne()
+        candidate = self._makePersist(oid='123', jar=cache.jar, state=UPTODATE)
+        cache['123'] = candidate
+        self.assertEqual(cache.ringlen(), 1)
+        cache.reify('123')
+        self.assertEqual(cache.ringlen(), 1)
+        self.assertEqual(candidate._p_state, UPTODATE)
+
+    def test_reify_hit_multiple_mixed(self):
+        from persistent.interfaces import GHOST
+        from persistent.interfaces import UPTODATE
+        cache = self._makeOne()
+        c1 = self._makePersist(oid='123', jar=cache.jar, state=GHOST)
+        cache['123'] = c1
+        c2 = self._makePersist(oid='456', jar=cache.jar, state=UPTODATE)
+        cache['456'] = c2
+        self.assertEqual(cache.ringlen(), 1)
+        cache.reify(['123', '456'])
+        self.assertEqual(cache.ringlen(), 2)
+        self.assertEqual(c1._p_state, UPTODATE)
+        self.assertEqual(c2._p_state, UPTODATE)
+
+    def test_invalidate_miss_single(self):
+        cache = self._makeOne()
+        cache.invalidate('123') # doesn't raise
+
+    def test_invalidate_miss_multiple(self):
+        cache = self._makeOne()
+        cache.invalidate(['123', '456']) # doesn't raise
+
+    def test_invalidate_hit_single_ghost(self):
+        from persistent.interfaces import GHOST
+        cache = self._makeOne()
+        candidate = self._makePersist(oid='123', jar=cache.jar, state=GHOST)
+        cache['123'] = candidate
+        self.assertEqual(cache.ringlen(), 0)
+        cache.invalidate('123')
+        self.assertEqual(cache.ringlen(), 0)
+        self.assertEqual(candidate._p_state, GHOST)
+
+    def test_invalidate_hit_single_non_ghost(self):
+        from persistent.interfaces import GHOST
+        from persistent.interfaces import UPTODATE
+        cache = self._makeOne()
+        candidate = self._makePersist(oid='123', jar=cache.jar, state=UPTODATE)
+        cache['123'] = candidate
+        self.assertEqual(cache.ringlen(), 1)
+        cache.invalidate('123')
+        self.assertEqual(cache.ringlen(), 0)
+        self.assertEqual(candidate._p_state, GHOST)
+
+    def test_invalidate_hit_multiple_mixed(self):
+        from persistent.interfaces import GHOST
+        from persistent.interfaces import UPTODATE
+        cache = self._makeOne()
+        c1 = self._makePersist(oid='123', jar=cache.jar, state=GHOST)
+        cache['123'] = c1
+        c2 = self._makePersist(oid='456', jar=cache.jar, state=UPTODATE)
+        cache['456'] = c2
+        self.assertEqual(cache.ringlen(), 1)
+        cache.invalidate(['123', '456'])
+        self.assertEqual(cache.ringlen(), 0)
+        self.assertEqual(c1._p_state, GHOST)
+        self.assertEqual(c2._p_state, GHOST)
+
+    def test_invalidate_hit_pclass(self):
+        class Pclass(object):
+            _p_oid = None
+            _p_jar = None
+        cache = self._makeOne()
+        cache['123'] = Pclass
+        self.failUnless(cache.persistent_classes['123'] is Pclass)
+        cache.invalidate('123')
+        self.failIf('123' in cache.persistent_classes)
+
+
 class DummyPersistent(object):
-    pass
 
+    def _p_invalidate(self):
+        from persistent.interfaces import GHOST
+        self._p_state = GHOST
+
+    def _p_activate(self):
+        from persistent.interfaces import UPTODATE
+        self._p_state = UPTODATE
+
+
 class DummyConnection:
+    pass
 
-    def setklassstate(self, obj):
-        """Method used by PickleCache."""
 
 def test_suite():
     return unittest.TestSuite((



More information about the Zodb-checkins mailing list