Commit 7ad4e9ae authored by Chris McDonough's avatar Chris McDonough

Merging 2-5 branch transience changes to trunk.

parent 67043f55
...@@ -95,7 +95,9 @@ class Transient(Interface.Base): ...@@ -95,7 +95,9 @@ class Transient(Interface.Base):
def getLastAccessed(): def getLastAccessed():
""" """
Return the time the transient object was last accessed in Return the time the transient object was last accessed in
integer seconds-since-the-epoch form. integer seconds-since-the-epoch form. Last accessed time
is defined as the last time the transient object's container
"asked about" this transient object.
""" """
def setLastAccessed(): def setLastAccessed():
...@@ -103,6 +105,20 @@ class Transient(Interface.Base): ...@@ -103,6 +105,20 @@ class Transient(Interface.Base):
Cause the last accessed time to be set to now. Cause the last accessed time to be set to now.
""" """
def getLastModified():
"""
Return the time the transient object was last modified in
integer seconds-since-the-epoch form. Modification generally implies
a call to one of the transient object's __setitem__ or __delitem__
methods, directly or indirectly as a result of a call to
update, clear, or other mutating data access methods.
"""
def setLastModified():
"""
Cause the last modified time to be set to now.
"""
def getCreated(): def getCreated():
""" """
Return the time the transient object was created in integer Return the time the transient object was created in integer
......
...@@ -13,10 +13,10 @@ ...@@ -13,10 +13,10 @@
""" """
Simple ZODB-based transient object implementation. Simple ZODB-based transient object implementation.
$Id: TransientObject.py,v 1.3 2001/11/28 15:51:09 matt Exp $ $Id: TransientObject.py,v 1.4 2002/03/24 04:44:32 chrism Exp $
""" """
__version__='$Revision: 1.3 $'[11:-2] __version__='$Revision: 1.4 $'[11:-2]
from Persistence import Persistent from Persistence import Persistent
from Acquisition import Implicit from Acquisition import Implicit
...@@ -25,11 +25,12 @@ from TransienceInterfaces import ItemWithId, Transient, DictionaryLike,\ ...@@ -25,11 +25,12 @@ from TransienceInterfaces import ItemWithId, Transient, DictionaryLike,\
TTWDictionary, ImmutablyValuedMappingOfPickleableObjects TTWDictionary, ImmutablyValuedMappingOfPickleableObjects
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
import Globals import Globals
from zLOG import LOG, BLATHER from zLOG import LOG, BLATHER, INFO
import sys
_notfound = [] _notfound = []
WRITEGRANULARITY=30 # Timing granularity for write clustering, in seconds WRITEGRANULARITY=30 # Timing granularity for access write clustering, seconds
class TransientObject(Persistent, Implicit): class TransientObject(Persistent, Implicit):
""" Dictionary-like object that supports additional methods """ Dictionary-like object that supports additional methods
...@@ -45,12 +46,21 @@ class TransientObject(Persistent, Implicit): ...@@ -45,12 +46,21 @@ class TransientObject(Persistent, Implicit):
security = ClassSecurityInfo() security = ClassSecurityInfo()
security.setDefaultAccess('allow') security.setDefaultAccess('allow')
security.declareObjectPublic() security.declareObjectPublic()
_last_modified = None
# _last modified indicates the last time that __setitem__, __delitem__,
# update or clear was called on us.
def __init__(self, containerkey): def __init__(self, containerkey):
self.token = containerkey self.token = containerkey
self.id = self._generateUniqueId() self.id = self._generateUniqueId()
self._container = {} self._container = {}
self._created = self._last_accessed = time.time() self._created = self._last_accessed = time.time()
# _last_accessed indicates the last time that *our container
# was asked about us* (NOT the last time __getitem__ or get
# or any of our other invariant data access methods are called).
# Our container manages our last accessed time, we don't much
# concern ourselves with it other than exposing an interface
# to set it on ourselves.
# ----------------------------------------------------------------- # -----------------------------------------------------------------
# ItemWithId # ItemWithId
...@@ -72,13 +82,19 @@ class TransientObject(Persistent, Implicit): ...@@ -72,13 +82,19 @@ class TransientObject(Persistent, Implicit):
def getLastAccessed(self): def getLastAccessed(self):
return self._last_accessed return self._last_accessed
def setLastAccessed(self, WG=WRITEGRANULARITY): def setLastAccessed(self):
# check to see if the last_accessed time is too recent, and avoid # check to see if the last_accessed time is too recent, and avoid
# setting if so, to cut down on heavy writes # setting if so, to cut down on heavy writes
t = time.time() t = time.time()
if (self._last_accessed + WG) < t: if (self._last_accessed + WRITEGRANULARITY) < t:
self._last_accessed = t self._last_accessed = t
def getLastModified(self):
return self._last_modified
def setLastModified(self):
self._last_modified = time.time()
def getCreated(self): def getCreated(self):
return self._created return self._created
...@@ -109,7 +125,7 @@ class TransientObject(Persistent, Implicit): ...@@ -109,7 +125,7 @@ class TransientObject(Persistent, Implicit):
def clear(self): def clear(self):
self._container.clear() self._container.clear()
self._p_changed = 1 self.setLastModified()
def update(self, d): def update(self, d):
for k in d.keys(): for k in d.keys():
...@@ -129,25 +145,22 @@ class TransientObject(Persistent, Implicit): ...@@ -129,25 +145,22 @@ class TransientObject(Persistent, Implicit):
k._p_jar = self._p_jar k._p_jar = self._p_jar
k._p_changed = 1 k._p_changed = 1
self._container[k] = v self._container[k] = v
self._p_changed = 1 self.setLastModified()
def __getitem__(self, k): def __getitem__(self, k):
return self._container[k] return self._container[k]
def __delitem__(self, k): def __delitem__(self, k):
del self._container[k] del self._container[k]
self.setLastModified()
# ----------------------------------------------------------------- # -----------------------------------------------------------------
# TTWDictionary # TTWDictionary
# #
set = __setitem__ set = __setitem__
def delete(self, k):
del self._container[k]
self._p_changed = 1
__guarded_setitem__ = __setitem__ __guarded_setitem__ = __setitem__
delete = __delitem__
# ----------------------------------------------------------------- # -----------------------------------------------------------------
# Other non interface code # Other non interface code
...@@ -159,27 +172,58 @@ class TransientObject(Persistent, Implicit): ...@@ -159,27 +172,58 @@ class TransientObject(Persistent, Implicit):
return 1 return 1
def _p_resolveConflict(self, saved, state1, state2): def _p_resolveConflict(self, saved, state1, state2):
attrs = ['token', 'id', '_created', '_invalid']
# note that last_accessed and _container are the only attrs
# missing from this list. The only time we can clearly resolve
# the conflict is if everything but the last_accessed time and
# the contents are the same, so we make sure nothing else has
# changed. We're being slightly sneaky here by accepting
# possibly conflicting data in _container, but it's acceptable
# in this context.
LOG('Transience', BLATHER, 'Resolving conflict in TransientObject') LOG('Transience', BLATHER, 'Resolving conflict in TransientObject')
for attr in attrs: try:
old = saved.get(attr) states = [saved, state1, state2]
st1 = state1.get(attr)
st2 = state2.get(attr) # We can clearly resolve the conflict if one state is invalid,
if not (old == st1 == st2): # because it's a terminal state.
return None for state in states:
# return the object with the most recent last_accessed value. if state.has_key('_invalid'):
if state1['_last_accessed'] > state2['_last_accessed']: LOG('Transience', BLATHER, 'a state was invalid')
return state1 return state
else: # The only other times we can clearly resolve the conflict is if
return state2 # the token, the id, or the creation time don't differ between
# the three states, so we check that here. If any differ, we punt
# by returning None. Returning None indicates that we can't
# resolve the conflict.
attrs = ['token', 'id', '_created']
for attr in attrs:
if not (saved.get(attr)==state1.get(attr)==state2.get(attr)):
LOG('Transience', BLATHER, 'cant resolve conflict')
return None
# Now we need to do real work.
#
# Data in our _container dictionaries might conflict. To make
# things simple, we intentionally create a race condition where the
# state which was last modified "wins". It would be preferable to
# somehow merge our _containers together, but as there's no
# generally acceptable way to union their states, there's not much
# we can do about it if we want to be able to resolve this kind of
# conflict.
# We return the state which was most recently modified, if
# possible.
states.sort(lastmodified_sort)
if states[0].get('_last_modified'):
LOG('Transience', BLATHER, 'returning last mod state')
return states[0]
# If we can't determine which object to return on the basis
# of last modification time (no state has been modified), we return
# the object that was most recently accessed (last pulled out of
# our parent). This will return an essentially arbitrary state if
# all last_accessed values are equal.
states.sort(lastaccessed_sort)
LOG('Transience', BLATHER, 'returning last_accessed state')
return states[0]
except:
LOG('Transience', INFO,
'Conflict resolution error in TransientObject', '',
sys.exc_info()
)
getName = getId # this is for SQLSession compatibility getName = getId # this is for SQLSession compatibility
def _generateUniqueId(self): def _generateUniqueId(self):
...@@ -192,4 +236,20 @@ class TransientObject(Persistent, Implicit): ...@@ -192,4 +236,20 @@ class TransientObject(Persistent, Implicit):
self.id, self.token, `self.items()` self.id, self.token, `self.items()`
) )
def lastmodified_sort(d1, d2):
""" sort dictionaries in descending order based on last mod time """
m1 = d1.get('_last_modified', 0)
m2 = d2.get('_last_modified', 0)
if m1 == m2: return 0
if m1 > m2: return -1 # d1 is "less than" d2
return 1
def lastaccessed_sort(d1, d2):
""" sort dictionaries in descending order based on last access time """
m1 = d1.get('_last_accessed', 0)
m2 = d2.get('_last_accessed', 0)
if m1 == m2: return 0
if m1 > m2: return -1 # d1 is "less than" d2
return 1
Globals.InitializeClass(TransientObject) Globals.InitializeClass(TransientObject)
...@@ -59,6 +59,21 @@ class TestTransientObject(TestCase): ...@@ -59,6 +59,21 @@ class TestTransientObject(TestCase):
ft = fauxtime.time() ft = fauxtime.time()
assert t.getCreated() <= ft assert t.getCreated() <= ft
def test_getLastModifiedUnset(self):
t = self.t.new('xyzzy')
assert t.getLastModified() == None
def test_getLastModifiedSet(self):
t = self.t.new('xyzzy')
t['a'] = 1
assert t.getLastModified() is not None
def testSetLastModified(self):
t = self.t.new('xyzzy')
ft = fauxtime.time()
t.setLastModified()
assert t.getLastModified() is not None
def test_setLastAccessed(self): def test_setLastAccessed(self):
t = self.t.new('xyzzy') t = self.t.new('xyzzy')
ft = fauxtime.time() ft = fauxtime.time()
......
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