[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)