[Zope-CVS] CVS: Products/Transience - Transience.py:1.3
Matthew T. Kromer
matt@zope.com
Mon, 22 Oct 2001 14:48:18 -0400
Update of /cvs-repository/Products/Transience
In directory cvs.zope.org:/tmp/cvs-serv29147
Modified Files:
Transience.py
Log Message:
Updated with more code from the original CoreSessionTracking SessionDataManager
for object expiry.
=== Products/Transience/Transience.py 1.2 => 1.3 ===
from Acquisition import Implicit, aq_base
from AccessControl import ClassSecurityInfo
+from BTrees import OOBTree
import os.path
+import math
import time
_notfound = []
+_marker = []
# permissions
ADD_DATAMGR_PERM = 'Add Transient Object Container'
CHANGE_DATAMGR_PERM = 'Change Transient Object Containers'
MGMT_SCREEN_PERM = 'View management screens'
ACCESS_CONTENTS_PERM = 'Access contents information'
+CREATE_TRANSIENTS_PERM = 'Create Transient Objects'
ACCESS_SESSIONDATA_PERM = 'Access Transient Objects'
MANAGE_CONTAINER_PERM = 'Manage Transient Object Container'
@@ -165,6 +169,8 @@
['Manager','Anonymous'])
security.setPermissionDefault(ACCESS_SESSIONDATA_PERM,
['Manager','Anonymous'])
+ security.setPermissionDefault(CREATE_TRANSIENTS_PERM,
+ ['Manager',])
security.declareProtected(MGMT_SCREEN_PERM, 'manage_container')
manage_container = HTMLFile('dtml/manageTransientObjectContainer',
@@ -180,17 +186,23 @@
#
def __init__(self, id, title='', timeout_mins=20, addNotification=None,
- delNotification=None):
+ delNotification=None, err_margin=.20, ctype=OOBTree.OOBTree):
+
self.id = id
self.title=title
- self._container = {}
+
+ self._ctype = ctype
+
self._addCallback = None
self._delCallback = None
- self.setTimeoutMinutes(timeout_mins)
+ self._err_margin = err_margin
+
+ self._setTimeout(timeout_mins)
+ self._reset()
- self.setAddNotificationTarget(addNotification)
self.setDelNotificationTarget(delNotification)
+ self.setAddNotificationTarget(addNotification)
# -----------------------------------------------------------------
# ItemWithID
@@ -204,24 +216,28 @@
# StringKeyedHomogenousItemContainer
#
+ security.declareProtected(CREATE_TRANSIENTS_PERM, 'new')
def new(self, key):
if type(key) is not type(''):
raise TypeError, (key, "key is not a string type")
- if self._container.has_key(key):
+ if self.has_key(key):
raise KeyError, key # Not allowed to dup keys
item = TransientObject(key, parent=self)
- self._container[key] = item
+ self[key] = item
+
+ self.notifyAdd(item)
return item
+ security.declareProtected(CREATE_TRANSIENTS_PERM, 'new_or_existing')
def new_or_existing(self, key):
- item = self._container.get(key,_notfound)
+ item = self.get(key,_notfound)
if item is not _notfound: return item
return self.new(key)
@@ -232,11 +248,15 @@
security.declareProtected(MANAGE_CONTAINER_PERM, 'setTimeoutMinutes')
def setTimeoutMinutes(self, timeout_mins):
- self._timeout = timeout_mins
+ """ """
+ if timeout_mins != self.getTimeoutMinutes():
+ self._setTimeout(timeout_mins)
+ self._reset()
security.declareProtected(MGMT_SCREEN_PERM, 'getTimeoutMinutes')
def getTimeoutMinutes(self):
- return self._timeout
+ """ """
+ return self._timeout_secs / 60
security.declareProtected(MGMT_SCREEN_PERM, 'getAddNotificationTarget')
def getAddNotificationTarget(self):
@@ -279,16 +299,6 @@
#
- security.declareProtected(MGMT_SCREEN_PERM, 'getLen')
- def getLen(self):
-
- """
- Potentially expensive helper function to figure out how
- many items are contained.
- """
- return len(self._container)
-
-
security.declareProtected(MANAGE_CONTAINER_PERM,
'manage_changeTransientObjectContainer')
def manage_changeTransientObjectContainer(self, title='',
@@ -318,7 +328,7 @@
f = os.path.join(Globals.data_dir, "transientobjects.zexp")
self.c = PersistentMapping()
- for k, v in self._container.items():
+ for k, v in self.items():
self.c[k] = v
get_transaction().commit()
@@ -340,12 +350,217 @@
conn = self._p_jar
ob = conn.importFile(f)
for k,v in ob.items():
- self._container[k] = v
+ self[k] = v
if REQUEST is not None:
return MessageDialog(
title="Transient objects imported",
message="Transient objects imported from %s" % f,
action="manage_container")
+
+ def _setTimeout(self, timeout_mins):
+ if type(timeout_mins) is not type(1):
+ raise TypeError, (timeout_mins, "Must be integer")
+ self._timeout_secs = timeout_mins * 60
+
+ def _reset(self):
+ t_secs = self._timeout_secs
+ r_secs = self._resolution_secs = int(t_secs * self._err_margin) or 1
+ numbuckets = int(math.floor(t_secs/r_secs)) or 1
+ l = []
+ i = 0
+ now = int(time.time())
+ for x in range(numbuckets):
+ dump_after = now + i
+ c = self._ctype()
+ l.insert(0, [c, dump_after])
+ i = i + r_secs
+ index = self._ctype()
+ self._ring = Ring(l, index)
+
+
+ def _getCurrentBucket(self, get_dump=0):
+ # no timeout always returns last bucket
+ if not self._timeout_secs:
+ b, dump_after = self._ring._data[0]
+ return b
+ index = self._ring._index
+ now = int(time.time())
+ i = self._timeout_secs
+ # expire all buckets in the ring which have a dump_after time that
+ # is before now, turning the ring as many turns as necessary to
+ # get to a non-expirable bucket.
+ while 1:
+ l = b, dump_after = self._ring._data[-1]
+ if now > dump_after:
+ self._ring.turn()
+ # mutate elements in-place in the ring
+ new_dump_after = now + i
+ l[1] = new_dump_after
+ self._clean(b, index)
+ i = i + self._resolution_secs
+ else:
+ break
+ if get_dump:
+ return self._ring._data[0], dump_after, now
+ else:
+ b, dump_after = self._ring._data[0]
+ return b
+
+ def _clean(self, b, index):
+
+
+ # What is all this?
+ #for ob in b.values():
+ # d = last = None
+ # f = getattr(ob, self._onend, None)
+ # #
+ # # HUH?
+ # #
+ # getDataMgr = getattr(ob, 'getDataMgr', None)
+ # if getDataMgr is not None:
+ # if callable(getDataMgr):
+ # d = getDataMgr()
+ # if d != last:
+ # mgr = self.aq_parent.unrestrictedTraverse(d)
+ # last = d
+ # if callable(f): f(mgr)
+
+ for k, v in list(index.items()):
+ if v is b:
+ self.notifyDestruct(index[k])
+ del index[k]
+ b.clear()
+
+
+ def _show(self):
+ """ debug method """
+ b,dump,now = self._getCurrentBucket(1)
+ ringdumps = map(lambda x: `x[1]`[-4], self._ring)
+ t = (
+ "now: "+`now`[-4:],
+ "dump_after: "+`dump`[-4:],
+ "ring_dumps: "+`ringdumps`,
+ "ring: " + `self._ring`
+ )
+
+ for x in t:
+ print x
+
+
+ def __setitem__(self, k, v):
+ current = self._getCurrentBucket()
+ index = self._ring._index
+ b = index.get(k)
+ if b is None:
+ # this is a new key
+ index[k] = current
+ elif b is not current:
+ # this is an old key that isn't in the current bucket.
+ del b[k] # delete it from the old bucket
+ index[k] = current
+ # change the value
+ current[k] = v
+
+ def __getitem__(self, k):
+ current = self._getCurrentBucket()
+ index = self._ring._index
+ # the next line will raise the proper error if the item has expired
+ b = index[k]
+ v = b[k] # grab the value before we potentially time it out.
+ if b is not current:
+ # it's not optimal to do writes in getitem, but there's no choice.
+ # we accessed the object, so it should become current.
+ index[k] = current # change the index to the current bucket.
+ current[k] = v # add the value to the current bucket.
+ del b[k] # delete the item from the old bucket.
+ return v
+
+ security.declareProtected(ACCESS_SESSIONDATA_PERM, 'get')
+ def set(self, k, v):
+ """ """
+ if type(k) is not type(''):
+ raise TypeError, "Transient Object Container keys must be strings"
+ self[k] = v
+
+ security.declareProtected(ACCESS_SESSIONDATA_PERM, 'get')
+ # Uses a different marker than _notfound
+ def get(self, k, default=_marker):
+ try: v = self[k]
+ except KeyError: v = _marker
+ if v is _marker:
+ if default is _marker:
+ return None
+ else:
+ return default
+ return v
+
+ def __delitem__(self, k):
+ self._getCurrentBucket()
+ index = self._ring._index
+ b = index[k]
+ del index[k]
+ del b[k]
+
+ security.declareProtected(ACCESS_SESSIONDATA_PERM, '__len__')
+ def __len__(self):
+ self._getCurrentBucket()
+ return len(self._ring._index)
+
+ security.declareProtected(ACCESS_SESSIONDATA_PERM, 'has_key')
+ def has_key(self, k):
+ self._getCurrentBucket()
+ index = self._ring._index
+ return index.get(k, _notfound) is not _notfound
+
+ def values(self):
+ return map(lambda k, self=self: self[k], self.keys())
+
+ def items(self):
+ return map(lambda k, self=self: (k, self[k]), self.keys())
+
+ def keys(self):
+ self._getCurrentBucket()
+ index = self._ring._index
+ return map(lambda x: x, index.keys())
+
+ def update(self):
+ raise NotImplementedError
+
+ def clear(self):
+ raise NotImplementedError
+
+ def copy(self):
+ raise NotImplementedError
+
+ security.declareProtected(ACCESS_SESSIONDATA_PERM, 'getLen')
+ getLen = __len__
+
+class Ring(Persistent):
+ """ Instances of this class will be frequently written to the ZODB,
+ so it's optimized as best possible for write-friendliness """
+ def __init__(self, l, index):
+ if not len(l):
+ raise "ring must have at least one element"
+ self._data = l
+ self._index = index
+
+ def __repr__(self):
+ return repr(self._data)
+
+ def __len__(self):
+ return len(self._data)
+
+ def __getitem__(self, i):
+ return self._data[i]
+
+ def turn(self):
+ last = self._data.pop(-1)
+ self._data.insert(0, last)
+ self._p_changed = 1
+
+ def _p_independent(self):
+ return 1
+
class TransientObject(Persistent, Implicit):