[Zope3-checkins] CVS: Zope3/src/persistence - cache.py:1.3

Jeremy Hylton jeremy@zope.com
Fri, 7 Mar 2003 15:26:47 -0500


Update of /cvs-repository/Zope3/src/persistence
In directory cvs.zope.org:/tmp/cvs-serv29075/persistence

Modified Files:
	cache.py 
Log Message:
Implement a new throw-away LRU cache.

The old cache, through a variety of confusions, was guaranteed never
to evict anything after the first call to incrgc().  Eliminate the
minimize() and full_sweep() methods that were only called by the tests
anyway.


=== Zope3/src/persistence/cache.py 1.2 => 1.3 ===
--- Zope3/src/persistence/cache.py:1.2	Wed Dec 25 09:12:13 2002
+++ Zope3/src/persistence/cache.py	Fri Mar  7 15:26:46 2003
@@ -21,8 +21,6 @@
 
     __implements__ = ICache
 
-    __iter=None
-
     def __init__(self, size=500, inactive=300):
         self.__ghosts = {}
         self.__gget = self.__ghosts.get
@@ -42,22 +40,23 @@
             return o
 
     def get(self, oid, default=None):
-        o = self.__gget(oid, self)
-        if o is self:
-            o = self.__active.get(oid, self)
-            if o is self: return default
-        o=o()
+        o = self.__gget(oid, None)
+        if o is None:
+            o = self.__active.get(oid, None)
+            if o is None:
+                return default
+        o = o()
         if o is None:
             return default
         else:
             return o
 
-    def __setitem__(self, oid, object):
-        if object._p_changed is None:
+    def __setitem__(self, oid, obj):
+        if obj._p_changed is None:
             # ghost
-            self.__ghosts[oid] = ref(object, _dictdel(oid, self.__ghosts))
+            self.__ghosts[oid] = ref(obj, _dictdel(oid, self.__ghosts))
         else:
-            self.__active[oid] = ref(object, _dictdel(oid, self.__active))
+            self.__active[oid] = ref(obj, _dictdel(oid, self.__active))
 
     def __delitem__(self, oid):
         # XXX is there any way to know which dict the key is in?
@@ -71,7 +70,7 @@
             pass
 
     def __len__(self):
-        return len(self.__ghosts)+len(self.__active)
+        return len(self.__ghosts) + len(self.__active)
 
     def setstate(self, oid, object):
         try:
@@ -80,103 +79,46 @@
             pass
         self.__active[oid] = ref(object, _dictdel(oid, self.__active))
 
-    def incrgc(self, multiple=1):
-        na=len(self.__active)
-        if na < 1: return
-
-        # how many objects do we scan?
-        n=min(multiple * max((na-self._size)/10, 3), na)
-
-        # how long can objects be inactive?
-        inactive = self._inactive * (
-            0.2 + 0.1 * (min(100, 8 * self._size/na))
-            )
-
-        active=self.__active
-        aget=active.get
-        ghosts=self.__ghosts
-        doomed=[]
-
-        now=int(time()%86400)
-
-        i=self.__iter
-        if i is None:
-            i=iter(self.__active)
-
-        while n:
-            n-=1
-            try: oid = i.next()
-            except StopIteration:
-                del self.__iter
-                return
-
-            ob=aget(oid, self)
-            if ob is self: continue
-            ob=ob()
-            state = ob._p_changed
-
-            if state==0 and abs(ob._p_atime-now) > inactive:
-                doomed.append(oid)
-                continue
-            if state is None:
-                doomed.append(oid)
-
-        for oid in doomed:
-            ob=aget(oid, self)
-            if ob is self: continue
-            ob=ob()
-            ob._p_deactivate()
-            state = ob._p_changed
-            if state is None:
-                del active[oid]
-                ghosts[oid] = ref(ob, _dictdel(oid, ghosts))
-
-    def full_sweep(self):
-        now=int(time()%86400)
-        active=self.__active
-        ghosts=self.__ghosts
-        na=len(active)
-
-        # how long can objects be inactive?
-        inactive = self._inactive * (
-            0.2 + 0.1 * (min(100, 8 * self._size/na))
-            )
-
-        doomed=[]
-
-        for oid in active:
-            ob=active[oid]
-            ob=ob()
-            state = ob._p_changed
-            if state==0 and abs(ob._p_atime-now) > inactive:
-                doomed.append(oid)
-                continue
-            if state is None:
-                doomed.append(oid)
-
-        for oid in doomed:
-            ob._p_deactivate()
-            state = ob._p_changed
-            if state is None:
-                del active[oid]
-                ghosts[oid] = ref(ob, _dictdel(oid, ghosts))
-
-    def minimize(self):
-        active=self.__active
-        aget=active.get
-        ghosts=self.__ghosts
-
-        # Grump: I cant use an iterator because the size will change
-        # during iteration. :(
-        for oid in active.keys():
-            ob=aget(oid, self)
-            if ob is self: continue
-            ob=ob()
-            ob._p_deactivate()
-            if ob._p_changed is None:
-                del active[oid]
-                ghosts[oid] = ref(ob, _dictdel(oid, ghosts))
-        self.__iter=None
+    def incrgc(self):
+        na = len(self.__active)
+        if na < 1:
+            return
+
+        now = int(time() % 86400)
+
+        # Implement a trivial LRU cache by sorting the items by
+        # access time and trundling over the last until we've reached
+        # out target.  The number of objects in the cache should
+        # be relatively small (thousands) so the memory for the
+        # list is pretty minimal.
+        L = []
+        for oid, ob in self.__active.iteritems():
+            if ob is not None:
+                ob = ob()
+            L.append((ob._p_atime, oid, ob))
+        L.sort()
+
+        if na > self._size:
+            # If the cache is full, ghostify everything up to the cache
+            # limit.
+            n = na - self._size
+            must_go = L[:n]
+            L = L[n:]
+            for atime, oid, ob in L:
+                self._ghostify(oid, ob)
+
+        # ghostify old objects regardless of cache size
+        stop_at = now - self._inactive
+        for atime, oid, ob in L:
+            if atime > stop_at:
+                break
+            self._ghostify(oid, ob)
+
+    def _ghostify(self, oid, ob):
+        ob._p_deactivate()
+        if ob._p_changed == None:
+            del self.__active[oid]
+            self.__ghosts[oid] = ref(ob, _dictdel(oid, self.__ghosts))
 
     def invalidate(self, oid):
         ob = self.__aget(oid)