[Zope-Checkins] CVS: Zope/lib/python/Products/Transience - TransienceInterfaces.py:1.13 TransientObject.py:1.4

Chris McDonough chrism@zope.com
Sat, 23 Mar 2002 23:44:33 -0500


Update of /cvs-repository/Zope/lib/python/Products/Transience
In directory cvs.zope.org:/tmp/cvs-serv1005

Modified Files:
	TransienceInterfaces.py TransientObject.py 
Log Message:
Merging 2-5 branch transience changes to trunk.


=== Zope/lib/python/Products/Transience/TransienceInterfaces.py 1.12 => 1.13 ===
         """
         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():
         """
         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():


=== Zope/lib/python/Products/Transience/TransientObject.py 1.3 => 1.4 ===
 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)