Commit 1f2a7cce authored by Chris McDonough's avatar Chris McDonough

Merge from 2.7 branch:

- Add "instance-local" "period" to TransientObjectContainer.  This allows
  users to dial a knob which may (or may not) reduce the number of conflicts
  that happen during heavy sessioning usage by reducing the frequency at
  which buckets potentially expire at the cost of expiration time
  accuracy.  Previously, this setting was hardcoded to 20 (seconds) at
  module scope.

- Add 'session-resolution-seconds' to zope.conf.in/zopeschema.xml to
  control instance-local period for /temp_folder/session_data.

- Update TOC UI, interface, and help files to deal with instance-local
  period.

- Update OFS/Application to deal with instance-local period for default
  /temp/session_data TOC.

- Use __setstate__ for TOC upgrade instead of a dedicated _upgrade method
  (it was too hard to figure out where to call _upgrade from and when to
  call it).

- Perform a few formatting changes that should make it easier to merge the 2.7
  branch with the HEAD going forward.  I beseech those who make formatting
  changes to a branch or the HEAD make them to the other at that time
  as well, especially with the SVN/CVS split it's very painful to do merging
  when there are non-substantive differences between HEAD/maint.  When I was
  a child, I never thought I would need to use the word "beseech", however, it
  has indeed happened.
