[Checkins] SVN: zope.session/trunk/ prepare for 3.5.2
Gary Poster
gary at zope.com
Thu Jun 12 17:02:43 EDT 2008
Log message for revision 87351:
prepare for 3.5.2
Changed:
U zope.session/trunk/CHANGES.txt
U zope.session/trunk/setup.py
U zope.session/trunk/src/zope/session/interfaces.py
U zope.session/trunk/src/zope/session/session.py
U zope.session/trunk/src/zope/session/tests.py
-=-
Modified: zope.session/trunk/CHANGES.txt
===================================================================
--- zope.session/trunk/CHANGES.txt 2008-06-12 20:31:16 UTC (rev 87350)
+++ zope.session/trunk/CHANGES.txt 2008-06-12 21:02:43 UTC (rev 87351)
@@ -2,6 +2,12 @@
CHANGES
=======
+version 3.5.2 (2008-06-12)
+--------------------------
+
+- Remove ConflictErrors caused on SessionData caused by setting
+ ``lastAccessTime``.
+
Version 3.5.1 (2008-04-30)
--------------------------
Modified: zope.session/trunk/setup.py
===================================================================
--- zope.session/trunk/setup.py 2008-06-12 20:31:16 UTC (rev 87350)
+++ zope.session/trunk/setup.py 2008-06-12 21:02:43 UTC (rev 87351)
@@ -24,7 +24,7 @@
return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
setup(name='zope.session',
- version = '3.5.2dev',
+ version = '3.5.2',
author='Zope Corporation and Contributors',
author_email='zope3-dev at zope.org',
description='Zope 3 Session Support',
Modified: zope.session/trunk/src/zope/session/interfaces.py
===================================================================
--- zope.session/trunk/src/zope/session/interfaces.py 2008-06-12 20:31:16 UTC (rev 87350)
+++ zope.session/trunk/src/zope/session/interfaces.py 2008-06-12 21:02:43 UTC (rev 87351)
@@ -125,6 +125,15 @@
"""
+ def getLastAccessTime():
+ "return approximate epoch time this ISessionData was last retrieved"
+
+ def setLastAccessTime():
+ "An API for ISessionDataContainer to set the last retrieved epoch time"
+
+ # consider deprecating this property, or at least making it readonly. The
+ # setter should be used instead of setting this property because of
+ # conflict resolution: see https://bugs.launchpad.net/zope3/+bug/239531
lastAccessTime = schema.Int(
title=_("Last Access Time"),
description=_(
Modified: zope.session/trunk/src/zope/session/session.py
===================================================================
--- zope.session/trunk/src/zope/session/session.py 2008-06-12 20:31:16 UTC (rev 87350)
+++ zope.session/trunk/src/zope/session/session.py 2008-06-12 21:02:43 UTC (rev 87351)
@@ -70,7 +70,7 @@
class PersistentSessionDataContainer(zope.location.Location,
- persistent.Persistent,
+ persistent.Persistent,
IterableUserDict):
"""A SessionDataContainer that stores data in the ZODB"""
@@ -96,7 +96,7 @@
To ensure stale data is removed, we can wind
back the clock using undocumented means...
- >>> sd.lastAccessTime = sd.lastAccessTime - 64
+ >>> sd.setLastAccessTime(sd.getLastAccessTime() - 64)
>>> sdc._v_last_sweep = sdc._v_last_sweep - 4
Now the data should be garbage collected
@@ -106,7 +106,7 @@
[...]
KeyError: 'clientid'
- Ensure lastAccessTime on the ISessionData is being updated
+ Ensure the lastAccessTime on the ISessionData is being updated
occasionally. The ISessionDataContainer maintains this whenever
the ISessionData is set or retrieved.
@@ -114,29 +114,30 @@
to the ISessionDataContainer
>>> sdc['client_id'] = sd = SessionData()
- >>> sd.lastAccessTime > 0
+ >>> sd.getLastAccessTime() > 0
True
- lastAccessTime is also updated whenever the ISessionData
+ The lastAccessTime is also updated whenever the ISessionData
is retrieved through the ISessionDataContainer, at most
once every 'resolution' seconds.
- >>> then = sd.lastAccessTime = sd.lastAccessTime - 4
- >>> now = sdc['client_id'].lastAccessTime
+ >>> then = sd.getLastAccessTime() - 4
+ >>> sd.setLastAccessTime(then)
+ >>> now = sdc['client_id'].getLastAccessTime()
>>> now > then
True
>>> time.sleep(1)
- >>> now == sdc['client_id'].lastAccessTime
+ >>> now == sdc['client_id'].getLastAccessTime()
True
- Ensure lastAccessTime is not modified and no garbage collection
+ Ensure the lastAccessTime is not modified and no garbage collection
occurs when timeout == 0. We test this by faking a stale
ISessionData object.
>>> sdc.timeout = 0
- >>> sd.lastAccessTime = sd.lastAccessTime - 5000
- >>> lastAccessTime = sd.lastAccessTime
- >>> sdc['client_id'].lastAccessTime == lastAccessTime
+ >>> sd.setLastAccessTime(sd.getLastAccessTime() - 5000)
+ >>> lastAccessTime = sd.getLastAccessTime()
+ >>> sdc['client_id'].getLastAccessTime() == lastAccessTime
True
Next, we test session expiration functionality beyond transactions.
@@ -163,7 +164,7 @@
>>> sdc = c.root()['sdc']
>>> sd = sdc['pkg_id']
- >>> sd.lastAccessTime = sd.lastAccessTime - 64
+ >>> sd.setLastAccessTime(sd.getLastAccessTime() - 64)
>>> sdc._v_last_sweep = sdc._v_last_sweep - 4
>>> transaction.commit()
@@ -211,10 +212,10 @@
self._v_last_sweep = now
rv = IterableUserDict.__getitem__(self, pkg_id)
- # Only update lastAccessTime once every few minutes, rather than
+ # Only update the lastAccessTime once every few minutes, rather than
# every hit, to avoid ZODB bloat and conflicts
- if rv.lastAccessTime + self.resolution < now:
- rv.lastAccessTime = int(now)
+ if rv.getLastAccessTime() + self.resolution < now:
+ rv.setLastAccessTime(int(now))
return rv
def __setitem__(self, pkg_id, session_data):
@@ -225,10 +226,10 @@
__setitem__ sets the ISessionData's lastAccessTime
- >>> sad.lastAccessTime
+ >>> sad.getLastAccessTime()
0
>>> sdc['1'] = sad
- >>> 0 < sad.lastAccessTime <= time.time()
+ >>> 0 < sad.getLastAccessTime() <= time.time()
True
We can retrieve the same object we put in
@@ -237,7 +238,7 @@
True
"""
- session_data.lastAccessTime = int(time.time())
+ session_data.setLastAccessTime(int(time.time()))
return IterableUserDict.__setitem__(self, pkg_id, session_data)
def sweep(self):
@@ -250,7 +251,8 @@
Wind back the clock on one of the ISessionData's
so it gets garbage collected
- >>> sdc['2'].lastAccessTime -= sdc.timeout * 2
+ >>> sdc['2'].setLastAccessTime(
+ ... sdc['2'].getLastAccessTime() - sdc.timeout * 2)
Sweep should leave '1' and remove '2'
@@ -267,7 +269,7 @@
# calculating the expiry time to ensure that we never remove
# data that has been accessed within timeout seconds.
expire_time = time.time() - self.timeout - self.resolution
- heap = [(v.lastAccessTime, k) for k,v in self.data.items()]
+ heap = [(v.getLastAccessTime(), k) for k,v in self.data.items()]
heapify(heap)
while heap:
lastAccessTime, key = heappop(heap)
@@ -345,11 +347,11 @@
If we use get we get None or default returned if the pkg_id
is not there.
-
+
>>> session = Session(request).get('not.there', 'default')
>>> session
'default'
-
+
This method is lazy and does not create the session data.
>>> session = Session(request).get('not.there')
>>> session is None
@@ -379,8 +381,8 @@
return sd[pkg_id]
except KeyError:
return default
-
+
def __getitem__(self, pkg_id):
"""See zope.session.interfaces.ISession
@@ -446,7 +448,7 @@
>>> session = SessionData()
>>> ISessionData.providedBy(session)
True
- >>> session.lastAccessTime
+ >>> session.getLastAccessTime()
0
Before the zope.minmax package this class used to have an attribute
@@ -466,14 +468,14 @@
>>> legacy_session = SessionData()
>>> del legacy_session._lastAccessTime
- >>> legacy_session.lastAccessTime
+ >>> legacy_session.getLastAccessTime()
0
Now, artificially add lastAccessTime to the instance's dictionary.
This should make it exactly like the legacy SessionData().
>>> legacy_session.__dict__['lastAccessTime'] = 42
- >>> legacy_session.lastAccessTime
+ >>> legacy_session.getLastAccessTime()
42
Finally, assign to lastAccessTime. Since the instance now looks like a
@@ -481,7 +483,7 @@
creation of a zope.minmax.Maximum() object which will take over the
handling of this value and its conflict resolution from now on.
- >>> legacy_session.lastAccessTime = 13
+ >>> legacy_session.setLastAccessTime(13)
>>> legacy_session._lastAccessTime.value
13
@@ -496,22 +498,25 @@
self.data = OOBTree()
self._lastAccessTime = zope.minmax.Maximum(0)
- def _getLastAccessTime(self):
+ # we include this for parallelism with setLastAccessTime
+ def getLastAccessTime(self):
# this conditional is for legacy sessions; this comment and
# the next two lines will be removed in a later release
if self._lastAccessTime is None:
return self.__dict__.get('lastAccessTime', 0)
return self._lastAccessTime.value
- def _setLastAccessTime(self, value):
+ # we need to set this value with setters in order to get optimal conflict
+ # resolution behavior
+ def setLastAccessTime(self, value):
# this conditional is for legacy sessions; this comment and
# the next two lines will be removed in a later release
if self._lastAccessTime is None:
self._lastAccessTime = zope.minmax.Maximum(0)
self._lastAccessTime.value = value
- lastAccessTime = property(fget=_getLastAccessTime,
- fset=_setLastAccessTime,
+ lastAccessTime = property(fget=getLastAccessTime,
+ fset=setLastAccessTime, # consider deprecating
doc='integer value of the last access time')
@@ -525,4 +530,3 @@
zope.interface.implements(ISessionPkgData)
def __init__(self):
self.data = OOBTree()
-
Modified: zope.session/trunk/src/zope/session/tests.py
===================================================================
--- zope.session/trunk/src/zope/session/tests.py 2008-06-12 20:31:16 UTC (rev 87350)
+++ zope.session/trunk/src/zope/session/tests.py 2008-06-12 21:02:43 UTC (rev 87351)
@@ -93,7 +93,63 @@
transaction.abort()
+def testConflicts():
+ """The SessionData objects have been plagued with unnecessary
+ ConflictErrors. The current implementation makes the most common source
+ of ConflictErrors in the past, setting the lastAccessTime, no longer a
+ problem in this regard.
+ To illustrate this, we will do a bit of an integration test. We'll begin
+ by getting a connection and putting a session data container in the root,
+ within transaction manager "A".
+
+ >>> from ZODB.DB import DB
+ >>> from ZODB.tests.util import ConflictResolvingMappingStorage
+ >>> from zope.session.session import (
+ ... PersistentSessionDataContainer, SessionData)
+ >>> db = DB(ConflictResolvingMappingStorage())
+ >>> import transaction
+ >>> tm_A = transaction.TransactionManager()
+ >>> conn_A = db.open(transaction_manager=tm_A)
+ >>> root_A = conn_A.root()
+ >>> sdc_A = root_A['sdc'] = PersistentSessionDataContainer()
+ >>> sdc_A.resolution = 3
+ >>> sd_A = sdc_A['clientid'] = SessionData()
+ >>> then = sd_A.getLastAccessTime() - 4
+ >>> sd_A.setLastAccessTime(then)
+ >>> tm_A.commit()
+
+ Now we have a session data container with a session data lastAccessTime
+ that is set to four seconds ago. Since we set the resolution to three
+ seconds, the next time the session is accessed, the lastAccessTime should
+ be updated.
+
+ We will access the session simultaneously in two transactions, which will
+ set the updated lastAccessTime on both objects, and then commit. Because
+ of the conflict resolution code in zope.minmax, both commits will succeed,
+ which is what we wanted to demonstrate.
+
+ >>> tm_B = transaction.TransactionManager()
+ >>> conn_B = db.open(transaction_manager=tm_B)
+ >>> root_B = conn_B.root()
+ >>> sdc_B = root_B['sdc']
+
+ >>> sd_B = sdc_B['clientid'] # has side effect of updating lastAccessTime
+ >>> sd_B.getLastAccessTime() > then
+ True
+
+ >>> sd_A is sdc_A['clientid'] # has side effect of updating lastAccessTime
+ True
+ >>> sd_A.getLastAccessTime() > then
+ True
+
+ >>> tm_A.commit()
+ >>> tm_B.commit()
+
+ Q.E.D.
+ """
+
+
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestBootstrap))
More information about the Checkins
mailing list