[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)