parent 0871e361
############################################################################ ##############################################################################
# #
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
# #
...@@ -375,9 +375,15 @@ class AppInitializer: ...@@ -375,9 +375,15 @@ class AppInitializer:
delnotify = getattr(config, 'session_delete_notify_script_path', delnotify = getattr(config, 'session_delete_notify_script_path',
None) None)
default_limit = 1000 default_limit = 1000
default_period_secs = 20
default_timeout_mins = 20
limit = (getattr(config, 'maximum_number_of_session_objects', None) limit = (getattr(config, 'maximum_number_of_session_objects', None)
or default_limit) or default_limit)
timeout_spec = getattr(config, 'session_timeout_minutes', None) timeout_spec = getattr(config, 'session_timeout_minutes',
default_timeout_mins)
period_spec = getattr(config, 'session_resolution_seconds',
default_period_secs)
if addnotify and app.unrestrictedTraverse(addnotify, None) is None: if addnotify and app.unrestrictedTraverse(addnotify, None) is None:
LOG('Zope Default Object Creation', WARNING, LOG('Zope Default Object Creation', WARNING,
...@@ -395,7 +401,8 @@ class AppInitializer: ...@@ -395,7 +401,8 @@ class AppInitializer:
'session_data', 'Session Data Container', 'session_data', 'Session Data Container',
addNotification = addnotify, addNotification = addnotify,
delNotification = delnotify, delNotification = delnotify,
limit=limit) limit=limit,
period_secs=period_spec)
if timeout_spec is not None: if timeout_spec is not None:
toc = TransientObjectContainer('session_data', toc = TransientObjectContainer('session_data',
...@@ -403,7 +410,8 @@ class AppInitializer: ...@@ -403,7 +410,8 @@ class AppInitializer:
timeout_mins = timeout_spec, timeout_mins = timeout_spec,
addNotification = addnotify, addNotification = addnotify,
delNotification = delnotify, delNotification = delnotify,
limit=limit) limit=limit,
period_secs = period_spec)
tf._setObject('session_data', toc) tf._setObject('session_data', toc)
tf_reserved = getattr(tf, '_reserved_names', ()) tf_reserved = getattr(tf, '_reserved_names', ())
......
...@@ -16,8 +16,6 @@ Transient Object Container Class ('timeslice'-based design, no index). ...@@ -16,8 +16,6 @@ Transient Object Container Class ('timeslice'-based design, no index).
$Id$ $Id$
""" """
__version__='$Revision: 1.32.12.5 $'[11:-2]
import math import math
import time import time
import random import random
...@@ -54,7 +52,6 @@ CREATE_TRANSIENTS_PERM = 'Create Transient Objects' ...@@ -54,7 +52,6 @@ CREATE_TRANSIENTS_PERM = 'Create Transient Objects'
ACCESS_TRANSIENTS_PERM = 'Access Transient Objects' ACCESS_TRANSIENTS_PERM = 'Access Transient Objects'
MANAGE_CONTAINER_PERM = 'Manage Transient Object Container' MANAGE_CONTAINER_PERM = 'Manage Transient Object Container'
PERIOD = 20 # signifies "resolution" of transience machinery in seconds
SPARE_BUCKETS = 15 # minimum number of buckets to keep "spare" SPARE_BUCKETS = 15 # minimum number of buckets to keep "spare"
BUCKET_CLASS = OOBTree # constructor for buckets BUCKET_CLASS = OOBTree # constructor for buckets
STRICT = os.environ.get('Z_TOC_STRICT', '') STRICT = os.environ.get('Z_TOC_STRICT', '')
...@@ -80,10 +77,11 @@ constructTransientObjectContainerForm = HTMLFile( ...@@ -80,10 +77,11 @@ constructTransientObjectContainerForm = HTMLFile(
'dtml/addTransientObjectContainer', globals()) 'dtml/addTransientObjectContainer', globals())
def constructTransientObjectContainer(self, id, title='', timeout_mins=20, def constructTransientObjectContainer(self, id, title='', timeout_mins=20,
addNotification=None, delNotification=None, limit=0, REQUEST=None): addNotification=None, delNotification=None, limit=0, period_secs=20,
REQUEST=None):
""" """ """ """
ob = TransientObjectContainer(id, title, timeout_mins, ob = TransientObjectContainer(id, title, timeout_mins,
addNotification, delNotification, limit=limit) addNotification, delNotification, limit=limit, period_secs=period_secs)
self._setObject(id, ob) self._setObject(id, ob)
if REQUEST is not None: if REQUEST is not None:
return self.manage_main(self, REQUEST, update_menu=1) return self.manage_main(self, REQUEST, update_menu=1)
...@@ -133,10 +131,10 @@ class TransientObjectContainer(SimpleItem): ...@@ -133,10 +131,10 @@ class TransientObjectContainer(SimpleItem):
security.setDefaultAccess('deny') security.setDefaultAccess('deny')
def __init__(self, id, title='', timeout_mins=20, addNotification=None, def __init__(self, id, title='', timeout_mins=20, addNotification=None,
delNotification=None, limit=0): delNotification=None, limit=0, period_secs=20):
self.id = id self.id = id
self.title=title self.title=title
self._setTimeout(timeout_mins) self._setTimeout(timeout_mins, period_secs)
self._setLimit(limit) self._setLimit(limit)
self.setDelNotificationTarget(delNotification) self.setDelNotificationTarget(delNotification)
self.setAddNotificationTarget(addNotification) self.setAddNotificationTarget(addNotification)
...@@ -144,12 +142,42 @@ class TransientObjectContainer(SimpleItem): ...@@ -144,12 +142,42 @@ class TransientObjectContainer(SimpleItem):
# helpers # helpers
def _setTimeout(self, timeout_mins): def _setTimeout(self, timeout_mins, period_secs):
if type(timeout_mins) is not type(1): if type(timeout_mins) is not type(1):
raise TypeError, (escape(`timeout_mins`), "Must be integer") raise TypeError, (escape(`timeout_mins`), "Must be integer")
self._timeout_secs = t_secs = timeout_mins * 60
# timeout_slices == fewest number of timeslices that's >= t_secs if type(period_secs) is not type(1):
self._timeout_slices=int(math.ceil(float(t_secs)/PERIOD)) raise TypeError, (escape(`period_secs`), "Must be integer")
timeout_secs = timeout_mins * 60
# special-case 0-minute timeout value by ignoring period
if timeout_secs != 0:
if period_secs == 0:
raise ValueError('resolution cannot be 0')
if period_secs > timeout_secs:
raise ValueError(
'resolution cannot be greater than timeout '
'minutes * 60 ( %s > %s )' % (period_secs, timeout_secs))
# we need the timeout to be evenly divisible by the period
if timeout_secs % period_secs != 0:
raise ValueError(
'timeout seconds (%s) must be evenly divisible '
'by resolution (%s)' % (timeout_secs, period_secs)
)
# our timeout secs is the number of seconds that an item should
# remain unexpired
self._timeout_secs = timeout_secs
# our _period is the number of seconds that constitutes a timeslice
self._period = period_secs
# timeout_slices == fewest number of timeslices that's >= timeout_secs
self._timeout_slices=int(math.ceil(float(timeout_secs)/period_secs))
def _setLimit(self, limit): def _setLimit(self, limit):
if type(limit) is not type(1): if type(limit) is not type(1):
...@@ -173,7 +201,10 @@ class TransientObjectContainer(SimpleItem): ...@@ -173,7 +201,10 @@ class TransientObjectContainer(SimpleItem):
# populate _data with some number of buckets, each of which # populate _data with some number of buckets, each of which
# is "current" for its timeslice key # is "current" for its timeslice key
if self._timeout_slices: if self._timeout_slices:
new_slices = getTimeslices(getCurrentTimeslice(), SPARE_BUCKETS*2) new_slices = getTimeslices(
getCurrentTimeslice(self._period),
SPARE_BUCKETS*2,
self._period)
for i in new_slices: for i in new_slices:
self._data[i] = BUCKET_CLASS() self._data[i] = BUCKET_CLASS()
# create an Increaser for max timeslice # create an Increaser for max timeslice
...@@ -190,32 +221,26 @@ class TransientObjectContainer(SimpleItem): ...@@ -190,32 +221,26 @@ class TransientObjectContainer(SimpleItem):
def _getCurrentSlices(self, now): def _getCurrentSlices(self, now):
if self._timeout_slices: if self._timeout_slices:
begin = now - (PERIOD * self._timeout_slices) begin = now - (self._period * self._timeout_slices)
# add add one to _timeout_slices below to account for the fact that # add add one to _timeout_slices below to account for the fact that
# a call to this method may happen any time within the current # a call to this method may happen any time within the current
# timeslice; calling it in the beginning of the timeslice can lead # timeslice; calling it in the beginning of the timeslice can lead
# to sessions becoming invalid a maximum of <PERIOD> seconds # to sessions becoming invalid a maximum of self._period seconds
# earlier than the requested timeout value. Adding one here can # earlier than the requested timeout value. Adding one here can
# lead to sessions becoming invalid *later* than the timeout value # lead to sessions becoming invalid *later* than the timeout value
# (also by a max of <PERIOD>), but in the common sessioning case, # (also by a max of self._period), but in the common sessioning
# that seems preferable. # case, that seems preferable.
num_slices = self._timeout_slices + 1 num_slices = self._timeout_slices + 1
else: else:
return [0] # sentinel for timeout value 0 (don't expire) return [0] # sentinel for timeout value 0 (don't expire)
DEBUG and TLOG('_getCurrentSlices, now = %s ' % now) DEBUG and TLOG('_getCurrentSlices, now = %s ' % now)
DEBUG and TLOG('_getCurrentSlices, begin = %s' % begin) DEBUG and TLOG('_getCurrentSlices, begin = %s' % begin)
DEBUG and TLOG('_getCurrentSlices, num_slices = %s' % num_slices) DEBUG and TLOG('_getCurrentSlices, num_slices = %s' % num_slices)
result = getTimeslices(begin, num_slices) result = getTimeslices(begin, num_slices, self._period)
DEBUG and TLOG('_getCurrentSlices, result = %s' % result) DEBUG and TLOG('_getCurrentSlices, result = %s' % result)
return result return result
def _move_item(self, k, current_ts, default=None): def _move_item(self, k, current_ts, default=None):
if not getattr(self, '_max_timeslice', None):
# in-place upgrade for old instances; this would usually be
# "evil" but sessions are all about write-on-read anyway,
# so it really doesn't matter.
self._upgrade()
if self._timeout_slices: if self._timeout_slices:
if self._roll(current_ts, 'replentish'): if self._roll(current_ts, 'replentish'):
...@@ -289,12 +314,8 @@ class TransientObjectContainer(SimpleItem): ...@@ -289,12 +314,8 @@ class TransientObjectContainer(SimpleItem):
return item return item
def _all(self): def _all(self):
if not getattr(self, '_max_timeslice', None):
# in-place upgrade for old instances
self._upgrade()
if self._timeout_slices: if self._timeout_slices:
current_ts = getCurrentTimeslice() current_ts = getCurrentTimeslice(self._period)
else: else:
current_ts = 0 current_ts = 0
...@@ -352,7 +373,7 @@ class TransientObjectContainer(SimpleItem): ...@@ -352,7 +373,7 @@ class TransientObjectContainer(SimpleItem):
def __getitem__(self, k): def __getitem__(self, k):
if self._timeout_slices: if self._timeout_slices:
current_ts = getCurrentTimeslice() current_ts = getCurrentTimeslice(self._period)
else: else:
current_ts = 0 current_ts = 0
item = self._move_item(k, current_ts, _marker) item = self._move_item(k, current_ts, _marker)
...@@ -366,7 +387,7 @@ class TransientObjectContainer(SimpleItem): ...@@ -366,7 +387,7 @@ class TransientObjectContainer(SimpleItem):
def __setitem__(self, k, v): def __setitem__(self, k, v):
DEBUG and TLOG('__setitem__: called with key %s, value %s' % (k,v)) DEBUG and TLOG('__setitem__: called with key %s, value %s' % (k,v))
if self._timeout_slices: if self._timeout_slices:
current_ts = getCurrentTimeslice() current_ts = getCurrentTimeslice(self._period)
else: else:
current_ts = 0 current_ts = 0
item = self._move_item(k, current_ts, _marker) item = self._move_item(k, current_ts, _marker)
...@@ -398,7 +419,7 @@ class TransientObjectContainer(SimpleItem): ...@@ -398,7 +419,7 @@ class TransientObjectContainer(SimpleItem):
def __delitem__(self, k): def __delitem__(self, k):
DEBUG and TLOG('__delitem__ called with key %s' % k) DEBUG and TLOG('__delitem__ called with key %s' % k)
if self._timeout_slices: if self._timeout_slices:
current_ts = getCurrentTimeslice() current_ts = getCurrentTimeslice(self._period)
else: else:
current_ts = 0 current_ts = 0
item = self._move_item(k, current_ts) item = self._move_item(k, current_ts)
...@@ -418,7 +439,7 @@ class TransientObjectContainer(SimpleItem): ...@@ -418,7 +439,7 @@ class TransientObjectContainer(SimpleItem):
def get(self, k, default=None): def get(self, k, default=None):
DEBUG and TLOG('get: called with key %s, default %s' % (k, default)) DEBUG and TLOG('get: called with key %s, default %s' % (k, default))
if self._timeout_slices: if self._timeout_slices:
current_ts = getCurrentTimeslice() current_ts = getCurrentTimeslice(self._period)
else: else:
current_ts = 0 current_ts = 0
item = self._move_item(k, current_ts, default) item = self._move_item(k, current_ts, default)
...@@ -431,7 +452,7 @@ class TransientObjectContainer(SimpleItem): ...@@ -431,7 +452,7 @@ class TransientObjectContainer(SimpleItem):
security.declareProtected(ACCESS_TRANSIENTS_PERM, 'has_key') security.declareProtected(ACCESS_TRANSIENTS_PERM, 'has_key')
def has_key(self, k): def has_key(self, k):
if self._timeout_slices: if self._timeout_slices:
current_ts = getCurrentTimeslice() current_ts = getCurrentTimeslice(self._period)
else: else:
current_ts = 0 current_ts = 0
DEBUG and TLOG('has_key: calling _move_item with %s' % str(k)) DEBUG and TLOG('has_key: calling _move_item with %s' % str(k))
...@@ -455,8 +476,8 @@ class TransientObjectContainer(SimpleItem): ...@@ -455,8 +476,8 @@ class TransientObjectContainer(SimpleItem):
is to minimize the chance that two threads will attempt to is to minimize the chance that two threads will attempt to
do housekeeping at the same time (causing conflicts). do housekeeping at the same time (causing conflicts).
""" """
low = now/PERIOD low = now/self._period
high = self._max_timeslice()/PERIOD high = self._max_timeslice()/self._period
if high <= low: if high <= low:
# we really need to win this roll because we have no # we really need to win this roll because we have no
# spare buckets (and no valid values to provide to randrange), so # spare buckets (and no valid values to provide to randrange), so
...@@ -481,7 +502,7 @@ class TransientObjectContainer(SimpleItem): ...@@ -481,7 +502,7 @@ class TransientObjectContainer(SimpleItem):
return # do nothing if no timeout return # do nothing if no timeout
max_ts = self._max_timeslice() max_ts = self._max_timeslice()
available_spares = (max_ts-now) / PERIOD available_spares = (max_ts-now) / self._period
DEBUG and TLOG('_replentish: now = %s' % now) DEBUG and TLOG('_replentish: now = %s' % now)
DEBUG and TLOG('_replentish: max_ts = %s' % max_ts) DEBUG and TLOG('_replentish: max_ts = %s' % max_ts)
DEBUG and TLOG('_replentish: available_spares = %s' DEBUG and TLOG('_replentish: available_spares = %s'
...@@ -490,19 +511,19 @@ class TransientObjectContainer(SimpleItem): ...@@ -490,19 +511,19 @@ class TransientObjectContainer(SimpleItem):
if available_spares < SPARE_BUCKETS: if available_spares < SPARE_BUCKETS:
if max_ts < now: if max_ts < now:
replentish_start = now replentish_start = now
replentish_end = now + (PERIOD * SPARE_BUCKETS) replentish_end = now + (self._period * SPARE_BUCKETS)
else: else:
replentish_start = max_ts + PERIOD replentish_start = max_ts + self._period
replentish_end = max_ts + (PERIOD * SPARE_BUCKETS) replentish_end = max_ts + (self._period * SPARE_BUCKETS)
DEBUG and TLOG('_replentish: replentish_start = %s' % DEBUG and TLOG('_replentish: replentish_start = %s' %
replentish_start) replentish_start)
DEBUG and TLOG('_replentish: replentish_end = %s' DEBUG and TLOG('_replentish: replentish_end = %s'
% replentish_end) % replentish_end)
# n is the number of buckets to create # n is the number of buckets to create
n = (replentish_end - replentish_start) / PERIOD n = (replentish_end - replentish_start) / self._period
new_buckets = getTimeslices(replentish_start, n) new_buckets = getTimeslices(replentish_start, n, self._period)
new_buckets.reverse() new_buckets.reverse()
STRICT and _assert(new_buckets) STRICT and _assert(new_buckets)
DEBUG and TLOG('_replentish: adding %s new buckets' % n) DEBUG and TLOG('_replentish: adding %s new buckets' % n)
...@@ -523,8 +544,8 @@ class TransientObjectContainer(SimpleItem): ...@@ -523,8 +544,8 @@ class TransientObjectContainer(SimpleItem):
return # dont do gc if there is no timeout return # dont do gc if there is no timeout
if now is None: if now is None:
now = getCurrentTimeslice() # for unit tests now = getCurrentTimeslice(self._period) # for unit tests
max_ts = now - (PERIOD * (self._timeout_slices + 1)) max_ts = now - (self._period * (self._timeout_slices + 1))
to_notify = [] to_notify = []
...@@ -633,16 +654,26 @@ class TransientObjectContainer(SimpleItem): ...@@ -633,16 +654,26 @@ class TransientObjectContainer(SimpleItem):
# TransientItemContainer methods # TransientItemContainer methods
security.declareProtected(MANAGE_CONTAINER_PERM, 'setTimeoutMinutes') security.declareProtected(MANAGE_CONTAINER_PERM, 'setTimeoutMinutes')
def setTimeoutMinutes(self, timeout_mins): def setTimeoutMinutes(self, timeout_mins, period_secs=20):
""" """ """ The period_secs parameter is defaulted to preserve backwards API
if timeout_mins != self.getTimeoutMinutes(): compatibility. In older versions of this code, period was
self._setTimeout(timeout_mins) hardcoded to 20. """
timeout_secs = timeout_mins * 60
if (timeout_mins != self.getTimeoutMinutes()
or period_secs != self.getPeriodSeconds()):
# do nothing unless something has changed
self._setTimeout(timeout_mins, period_secs)
self._reset() self._reset()
def getTimeoutMinutes(self): def getTimeoutMinutes(self):
""" """ """ """
return self._timeout_secs / 60 return self._timeout_secs / 60
def getPeriodSeconds(self):
""" """
return self._period
security.declareProtected(MGMT_SCREEN_PERM, 'getSubobjectLimit') security.declareProtected(MGMT_SCREEN_PERM, 'getSubobjectLimit')
def getSubobjectLimit(self): def getSubobjectLimit(self):
""" """ """ """
...@@ -681,11 +712,11 @@ class TransientObjectContainer(SimpleItem): ...@@ -681,11 +712,11 @@ class TransientObjectContainer(SimpleItem):
'manage_changeTransientObjectContainer') 'manage_changeTransientObjectContainer')
def manage_changeTransientObjectContainer( def manage_changeTransientObjectContainer(
self, title='', timeout_mins=20, addNotification=None, self, title='', timeout_mins=20, addNotification=None,
delNotification=None, limit=0, REQUEST=None delNotification=None, limit=0, period_secs=20, REQUEST=None
): ):
""" Change an existing transient object container. """ """ Change an existing transient object container. """
self.title = title self.title = title
self.setTimeoutMinutes(timeout_mins) self.setTimeoutMinutes(timeout_mins, period_secs)
self.setSubobjectLimit(limit) self.setSubobjectLimit(limit)
if not addNotification: if not addNotification:
addNotification = None addNotification = None
...@@ -699,49 +730,61 @@ class TransientObjectContainer(SimpleItem): ...@@ -699,49 +730,61 @@ class TransientObjectContainer(SimpleItem):
self, REQUEST, manage_tabs_message='Changes saved.' self, REQUEST, manage_tabs_message='Changes saved.'
) )
def _upgrade(self): def __setstate__(self, state):
# inplace upgrade for versions of Transience in Zope versions less # upgrade versions of Transience in Zope versions less
# than 2.7.1, which used a different transience mechanism. Note: # than 2.7.1, which used a different transience mechanism. Note:
# this will not work for upgrading versions older than 2.6.0, # this will not work for upgrading versions older than 2.6.0,
# all of which used a very different transience implementation # all of which used a very different transience implementation
if not getattr(self, '_max_timeslice', None): # can't make __len__ an instance variable in new-style classes
new_slices = getTimeslices(getCurrentTimeslice(), SPARE_BUCKETS*2)
# f/w compat: 2.8 cannot use __len__ as an instance variable
if not state.has_key('_length'):
length = state.get('__len__', Length())
self._length = self.getLen = length
# TOCs prior to 2.7.1 took their period from a global
if not state.has_key('_period'):
self._period = 20 # this was the default for all prior releases
# TOCs prior to 2.7.1 used a different set of data structures
# for efficiently keeping tabs on the maximum slice
if not state.has_key('_max_timeslice'):
new_slices = getTimeslices(
getCurrentTimeslice(self._period),
SPARE_BUCKETS*2,
self._period)
for i in new_slices: for i in new_slices:
if not self._data.has_key(i): if not self._data.has_key(i):
self._data[i] = BUCKET_CLASS() self._data[i] = BUCKET_CLASS()
# create an Increaser for max timeslice # create an Increaser for max timeslice
self._max_timeslice = Increaser(max(new_slices)) self._max_timeslice = Increaser(max(new_slices))
# can't make __len__ an instance variable in new-style classes # we should probably delete older attributes from state such as
if not getattr(self, '_length', None):
length = self.__dict__.get('__len__', Length())
self._length = self.getLen = length
# we should probably delete older attributes such as
# '_last_timeslice', '_deindex_next',and '__len__' here but we leave # '_last_timeslice', '_deindex_next',and '__len__' here but we leave
# them in order to allow people to switch between 2.6.0->2.7.0 and # them in order to allow people to switch between 2.6.0->2.7.0 and
# 2.7.1+ as necessary (although that has not been tested) # 2.7.1+ as necessary (although that has not been tested)
self.__dict__.update(state)
def getCurrentTimeslice(): def getCurrentTimeslice(period):
""" """
Return an integer representing the 'current' timeslice. Return an integer representing the 'current' timeslice.
The current timeslice is guaranteed to be the same integer The current timeslice is guaranteed to be the same integer
within a 'slice' of time based on a divisor of 'period'. within a 'slice' of time based on a divisor of 'self._period'.
'period' is the number of seconds in a slice. 'self._period' is the number of seconds in a slice.
""" """
now = time.time() now = time.time()
low = int(math.floor(now)) - PERIOD + 1 low = int(math.floor(now)) - period + 1
high = int(math.ceil(now)) + 1 high = int(math.ceil(now)) + 1
for x in range(low, high): for x in range(low, high):
if x % PERIOD == 0: if x % period == 0:
return x return x
def getTimeslices(begin, n): def getTimeslices(begin, n, period):
""" Get a list of future timeslice integers of 'n' size in descending """ Get a list of future timeslice integers of 'n' size in descending
order """ order """
l = [] l = []
for x in range(n): for x in range(n):
l.insert(0, begin + (x * PERIOD)) l.insert(0, begin + (x * period))
return l return l
def _assert(case): def _assert(case):
......
...@@ -62,7 +62,7 @@ the Zope physical path to the method to be invoked to receive the notification ...@@ -62,7 +62,7 @@ the Zope physical path to the method to be invoked to receive the notification
<TR> <TR>
<TD ALIGN="LEFT" VALIGN="TOP"> <TD ALIGN="LEFT" VALIGN="TOP">
<div class="form-label"> <div class="form-label">
Data object timeout in minutes Data object timeout (in minutes)
</div> </div>
<div class="form-help"> <div class="form-help">
("0" means no expiration) ("0" means no expiration)
...@@ -73,6 +73,20 @@ the Zope physical path to the method to be invoked to receive the notification ...@@ -73,6 +73,20 @@ the Zope physical path to the method to be invoked to receive the notification
</TD> </TD>
</TR> </TR>
<TR>
<TD ALIGN="LEFT" VALIGN="TOP">
<div class="form-label">
Timeout resolution (in seconds)
</div>
<div class="form-help">
(accept the default if you're not sure)
</div>
</TD>
<TD ALIGN="LEFT" VALIGN="TOP">
<INPUT TYPE="TEXT" NAME="period_secs:int" SIZE="10" value="20">
</TD>
</TR>
<TR> <TR>
<TD ALIGN="LEFT" VALIGN="TOP"> <TD ALIGN="LEFT" VALIGN="TOP">
<div class="form-label"> <div class="form-label">
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
help_topic='Transience-change.stx' help_topic='Transience-change.stx'
)"> )">
<form action="manage_changeTransientObjectContainer" method="post"> <form action="manage_changeTransientObjectContainer" method="post">
<p class="form-help"> <p class="form-help">
...@@ -13,7 +12,9 @@ Transient Object Containers are used to store transient data. ...@@ -13,7 +12,9 @@ Transient Object Containers are used to store transient data.
Transient data will persist, but only for a user-specified period of time Transient data will persist, but only for a user-specified period of time
(the "data object timeout") after which it will be flushed. (the "data object timeout") after which it will be flushed.
</p> </p>
<dtml-call nudge><!-- turn the buckets if necessary --> <dtml-call nudge><!-- turn the buckets if necessary -->
<p class="form-label"> <p class="form-label">
<font color="green"> <font color="green">
<dtml-let l=getLen> <dtml-let l=getLen>
...@@ -43,7 +44,7 @@ Transient data will persist, but only for a user-specified period of time ...@@ -43,7 +44,7 @@ Transient data will persist, but only for a user-specified period of time
<tr> <tr>
<td align="left" valign="top"> <td align="left" valign="top">
<div class="form-label"> <div class="form-label">
Data object timeout value in minutes Data object timeout value (in minutes)
</div> </div>
<div class="form-help"> <div class="form-help">
("0" means no expiration) ("0" means no expiration)
...@@ -55,6 +56,26 @@ Transient data will persist, but only for a user-specified period of time ...@@ -55,6 +56,26 @@ Transient data will persist, but only for a user-specified period of time
</td> </td>
</tr> </tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Timeout resolution (in seconds)
</div>
<div class="form-help">
Defines what the "resolution" of item timeout is. Setting this higher
allows the transience machinery to do fewer "writes" at the expense of
causing items to time out later than the "Data object timeout value" by
a factor of (at most) this many seconds. This number must divide evenly
into the number of timeout seconds ("Data object timeout value" * 60)
and cannot be set higher than the timeout value in seconds.
</div>
</td>
<td align="left" valign="top">
<input type="text" name="period_secs:int" size=10
value=&dtml-getPeriodSeconds;>
</td>
</tr>
<tr> <tr>
<td align="left" valign="top"> <td align="left" valign="top">
<div class="form-label"> <div class="form-label">
...@@ -100,6 +121,22 @@ Transient data will persist, but only for a user-specified period of time ...@@ -100,6 +121,22 @@ Transient data will persist, but only for a user-specified period of time
</td> </td>
</tr> </tr>
<dtml-let l=getLen>
<dtml-if l>
<tr>
<td colspan=2>
<br/>
<p class="form-label">
<font color="red">WARNING!</font>
All data objects existing in this transient object container
will be deleted when the data object timeout or expiration resolution
is changed.
</p>
</tr>
</td>
</dtml-if>
</dtml-let>
<tr> <tr>
<td></td> <td></td>
<td> <td>
...@@ -109,12 +146,5 @@ Transient data will persist, but only for a user-specified period of time ...@@ -109,12 +146,5 @@ Transient data will persist, but only for a user-specified period of time
</table> </table>
</form> </form>
<p class="form-label">
<font color="red">WARNING!</font>
The data objects existing in this transient object container
will be deleted when the data object timeout is changed.
</p>
<dtml-var manage_page_footer> <dtml-var manage_page_footer>
...@@ -23,13 +23,23 @@ TransientObjectContainer - Add ...@@ -23,13 +23,23 @@ TransientObjectContainer - Add
The title of the object. The title of the object.
- **Data object timeout in minutes** - **Data object timeout (in minutes)**
The minimum number of minutes that objects in the container will The minimum number of minutes that objects in the container will
persist for. Objects in the container are passively deleted, so persist for. Objects in the container are passively deleted, so
they may not be deleted exactly after this number of minutes elapses. they may not be deleted exactly after this number of minutes elapses.
A setting of "0" indicates that objects should not expire. A setting of "0" indicates that objects should not expire.
- **Timeout resolution (in seconds)**
Defines what the "resolution" of item timeout is. Setting this
higher allows the transience machinery to do fewer "writes" at
the expense of causing items to time out later than the "Data
object timeout value" by a factor of (at most) this many
seconds. This number must divide evenly into the number of
timeout seconds ("Data object timeout value" * 60) and cannot
be set higher than the timeout value in seconds.
- **Maximum number of subobjects ** - **Maximum number of subobjects **
The maximum number of subobjects that this container may The maximum number of subobjects that this container may
......
...@@ -23,7 +23,7 @@ TransientObjectContainer - Manage ...@@ -23,7 +23,7 @@ TransientObjectContainer - Manage
The title of the object. The title of the object.
- **Data object timeout in minutes** - **Data object timeout (in minutes)**
The minimum number of minutes that objects in the container will The minimum number of minutes that objects in the container will
persist for. Objects in the container are passively deleted, so persist for. Objects in the container are passively deleted, so
...@@ -34,6 +34,16 @@ TransientObjectContainer - Manage ...@@ -34,6 +34,16 @@ TransientObjectContainer - Manage
If the timeout value is "0", objects will not time out. If the timeout value is "0", objects will not time out.
- **Timeout resolution (in seconds)**
Defines what the "resolution" of item timeout is. Setting this
higher allows the transience machinery to do fewer "writes" at
the expense of causing items to time out later than the "Data
object timeout value" by a factor of (at most) this many
seconds. This number must divide evenly into the number of
timeout seconds ("Data object timeout value" * 60) and cannot
be set higher than the timeout value in seconds.
- **Maximum number of subobjects ** - **Maximum number of subobjects **
The maximum number of subobjects that this container may The maximum number of subobjects that this container may
...@@ -92,11 +102,6 @@ TransientObjectContainer - Manage ...@@ -92,11 +102,6 @@ TransientObjectContainer - Manage
from zLOG import LOG from zLOG import LOG
LOG(100, 'test', 'id: %s' % item.getId()) LOG(100, 'test', 'id: %s' % item.getId())
Environment Variables
You can control some transient object settings with environment
variables. See 'doc/ENVIORNMENT.txt' for more informatin.
See Also See Also
- "Transience API":TransienceInterfaces.py - "Transience API":TransienceInterfaces.py
......
...@@ -80,10 +80,12 @@ class TransientObjectContainer: ...@@ -80,10 +80,12 @@ class TransientObjectContainer:
Permission -- 'Create Transient Objects' Permission -- 'Create Transient Objects'
""" """
def setTimeoutMinutes(self, timeout_mins): def setTimeoutMinutes(self, timeout_mins, period=20):
""" """
Set the number of minutes of inactivity allowable for subobjects Set the number of minutes of inactivity allowable for subobjects
before they expire. before they expire (timeout_mins) as well as the 'timeout resolution'
in seconds (period). 'timeout_mins' * 60 must be evenly divisible
by the period. Period must be less than 'timeout_mins' * 60.
Permission -- 'Manage Transient Object Container' Permission -- 'Manage Transient Object Container'
""" """
...@@ -96,6 +98,13 @@ class TransientObjectContainer: ...@@ -96,6 +98,13 @@ class TransientObjectContainer:
Permission -- 'View management screens' Permission -- 'View management screens'
""" """
def getPeriodSeconds(self):
"""
Return the 'timeout resolution' in seconds.
Permission -- 'View management screens'
"""
def getAddNotificationTarget(self): def getAddNotificationTarget(self):
""" """
Returns the current 'after add' function, or None. Returns the current 'after add' function, or None.
......
...@@ -34,7 +34,9 @@ class TestBase(TestCase): ...@@ -34,7 +34,9 @@ class TestBase(TestCase):
self.errmargin = .20 self.errmargin = .20
self.timeout = 120 self.timeout = 120
self.t = TransientObjectContainer('sdc', timeout_mins=self.timeout/60) self.period = 20
self.t = TransientObjectContainer('sdc', timeout_mins=self.timeout/60,
period_secs=self.period)
def tearDown(self): def tearDown(self):
self.t = None self.t = None
...@@ -267,7 +269,7 @@ class TestTransientObjectContainer(TestBase): ...@@ -267,7 +269,7 @@ class TestTransientObjectContainer(TestBase):
self.assertEqual(len(self.t.keys()), 0) self.assertEqual(len(self.t.keys()), 0)
# 2 minutes # 2 minutes
self.t._setTimeout(self.timeout/60*2) self.t._setTimeout(self.timeout/60*2, self.period)
self.t._reset() self.t._reset()
for x in range(10, 110): for x in range(10, 110):
self.t[x] = x self.t[x] = x
...@@ -279,7 +281,7 @@ class TestTransientObjectContainer(TestBase): ...@@ -279,7 +281,7 @@ class TestTransientObjectContainer(TestBase):
self.assertEqual(len(self.t.keys()), 0) self.assertEqual(len(self.t.keys()), 0)
# 3 minutes # 3 minutes
self.t._setTimeout(self.timeout/60*3) self.t._setTimeout(self.timeout/60*3, self.period)
self.t._reset() self.t._reset()
for x in range(10, 110): for x in range(10, 110):
self.t[x] = x self.t[x] = x
...@@ -318,7 +320,8 @@ class TestTransientObjectContainer(TestBase): ...@@ -318,7 +320,8 @@ class TestTransientObjectContainer(TestBase):
def testLen(self): def testLen(self):
# This test must not time out else it will fail. # This test must not time out else it will fail.
self.t._setTimeout(self.timeout) # make timeout extremely unlikely # make timeout extremely unlikely by setting it very high
self.t._setTimeout(self.timeout, self.period)
added = {} added = {}
r = range(10, 1010) r = range(10, 1010)
for x in r: for x in r:
...@@ -340,8 +343,9 @@ class TestTransientObjectContainer(TestBase): ...@@ -340,8 +343,9 @@ class TestTransientObjectContainer(TestBase):
def testGetTimeoutMinutesWorks(self): def testGetTimeoutMinutesWorks(self):
self.assertEqual(self.t.getTimeoutMinutes(), self.timeout / 60) self.assertEqual(self.t.getTimeoutMinutes(), self.timeout / 60)
self.t._setTimeout(10) self.t._setTimeout(10, 30)
self.assertEqual(self.t.getTimeoutMinutes(), 10) self.assertEqual(self.t.getTimeoutMinutes(), 10)
self.assertEqual(self.t.getPeriodSeconds(), 30)
def test_new(self): def test_new(self):
t = self.t.new('foobieblech') t = self.t.new('foobieblech')
...@@ -369,7 +373,7 @@ class TestTransientObjectContainer(TestBase): ...@@ -369,7 +373,7 @@ class TestTransientObjectContainer(TestBase):
self.assertRaises(MaxTransientObjectsExceeded, self._maxOut) self.assertRaises(MaxTransientObjectsExceeded, self._maxOut)
def testZeroTimeoutMeansPersistForever(self): def testZeroTimeoutMeansPersistForever(self):
self.t._setTimeout(0) self.t._setTimeout(0, self.period)
self.t._reset() self.t._reset()
for x in range(10, 110): for x in range(10, 110):
self.t[x] = x self.t[x] = x
......
...@@ -627,6 +627,16 @@ ...@@ -627,6 +627,16 @@
<metadefault>20</metadefault> <metadefault>20</metadefault>
</key> </key>
<key name="session-resolution-seconds" datatype="integer"
default="20">
<description>
An integer value representing the number of seconds to be used as the
"timeout resolution" of the '/temp_folder/session_data' transient
object container in Zope's object database.
</description>
<metadefault>20</metadefault>
</key>
<key name="suppress-all-access-rules" datatype="boolean" <key name="suppress-all-access-rules" datatype="boolean"
default="off" handler="suppress_all_access_rules"> default="off" handler="suppress_all_access_rules">
<description> <description>
......
...@@ -548,6 +548,20 @@ instancehome $INSTANCE ...@@ -548,6 +548,20 @@ instancehome $INSTANCE
# session-timeout-minutes 30 # session-timeout-minutes 30
# Directive: session-resolution-seconds
#
# Description:
# An integer value representing the number of seconds to be used as the
# "timeout resolution" of the '/temp_folder/session_data' transient
# object container.
#
# Default: 20
#
# Example:
#
# session-resolution-seconds 60
# Directive: suppress-all-access-rules # Directive: suppress-all-access-rules
# #
# Description: # Description:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment