[Zope-CVS] CVS: Products/Ape/apelib/zodb3 - connection.py:1.3

Shane Hathaway shane@zope.com
Sat, 29 Mar 2003 14:45:27 -0500


Update of /cvs-repository/Products/Ape/apelib/zodb3
In directory cvs.zope.org:/tmp/cvs-serv13976/apelib/zodb3

Modified Files:
	connection.py 
Log Message:
Stopped using the _p_serial attribute of persistent objects.  ZODB
assumes that _p_serial is a transaction time stamp and uses it to
compute _p_mtime, but that assumption is not valid for Ape.  Until
now, Ape has monkey-patched PersistentExtra to make it so the Zope
application doesn't depend so much on _p_mtime.  But that patch wasn't
enough, since other parts of Zope depend on _p_mtime.

So now Ape stores the object hashes in a dictionary external to the
object and periodically prunes hashes no longer in use.  Once again
Ape got around the need to patch the C code, but only by a narrow
margin.  Hopefully, ZODB 4 already provides a better answer for this
dilemma.


=== Products/Ape/apelib/zodb3/connection.py 1.2 => 1.3 ===
--- Products/Ape/apelib/zodb3/connection.py:1.2	Sat Mar 15 20:47:41 2003
+++ Products/Ape/apelib/zodb3/connection.py	Sat Mar 29 14:44:56 2003
@@ -28,6 +28,7 @@
      import ConflictError, ReadConflictError, InvalidObjectReference, \
      StorageError
 from ZODB.Connection import Connection
+from ZODB.ConflictResolution import ResolvedSerial
 from zLOG import LOG, ERROR
 
 from consts import HASH0, DEBUG
@@ -98,7 +99,6 @@
         object._p_oid=oid
         object._p_jar=self
         object._p_changed=None
-        object._p_serial=serial
 
         self._cache[oid] = object
         if oid=='\0\0\0\0\0\0\0\0':
@@ -198,8 +198,8 @@
             del stack[-1]
             oid=object._p_oid
             assert oid != 'unmanaged', repr(object)
-            serial=getattr(object, '_p_serial', '\0\0\0\0\0\0\0\0')
-            if serial == '\0\0\0\0\0\0\0\0':
+            serial = self.getSerial(object)
+            if serial == HASH0:
                 # new object
                 self._creating.append(oid)
             else:
@@ -249,8 +249,7 @@
             ext_refs = event.getExternalRefs()
             if ext_refs:
                 for (ext_keychain, ext_ref) in ext_refs:
-                    if (not ext_ref._p_serial
-                        or ext_ref._p_serial == HASH0):
+                    if self.getSerial(ext_ref) == HASH0:
                         ext_oid = oid_encoder.encode(ext_keychain)
                         if ext_ref._p_jar:
                             if ext_ref._p_jar != self:
@@ -363,7 +362,7 @@
             if unmanaged:
                 self.handleUnmanaged(object, unmanaged)
 
-            object._p_serial=serial
+            self.setSerial(object, serial)
 
             if invalid:
                 if object._p_independent():
@@ -447,10 +446,10 @@
         for oid, ob in self._cache.items():
             if ob._p_changed is not None:
                 p, serial = self._storage.load(oid, self._version)
-                if serial != ob._p_serial:
+                if serial != self.getSerial(ob):
                     keychain = self._db._oid_encoder.decode(oid)
                     raise StorageError(
-                        "Inconsistent hash for keychain %s" % repr(keychain))
+                        "Inconsistent serial for keychain %s" % repr(keychain))
     
 
     def exportFile(self, oid, file=None):
@@ -458,6 +457,94 @@
 
     def importFile(self, file, clue='', customImporters=None):
         raise NotImplementedError, 'ZEXP Import not implemented'
+
+
+    # A note on serials: Serials need to be stored independently of
+    # objects because the current Persistent base class uses _p_serial
+    # to derive _p_mtime.  Applications like Zope use _p_mtime, but
+    # the _p_serial for Ape isn't always a date, so Ape can't use
+    # _p_serial to store serials.  Instead, ApeConnection puts them in
+    # a _serials dictionary.
+
+    _serials = None
+    SERIAL_CLEANUP_THRESHOLD = 1000
+
+    def getSerial(self, ob):
+        oid = ob._p_oid
+        if oid is None or self._cache.get(oid, None) is not ob:
+            return HASH0
+        serials = self._serials
+        if serials is None:
+            return HASH0
+        return serials.get(oid, HASH0)
+
+    def setSerial(self, ob, s):
+        oid = ob._p_oid
+        assert oid is not None
+        if s is None:
+            s = HASH0
+        serials = self._serials
+        if serials is None:
+            serials = {}
+            self._serials = serials
+        if not serials.has_key(oid):
+            # When the number of recorded serials exceeds the number of
+            # cache entries by SERIAL_CLEANUP_THRESHOLD, prune the serials
+            # dictionary.
+            if (len(serials) >= len(self._cache) +
+                self.SERIAL_CLEANUP_THRESHOLD):
+                # clean up
+                cache_get = self._cache.get
+                for oid in serials.keys():
+                    ob = cache_get(oid, None)
+                    if ob is None or ob._p_changed is None:
+                        del serials[oid]
+        serials[oid] = s
+
+    def _handle_serial(self, store_return, oid=None, change=1):
+        """Handle the returns from store() and tpc_vote() calls."""
+
+        # These calls can return different types depending on whether
+        # ZEO is used.  ZEO uses asynchronous returns that may be
+        # returned in batches by the ClientStorage.  ZEO1 can also
+        # return an exception object and expect that the Connection
+        # will raise the exception.
+
+        # When commit_sub() exceutes a store, there is no need to
+        # update the _p_changed flag, because the subtransaction
+        # tpc_vote() calls already did this.  The change=1 argument
+        # exists to allow commit_sub() to avoid setting the flag
+        # again.
+        if not store_return:
+            return
+        if isinstance(store_return, StringType):
+            assert oid is not None
+            serial = store_return
+            obj = self._cache.get(oid, None)
+            if obj is None:
+                return
+            if serial == ResolvedSerial:
+                obj._p_changed = None
+            else:
+                if change:
+                    obj._p_changed = 0
+                #obj._p_serial = serial
+                self.setSerial(obj, serial)
+        else:
+            for oid, serial in store_return:
+                if not isinstance(serial, StringType):
+                    raise serial
+                obj = self._cache.get(oid, None)
+                if obj is None:
+                    continue
+                if serial == ResolvedSerial:
+                    obj._p_changed = None
+                else:
+                    if change:
+                        obj._p_changed = 0
+                    #obj._p_serial = serial
+                    self.setSerial(obj, serial)
+
 
 
 class UnmanagedJar: