[Zope-Checkins] CVS: Zope/lib/python/Products/Transience - TransienceInterfaces.py:1.11.34.1 TransientObject.py:1.3.34.1
Casey Duncan
casey@zope.com
Wed, 27 Mar 2002 15:51:50 -0500
Update of /cvs-repository/Zope/lib/python/Products/Transience
In directory cvs.zope.org:/tmp/cvs-serv22094/lib/python/Products/Transience
Modified Files:
Tag: casey-death_to_index_html-branch
TransienceInterfaces.py TransientObject.py
Log Message:
Updating branch to head for testing
=== Zope/lib/python/Products/Transience/TransienceInterfaces.py 1.11 => 1.11.34.1 ===
class Transient(Interface.Base):
- def invalidate(self):
+ def invalidate():
"""
Invalidate (expire) the transient object.
@@ -85,69 +85,85 @@
related to this object to be called as a side effect.
"""
- def isValid(self):
+ def isValid():
"""
Return true if transient object is still valid, false if not.
A transient object is valid if its invalidate method has not been
called.
"""
- def getLastAccessed(self):
+ def getLastAccessed():
"""
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(self):
+ def setLastAccessed():
"""
Cause the last accessed time to be set to now.
"""
- def getCreated(self):
+ 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():
"""
Return the time the transient object was created in integer
seconds-since-the-epoch form.
"""
- def getContainerKey(self):
+ def getContainerKey():
"""
Return the key under which the object was placed in its
container.
"""
class DictionaryLike(Interface.Base):
- def keys(self):
+ def keys():
"""
Return sequence of key elements.
"""
- def values(self):
+ def values():
"""
Return sequence of value elements.
"""
- def items(self):
+ def items():
"""
Return sequence of (key, value) elements.
"""
- def get(self, k, default='marker'):
+ def get(k, default='marker'):
"""
Return value associated with key k. If k does not exist and default
is not marker, return default, else raise KeyError.
"""
- def has_key(self, k):
+ def has_key(k):
"""
Return true if item referenced by key k exists.
"""
- def clear(self):
+ def clear():
"""
Remove all key/value pairs.
"""
- def update(self, d):
+ def update(d):
"""
Merge dictionary d into ourselves.
"""
@@ -155,36 +171,36 @@
# DictionaryLike does NOT support copy()
class ItemWithId(Interface.Base):
- def getId(self):
+ def getId():
"""
Returns a meaningful unique id for the object. Note that this id
need not the key under which the object is stored in its container.
"""
class TTWDictionary(DictionaryLike, ItemWithId):
- def set(self, k, v):
+ def set(k, v):
"""
Call __setitem__ with key k, value v.
"""
- def delete(self, k):
+ def delete(k):
"""
Call __delitem__ with key k.
"""
- def __guarded_setitem__(self, k, v):
+ def __guarded_setitem__(k, v):
"""
Call __setitem__ with key k, value v.
"""
class ImmutablyValuedMappingOfPickleableObjects(Interface.Base):
- def __setitem__(self, k, v):
+ def __setitem__(k, v):
"""
Sets key k to value v, if k is both hashable and pickleable and
v is pickleable, else raise TypeError.
"""
- def __getitem__(self, k):
+ def __getitem__(k):
"""
Returns the value associated with key k.
@@ -195,7 +211,7 @@
to the mapping via __setitem__.
"""
- def __delitem__(self, k):
+ def __delitem__(k):
"""
Remove the key/value pair related to key k.
"""
@@ -207,7 +223,7 @@
2. Is responsible for the creation of its subobjects.
3. Allows for the access of a subobject by key.
"""
- def get(self, k, default=None):
+ def get(k, default=None):
"""
Return value associated with key k via __getitem__. If value
associated with k does not exist, return default.
@@ -216,14 +232,14 @@
is passed in and returned.
"""
- def has_key(self, k):
+ def has_key(k):
"""
Return true if container has value associated with key k, else
return false.
"""
class StringKeyedHomogeneousItemContainer(HomogeneousItemContainer):
- def new(self, k):
+ def new(k):
"""
Creates a new subobject of the type supported by this container
with key "k" and returns it.
@@ -239,7 +255,7 @@
Returned object is acquisition-wrapped in self.
"""
- def new_or_existing(self, k):
+ def new_or_existing(k):
"""
If an object already exists in the container with key "k", it
is returned.
@@ -256,24 +272,24 @@
"""
class TransientItemContainer(Interface.Base):
- def setTimeoutMinutes(self, timeout_mins):
+ def setTimeoutMinutes(timeout_mins):
"""
Set the number of minutes of inactivity allowable for subobjects
before they expire.
"""
- def getTimeoutMinutes(self):
+ def getTimeoutMinutes():
"""
Return the number of minutes allowed for subobject inactivity
before expiration.
"""
- def getAddNotificationTarget(self):
+ def getAddNotificationTarget():
"""
Returns the currently registered 'add notification' value, or None.
"""
- def setAddNotificationTarget(self, f):
+ def setAddNotificationTarget(f):
"""
Cause the 'add notification' function to be 'f'.
@@ -290,13 +306,13 @@
print "id of 'item' arg was %s" % item.getId()
"""
- def getDelNotificationTarget(self):
+ def getDelNotificationTarget():
"""
Returns the currently registered 'delete notification' value, or
None.
"""
- def setDelNotificationTarget(self, f):
+ def setDelNotificationTarget(f):
"""
Cause the 'delete notification' function to be 'f'.
=== Zope/lib/python/Products/Transience/TransientObject.py 1.3 => 1.3.34.1 ===
from AccessControl import ClassSecurityInfo
import Globals
-from zLOG import LOG, BLATHER
+from zLOG import LOG, BLATHER, INFO
+import sys
_notfound = []
-WRITEGRANULARITY=30 # Timing granularity for write clustering, in seconds
+WRITEGRANULARITY=30 # Timing granularity for access write clustering, seconds
class TransientObject(Persistent, Implicit):
""" Dictionary-like object that supports additional methods
@@ -45,12 +46,21 @@
security = ClassSecurityInfo()
security.setDefaultAccess('allow')
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):
self.token = containerkey
self.id = self._generateUniqueId()
self._container = {}
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
@@ -72,13 +82,19 @@
def getLastAccessed(self):
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
# setting if so, to cut down on heavy writes
t = time.time()
- if (self._last_accessed + WG) < t:
+ if (self._last_accessed + WRITEGRANULARITY) < t:
self._last_accessed = t
+ def getLastModified(self):
+ return self._last_modified
+
+ def setLastModified(self):
+ self._last_modified = time.time()
+
def getCreated(self):
return self._created
@@ -109,7 +125,7 @@
def clear(self):
self._container.clear()
- self._p_changed = 1
+ self.setLastModified()
def update(self, d):
for k in d.keys():
@@ -129,25 +145,22 @@
k._p_jar = self._p_jar
k._p_changed = 1
self._container[k] = v
- self._p_changed = 1
+ self.setLastModified()
def __getitem__(self, k):
return self._container[k]
def __delitem__(self, k):
del self._container[k]
+ self.setLastModified()
# -----------------------------------------------------------------
# TTWDictionary
#
set = __setitem__
-
- def delete(self, k):
- del self._container[k]
- self._p_changed = 1
-
__guarded_setitem__ = __setitem__
+ delete = __delitem__
# -----------------------------------------------------------------
# Other non interface code
@@ -159,27 +172,58 @@
return 1
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')
- for attr in attrs:
- old = saved.get(attr)
- st1 = state1.get(attr)
- st2 = state2.get(attr)
- if not (old == st1 == st2):
- return None
- # return the object with the most recent last_accessed value.
- if state1['_last_accessed'] > state2['_last_accessed']:
- return state1
- else:
- return state2
+ try:
+ states = [saved, state1, state2]
+ # We can clearly resolve the conflict if one state is invalid,
+ # because it's a terminal state.
+ for state in states:
+ if state.has_key('_invalid'):
+ LOG('Transience', BLATHER, 'a state was invalid')
+ return state
+ # The only other times we can clearly resolve the conflict is if
+ # 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
def _generateUniqueId(self):
@@ -191,5 +235,21 @@
return "id: %s, token: %s, contents: %s" % (
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)