[Zope-Checkins] CVS: Zope/lib/python/ZODB - ActivityMonitor.py:1.3.6.2 BaseStorage.py:1.21.2.2 ConflictResolution.py:1.13.6.1 Connection.py:1.72.6.1 DB.py:1.43.6.1 TimeStamp.c:1.15.60.1 Transaction.py:1.37.6.1 __init__.py:1.14.2.1 cPersistence.c:1.62.10.2 coptimizations.c:1.17.60.1 fsdump.py:1.3.70.1

Jeremy Hylton jeremy@zope.com
Tue, 12 Nov 2002 16:14:29 -0500


Update of /cvs-repository/Zope/lib/python/ZODB
In directory cvs.zope.org:/tmp/cvs-serv27252/lib/python/ZODB

Modified Files:
      Tag: Zope-2_6-branch
	ActivityMonitor.py BaseStorage.py ConflictResolution.py 
	Connection.py DB.py TimeStamp.c Transaction.py __init__.py 
	cPersistence.c coptimizations.c fsdump.py 
Log Message:
Sync Zope 2.6 and ZODB 3.1 release branches.

ZODB deadlock prevention code.
Bug in ConflictResolution bad_classes handling.
Don't let exceptions propagate out of ConflictResolution.
Add data_txn atribute to records returned by storage iterators.
Other sundry changes.



=== Zope/lib/python/ZODB/ActivityMonitor.py 1.3.6.1 => 1.3.6.2 ===


=== Zope/lib/python/ZODB/BaseStorage.py 1.21.2.1 => 1.21.2.2 ===
--- Zope/lib/python/ZODB/BaseStorage.py:1.21.2.1	Tue Nov  5 16:58:48 2002
+++ Zope/lib/python/ZODB/BaseStorage.py	Tue Nov 12 16:13:58 2002
@@ -63,6 +63,15 @@
     def close(self):
         pass
 
+    def sortKey(self):
+        """Return a string that can be used to sort storage instances.
+
+        The key must uniquely identify a storage and must be the same
+        across multiple instantiations of the same storage.
+        """
+        # name may not be sufficient, e.g. ZEO has a user-definable name.
+        return self.__name__
+
     def getName(self):
         return self.__name__
 


=== Zope/lib/python/ZODB/ConflictResolution.py 1.13 => 1.13.6.1 ===
--- Zope/lib/python/ZODB/ConflictResolution.py:1.13	Thu Aug 15 11:48:55 2002
+++ Zope/lib/python/ZODB/ConflictResolution.py	Tue Nov 12 16:13:58 2002
@@ -11,6 +11,7 @@
 # FOR A PARTICULAR PURPOSE
 #
 ##############################################################################
+import sys
 from cStringIO import StringIO
 from cPickle import Unpickler, Pickler
 
@@ -77,7 +78,7 @@
     except (ImportError, AttributeError):
         zLOG.LOG("Conflict Resolution", zLOG.BLATHER,
                  "Unable to load class", error=sys.exc_info())
-        bad_class[class_tuple] = 1
+        bad_classes[class_tuple] = 1
         return None
     return klass
 
@@ -114,6 +115,15 @@
         pickler.dump(resolved)
         return file.getvalue(1)
     except ConflictError:
+        return 0
+    except:
+        # If anything else went wrong, catch it here and avoid passing an
+        # arbitrary exception back to the client.  The error here will mask
+        # the original ConflictError.  A client can recover from a
+        # ConflictError, but not necessarily from other errors.  But log
+        # the error so that any problems can be fixed.
+        zLOG.LOG("Conflict Resolution", zLOG.ERROR,
+                 "Unexpected error", error=sys.exc_info())
         return 0
 
 class ConflictResolvingStorage:


=== Zope/lib/python/ZODB/Connection.py 1.72 => 1.72.6.1 ===
--- Zope/lib/python/ZODB/Connection.py:1.72	Wed Aug 14 18:07:09 2002
+++ Zope/lib/python/ZODB/Connection.py	Tue Nov 12 16:13:58 2002
@@ -191,6 +191,14 @@
             return obj
         return self[oid]
 
+    def sortKey(self):
+        # XXX will raise an exception if the DB hasn't been set
+        storage_key = self._sortKey()
+        # If two connections use the same storage, give them a
+        # consistent order using id().  This is unique for the
+        # lifetime of a connection, which is good enough.
+        return "%s:%s" % (storage_key, id(self))
+
     def _setDB(self, odb):
         """Begin a new transaction.
 
@@ -198,6 +206,7 @@
         """
         self._db=odb
         self._storage=s=odb._storage
+        self._sortKey = odb._storage.sortKey
         self.new_oid=s.new_oid
         if self._code_timestamp != global_code_timestamp:
             # New code is in place.  Start a new cache.
@@ -275,21 +284,18 @@
                 method_name, args, kw = self.__onCommitActions.pop(0)
                 apply(getattr(self, method_name), (transaction,) + args, kw)
             return
-        oid=object._p_oid
-        invalid=self._invalid
+        
+        oid = object._p_oid
+        invalid = self._invalid
         if oid is None or object._p_jar is not self:
             # new object
             oid = self.new_oid()
-            object._p_jar=self
-            object._p_oid=oid
+            object._p_jar = self
+            object._p_oid = oid
             self._creating.append(oid)
 
         elif object._p_changed:
-            if (
-                (invalid(oid) and not hasattr(object, '_p_resolveConflict'))
-                or
-                invalid(None)
-                ):
+            if invalid(oid) and not hasattr(object, '_p_resolveConflict'):
                 raise ConflictError(object=object)
             self._invalidating.append(oid)
 
@@ -297,7 +303,7 @@
             # Nothing to do
             return
 
-        stack=[object]
+        stack = [object]
 
         # Create a special persistent_id that passes T and the subobject
         # stack along:
@@ -330,7 +336,7 @@
         file=StringIO()
         seek=file.seek
         pickler=Pickler(file,1)
-        pickler.persistent_id=new_persistent_id(self, stack.append)
+        pickler.persistent_id=new_persistent_id(self, stack)
         dbstore=self._storage.store
         file=file.getvalue
         cache=self._cache
@@ -351,12 +357,7 @@
                 self._creating.append(oid)
             else:
                 #XXX We should never get here
-                if (
-                    (invalid(oid) and
-                     not hasattr(object, '_p_resolveConflict'))
-                    or
-                    invalid(None)
-                    ):
+                if invalid(oid) and not hasattr(object, '_p_resolveConflict'):
                     raise ConflictError(object=object)
                 self._invalidating.append(oid)
 
@@ -517,8 +518,7 @@
             # storage to make sure that we don't miss an invaildation
             # notifications between the time we check and the time we
             # read.
-            invalid = self._invalid
-            if invalid(oid) or invalid(None):
+            if self._invalid(oid):
                 if not hasattr(object.__class__, '_p_independent'):
                     get_transaction().register(self)
                     raise ReadConflictError(object=object)
@@ -602,24 +602,20 @@
         if self.__onCommitActions is not None:
             del self.__onCommitActions
         self._storage.tpc_abort(transaction)
-        cache=self._cache
-        cache.invalidate(self._invalidated)
-        cache.invalidate(self._invalidating)
+        self._cache.invalidate(self._invalidated)
+        self._cache.invalidate(self._invalidating)
         self._invalidate_creating()
 
     def tpc_begin(self, transaction, sub=None):
-        if self._invalid(None): # Some nitwit invalidated everything!
-            raise ConflictError("transaction already invalidated")
-        self._invalidating=[]
-        self._creating=[]
+        self._invalidating = []
+        self._creating = []
 
         if sub:
             # Sub-transaction!
-            _tmp=self._tmp
-            if _tmp is None:
-                _tmp=TmpStore.TmpStore(self._version)
-                self._tmp=self._storage
-                self._storage=_tmp
+            if self._tmp is None:
+                _tmp = TmpStore.TmpStore(self._version)
+                self._tmp = self._storage
+                self._storage = _tmp
                 _tmp.registerDB(self._db, 0)
 
         self._storage.tpc_begin(transaction)
@@ -628,7 +624,7 @@
         if self.__onCommitActions is not None:
             del self.__onCommitActions
         try:
-            vote=self._storage.tpc_vote
+            vote = self._storage.tpc_vote
         except AttributeError:
             return
         s = vote(transaction)


=== Zope/lib/python/ZODB/DB.py 1.43 => 1.43.6.1 ===
--- Zope/lib/python/ZODB/DB.py:1.43	Wed Aug 14 18:07:09 2002
+++ Zope/lib/python/ZODB/DB.py	Tue Nov 12 16:13:58 2002
@@ -577,7 +577,11 @@
         self.tpc_begin=s.tpc_begin
         self.tpc_vote=s.tpc_vote
         self.tpc_finish=s.tpc_finish
+        self._sortKey=s.sortKey
         get_transaction().register(self)
+
+    def sortKey(self):
+        return "%s:%s" % (self._sortKey(), id(self))
 
     def abort(self, reallyme, t): pass
 


=== Zope/lib/python/ZODB/TimeStamp.c 1.15 => 1.15.60.1 ===
--- Zope/lib/python/ZODB/TimeStamp.c:1.15	Fri Mar  8 13:36:13 2002
+++ Zope/lib/python/ZODB/TimeStamp.c	Tue Nov 12 16:13:58 2002
@@ -344,7 +344,10 @@
 static int
 TimeStamp_compare(TimeStamp *v, TimeStamp *w)
 {
-  return memcmp(v->data, w->data, 8);
+  int cmp = memcmp(v->data, w->data, 8);
+  if (cmp < 0) return -1;
+  if (cmp > 0) return 1;
+  return 0;
 }
 
 static long


=== Zope/lib/python/ZODB/Transaction.py 1.37 => 1.37.6.1 ===
--- Zope/lib/python/ZODB/Transaction.py:1.37	Wed Aug 14 18:07:09 2002
+++ Zope/lib/python/ZODB/Transaction.py	Tue Nov 12 16:13:58 2002
@@ -19,12 +19,34 @@
 import time, sys, struct, POSException
 from struct import pack
 from string import split, strip, join
-from zLOG import LOG, ERROR, PANIC
+from zLOG import LOG, ERROR, PANIC, INFO, BLATHER, WARNING
 from POSException import ConflictError
+from ZODB import utils
 
 # Flag indicating whether certain errors have occurred.
 hosed=0
 
+# There is an order imposed on all jars, based on the storages they
+# serve, that must be consistent across all applications using the
+# storages.  The order is defined by the sortKey() method of the jar.
+
+def jar_cmp(j1, j2):
+    # Call sortKey() every time, because a ZEO client could reconnect
+    # to a different server at any time.
+    try:
+        k1 = j1.sortKey()
+    except:
+        LOG("TM", WARNING, "jar missing sortKey() method: %s" % j1)
+        k1 = id(j1)
+
+    try:
+        k2 = j2.sortKey()
+    except:
+        LOG("TM", WARNING, "jar missing sortKey() method: %s" % j2)
+        k2 = id(j2)
+        
+    return cmp(k1, k2)
+
 class Transaction:
     'Simple transaction objects for single-threaded applications.'
     user=''
@@ -53,6 +75,9 @@
             for c in self._connections.values(): c.close()
             del self._connections
 
+    def log(self, msg, level=INFO, error=None):
+        LOG("TM:%s" % self._id, level, msg, error=error)
+
     def sub(self):
         # Create a manually managed subtransaction for internal use
         r=self.__class__()
@@ -84,11 +109,8 @@
                 """)
 
         t = None
-        subj = self._sub
-        subjars = ()
 
         if not subtransaction:
-
             # Must add in any non-subtransaction supporting objects that
             # may have been stowed away from previous subtransaction
             # commits.
@@ -96,11 +118,14 @@
                 self._objects.extend(self._non_st_objects)
                 self._non_st_objects = None
 
-            if subj is not None:
+            if self._sub is not None:
                 # Abort of top-level transaction after commiting
                 # subtransactions.
-                subjars = subj.values()
+                subjars = self._sub.values()
+                subjars.sort(jar_cmp)
                 self._sub = None
+            else:
+                subjars = []
 
         try:
             # Abort the objects
@@ -110,13 +135,20 @@
                     if j is not None:
                         j.abort(o, self)
                 except:
+                    # Record the first exception that occurred
                     if t is None:
                         t, v, tb = sys.exc_info()
+                    else:
+                        self.log("Failed to abort object %016x" %
+                                 utils.U64(o._p_oid), error=sys.exc_info())
 
-            # Ugh, we need to abort work done in sub-transactions.
-            while subjars:
-                j = subjars.pop()
-                j.abort_sub(self) # This should never fail
+            # tpc_begin() was never called, so tpc_abort() should not be
+            # called.
+
+            if not subtransaction:
+                # abort_sub() must be called to clear subtransaction state
+                for jar in subjars:
+                    jar.abort_sub(self) # This should never fail
 
             if t is not None:
                 raise t, v, tb
@@ -136,7 +168,8 @@
 
         This aborts any transaction in progres.
         '''
-        if self._objects: self.abort(subtransaction, 0)
+        if self._objects:
+            self.abort(subtransaction, 0)
         if info:
             info=split(info,'\t')
             self.user=strip(info[0])
@@ -146,30 +179,32 @@
         'Finalize the transaction'
 
         objects = self._objects
-        jars = {}
-        jarsv = None
-        subj = self._sub
-        subjars = ()
 
+        subjars = []
         if subtransaction:
-            if subj is None:
-                self._sub = subj = {}
+            if self._sub is None:
+                # Must store state across multiple subtransactions
+                # so that the final commit can commit all subjars.
+                self._sub = {}
         else:
-            if subj is not None:
+            if self._sub is not None:
+                # This commit is for a top-level transaction that
+                # has previously committed subtransactions.  Do
+                # one last subtransaction commit to clear out the
+                # current objects, then commit all the subjars.
                 if objects:
-                    # Do an implicit sub-transaction commit:
                     self.commit(1)
-                    # XXX What does this do?
                     objects = []
-                subjars = subj.values()
+                subjars = self._sub.values()
+                subjars.sort(jar_cmp)
                 self._sub = None
 
-        # If not a subtransaction, then we need to add any non-
-        # subtransaction-supporting objects that may have been
-        # stowed away during subtransaction commits to _objects.
-        if (subtransaction is None) and (self._non_st_objects is not None):
-            objects.extend(self._non_st_objects)
-            self._non_st_objects = None
+                # If there were any non-subtransaction-aware jars
+                # involved in earlier subtransaction commits, we need
+                # to add them to the list of jars to commit.
+                if self._non_st_objects is not None:
+                    objects.extend(self._non_st_objects)
+                    self._non_st_objects = None
 
         if (objects or subjars) and hosed:
             # Something really bad happened and we don't
@@ -188,89 +223,140 @@
         #   either call tpc_abort or tpc_finish. It is OK to call
         #   these multiple times, as the storage is required to ignore
         #   these calls if tpc_begin has not been called.
+        #
+        # - That we call tpc_begin() in a globally consistent order,
+        #   so that concurrent transactions involving multiple storages
+        #   do not deadlock.
         try:
             ncommitted = 0
+            jars = self._get_jars(objects, subtransaction)
             try:
-                ncommitted += self._commit_objects(objects, jars,
-                                                   subtransaction, subj)
-
-                self._commit_subtrans(jars, subjars)
-
-                jarsv = jars.values()
-                for jar in jarsv:
-                    if not subtransaction:
+                # If not subtransaction, then jars will be modified.
+                self._commit_begin(jars, subjars, subtransaction)
+                ncommitted += self._commit_objects(objects)
+                if not subtransaction:
+                    # Unless this is a really old jar that doesn't
+                    # implement tpc_vote(), it must raise an exception
+                    # if it can't commit the transaction.
+                    for jar in jars:
                         try:
                             vote = jar.tpc_vote
-                        except:
+                        except AttributeError:
                             pass
                         else:
-                            vote(self) # last chance to bail
+                            vote(self)
 
-                # Try to finish one jar, since we may be able to
-                # recover if the first one fails.
-                self._finish_one(jarsv)
-                # Once a single jar has finished, it's a fatal (hosed)
-                # error if another jar fails.
-                self._finish_rest(jarsv)
+                # Handle multiple jars separately.  If there are
+                # multiple jars and one fails during the finish, we
+                # mark this transaction manager as hosed.
+                if len(jars) == 1:
+                    self._finish_one(jars[0])
+                else:
+                    self._finish_many(jars)
             except:
                 # Ugh, we got an got an error during commit, so we
-                # have to clean up.
-                exc_info = sys.exc_info()
-                if jarsv is None:
-                    jarsv = jars.values()
-                self._commit_error(exc_info, objects, ncommitted,
-                                   jarsv, subjars)
+                # have to clean up.  First save the original exception
+                # in case the cleanup process causes another
+                # exception.
+                t, v, tb = sys.exc_info()
+                try:
+                    self._commit_error(objects, ncommitted, jars, subjars)
+                except:
+                    LOG('ZODB', ERROR,
+                        "A storage error occured during transaction "
+                        "abort.  This shouldn't happen.",
+                        error=sys.exc_info())
+                    
+                raise t, v, tb
         finally:
             del objects[:] # clear registered
             if not subtransaction and self._id is not None:
                 free_transaction()
 
-    def _commit_objects(self, objects, jars, subtransaction, subj):
-        # commit objects and return number of commits
-        ncommitted = 0
+    def _get_jars(self, objects, subtransaction):
+        # Returns a list of jars for this transaction.
+        
+        # Find all the jars and sort them in a globally consistent order.
+        # objects is a list of persistent objects and jars.
+        # If this is a subtransaction and a jar is not subtransaction aware,
+        # it's object gets delayed until the parent transaction commits.
+        
+        d = {}
         for o in objects:
-            j = getattr(o, '_p_jar', o)
-            if j is not None:
-                i = id(j)
-                if not jars.has_key(i):
-                    jars[i] = j
-
-                    if subtransaction:
-                        # If a jar does not support subtransactions,
-                        # we need to save it away to be committed in
-                        # the outer transaction.
-                        try:
-                            j.tpc_begin(self, subtransaction)
-                        except TypeError:
-                            j.tpc_begin(self)
+            jar = getattr(o, '_p_jar', o)
+            if jar is None:
+                # I don't think this should ever happen, but can't
+                # prove that it won't.  If there is no jar, there
+                # is nothing to be done.
+                self.log("Object with no jar registered for transaction: "
+                         "%s" % repr(o), level=BLATHER)
+                continue
+            # jar may not be safe as a dictionary key
+            key = id(jar)
+            d[key] = jar
+
+            if subtransaction:
+                if hasattr(jar, "commit_sub"):
+                    self._sub[key] = jar
+                else:
+                    if self._non_st_objects is None:
+                        self._non_st_objects = []
+                    self._non_st_objects.append(o)
+                
+        jars = d.values()
+        jars.sort(jar_cmp)
 
-                        if hasattr(j, 'commit_sub'):
-                            subj[i] = j
-                        else:
-                            if self._non_st_objects is None:
-                                self._non_st_objects = []
-                            self._non_st_objects.append(o)
-                            continue
-                    else:
-                        j.tpc_begin(self)
-                j.commit(o, self)
+        return jars
+
+    def _commit_begin(self, jars, subjars, subtransaction):
+        if subtransaction:
+            assert not subjars
+            for jar in jars:
+                try:
+                    jar.tpc_begin(self, subtransaction)
+                except TypeError:
+                    # Assume that TypeError means that tpc_begin() only
+                    # takes one argument, and that the jar doesn't
+                    # support subtransactions.
+                    jar.tpc_begin(self)
+        else:
+            # Merge in all the jars used by one of the subtransactions.
+
+            # When the top-level subtransaction commits, the tm must
+            # call commit_sub() for each jar involved in one of the
+            # subtransactions.  The commit_sub() method should call
+            # tpc_begin() on the storage object.
+
+            # It must also call tpc_begin() on jars that were used in
+            # a subtransaction but don't support subtransactions.
+
+            # These operations must be performed on the jars in order.
+
+            # Modify jars inplace to include the subjars, too.
+            jars += subjars
+            jars.sort(jar_cmp)
+            # assume that subjars is small, so that it's cheaper to test
+            # whether jar in subjars than to make a dict and do has_key.
+            for jar in jars:
+                if jar in subjars:
+                    jar.commit_sub(self)
+                else:
+                    jar.tpc_begin(self)
+
+    def _commit_objects(self, objects):
+        ncommitted = 0
+        for o in objects:
+            jar = getattr(o, "_p_jar", o)
+            if jar is None:
+                continue
+            jar.commit(o, self)
             ncommitted += 1
         return ncommitted
 
-    def _commit_subtrans(self, jars, subjars):
-        # Commit work done in subtransactions
-        while subjars:
-            j = subjars.pop()
-            i = id(j)
-            if not jars.has_key(i):
-                jars[i] = j
-            j.commit_sub(self)
-
-    def _finish_one(self, jarsv):
+    def _finish_one(self, jar):
         try:
-            if jarsv:
-                jarsv[-1].tpc_finish(self) # This should never fail
-                jarsv.pop() # It didn't, so it's taken care of.
+            # The database can't guarantee consistency if call fails.
+            jar.tpc_finish(self)
         except:
             # Bug if it does, we need to keep track of it
             LOG('ZODB', ERROR,
@@ -279,42 +365,40 @@
                 error=sys.exc_info())
             raise
 
-    def _finish_rest(self, jarsv):
+    def _finish_many(self, jars):
         global hosed
         try:
-            while jarsv:
-                jarsv[-1].tpc_finish(self) # This should never fail
-                jarsv.pop() # It didn't, so it's taken care of.
+            for jar in jars:
+                # The database can't guarantee consistency if call fails.
+                jar.tpc_finish(self)
         except:
-            # Bug if it does, we need to yell FIRE!
-            # Someone finished, so don't allow any more
-            # work without at least a restart!
             hosed = 1
             LOG('ZODB', PANIC,
                 "A storage error occurred in the last phase of a "
                 "two-phase commit.  This shouldn\'t happen. "
-                "The application may be in a hosed state, so "
-                "transactions will not be allowed to commit "
+                "The application will not be allowed to commit "
                 "until the site/storage is reset by a restart. ",
                 error=sys.exc_info())
             raise
 
-    def _commit_error(self, (t, v, tb),
-                      objects, ncommitted, jarsv, subjars):
-        # handle an exception raised during commit
-        # takes sys.exc_info() as argument
-
-        # First, we have to abort any uncommitted objects.
+    def _commit_error(self, objects, ncommitted, jars, subjars):
+        # First, we have to abort any uncommitted objects.  The abort
+        # will mark the object for invalidation, so that it's last
+        # committed state will be restored.
         for o in objects[ncommitted:]:
             try:
                 j = getattr(o, '_p_jar', o)
                 if j is not None:
                     j.abort(o, self)
             except:
-                pass
-
-        # Then, we unwind TPC for the jars that began it.
-        for j in jarsv:
+                # nothing to do but log the error
+                self.log("Failed to abort object %016x" % utils.U64(o._p_oid),
+                         error=sys.exc_info())
+
+        # Abort the two-phase commit.  It's only necessary to abort the
+        # commit for jars that began it, but it is harmless to abort it
+        # for all.
+        for j in jars:
             try:
                 j.tpc_abort(self) # This should never fail
             except:
@@ -322,9 +406,14 @@
                     "A storage error occured during object abort. This "
                     "shouldn't happen. ", error=sys.exc_info())
 
-        # Ugh, we need to abort work done in sub-transactions.
-        while subjars:
-            j = subjars.pop()
+        # After the tpc_abort(), call abort_sub() on all the
+        # subtrans-aware jars to *really* abort the subtransaction.
+        
+        # Example: For Connection(), the tpc_abort() will abort the
+        # subtransaction TmpStore() and abort_sub() will remove the
+        # TmpStore.
+
+        for j in subjars:
             try:
                 j.abort_sub(self) # This should never fail
             except:
@@ -333,8 +422,6 @@
                     "object abort.  This shouldn't happen.",
                     error=sys.exc_info())
 
-        raise t, v, tb
-
     def register(self,object):
         'Register the given object for transaction control.'
         self._append(object)
@@ -367,8 +454,6 @@
 information on the error that lead to this problem.
 """
 
-
-
 ############################################################################
 # install get_transaction:
 
@@ -405,5 +490,6 @@
 
 del _t
 
-import __main__
-__main__.__builtins__.get_transaction=get_transaction
+import __builtin__
+__builtin__.get_transaction=get_transaction
+del __builtin__


=== Zope/lib/python/ZODB/__init__.py 1.14 => 1.14.2.1 ===
--- Zope/lib/python/ZODB/__init__.py:1.14	Thu Aug 22 20:52:57 2002
+++ Zope/lib/python/ZODB/__init__.py	Tue Nov 12 16:13:58 2002
@@ -11,31 +11,34 @@
 # FOR A PARTICULAR PURPOSE
 #
 ##############################################################################
+
+__version__ = '3.1'
+
 import sys
 import cPersistence, Persistence
 from zLOG import register_subsystem
 register_subsystem('ZODB')
 
 # This is lame. Don't look. :(
-sys.modules['cPersistence']=cPersistence
+sys.modules['cPersistence'] = cPersistence
 
-Persistent=cPersistence.Persistent
+Persistent = cPersistence.Persistent
 
 # Install Persistent and PersistentMapping in Persistence
 if not hasattr(Persistence, 'Persistent'):
-    Persistence.Persistent=Persistent
-    Persistent.__module__='Persistence'
-    Persistence.Overridable=cPersistence.Overridable
-    Persistence.Overridable.__module__='Persistence'
+    Persistence.Persistent = Persistent
+    Persistent.__module__ = 'Persistence'
+    Persistence.Overridable = cPersistence.Overridable
+    Persistence.Overridable.__module__ = 'Persistence'
     if not hasattr(Persistence, 'PersistentMapping'):
         import PersistentMapping
-        sys.modules['PersistentMapping']=PersistentMapping
-        sys.modules['BoboPOS']=sys.modules['ZODB']
-        sys.modules['BoboPOS.PersistentMapping']=PersistentMapping
-        PersistentMapping=PersistentMapping.PersistentMapping
+        sys.modules['PersistentMapping'] = PersistentMapping
+        sys.modules['BoboPOS'] = sys.modules['ZODB']
+        sys.modules['BoboPOS.PersistentMapping'] = PersistentMapping
+        PersistentMapping = PersistentMapping.PersistentMapping
         from PersistentMapping import PersistentMapping
-        Persistence.PersistentMapping=PersistentMapping
-        PersistentMapping.__module__='Persistence'
+        Persistence.PersistentMapping = PersistentMapping
+        PersistentMapping.__module__ = 'Persistence'
         del PersistentMapping
 
 del cPersistence


=== Zope/lib/python/ZODB/cPersistence.c 1.62.10.1 => 1.62.10.2 ===
--- Zope/lib/python/ZODB/cPersistence.c:1.62.10.1	Wed Oct 16 12:22:03 2002
+++ Zope/lib/python/ZODB/cPersistence.c	Tue Nov 12 16:13:58 2002
@@ -848,7 +848,7 @@
 {
   PyObject *m, *d, *s;
 
-  s = PyString_FromString("TimeStamp");
+  s = PyString_FromString("ZODB.TimeStamp");
   if (s == NULL)
       return;
   m = PyImport_Import(s);
@@ -856,7 +856,8 @@
       Py_DECREF(s);
       return;
   }
-  TimeStamp = PyObject_GetAttr(m, s);
+  TimeStamp = PyObject_GetAttrString(m, "TimeStamp");
+  assert(TimeStamp);
   Py_DECREF(m);
   Py_DECREF(s);
 


=== Zope/lib/python/ZODB/coptimizations.c 1.17 => 1.17.60.1 ===
--- Zope/lib/python/ZODB/coptimizations.c:1.17	Fri Mar  8 13:36:14 2002
+++ Zope/lib/python/ZODB/coptimizations.c	Tue Nov 12 16:13:58 2002
@@ -33,207 +33,232 @@
 
 typedef struct {
   PyObject_HEAD
-  PyObject *jar, *stackup, *new_oid;
+  PyObject *jar, *stack, *new_oid;
 } persistent_id;
 
-staticforward PyTypeObject persistent_idType;
+static PyTypeObject persistent_idType;
 
 static persistent_id *
 newpersistent_id(PyObject *ignored, PyObject *args)
 {
-  persistent_id *self;
-  PyObject *jar, *stackup;
+    persistent_id *self;
+    PyObject *jar, *stack;
 
-  UNLESS (PyArg_ParseTuple(args, "OO", &jar, &stackup)) return NULL;
-  UNLESS(self = PyObject_NEW(persistent_id, &persistent_idType)) return NULL;
-  Py_INCREF(jar);
-  self->jar=jar;
-  Py_INCREF(stackup);
-  self->stackup=stackup;
-  self->new_oid=NULL;
-  return self;
+    if (!PyArg_ParseTuple(args, "OO!", &jar, &PyList_Type, &stack)) 
+	return NULL;
+    self = PyObject_NEW(persistent_id, &persistent_idType);
+    if (!self)
+	return NULL;
+    Py_INCREF(jar);
+    self->jar = jar;
+    Py_INCREF(stack);
+    self->stack = stack;
+    self->new_oid = NULL;
+    return self;
 }
 
-
 static void
 persistent_id_dealloc(persistent_id *self)
 {
-  Py_DECREF(self->jar);
-  Py_DECREF(self->stackup);
-  Py_XDECREF(self->new_oid);
-  PyObject_DEL(self);
+    Py_DECREF(self->jar);
+    Py_DECREF(self->stack);
+    Py_XDECREF(self->new_oid);
+    PyObject_DEL(self);
+}
+
+/* Returns the klass of a persistent object.
+   Returns NULL for other objects.
+*/
+static PyObject *
+get_class(PyObject *object)
+{
+    PyObject *class = NULL;
+
+    if (!PyExtensionClass_Check(object)) {
+	if (PyExtensionInstance_Check(object)) {
+	    class = PyObject_GetAttr(object, py___class__);
+	    if (!class) {
+		PyErr_Clear();
+		return NULL;
+	    }
+	    if (!PyExtensionClass_Check(class) ||
+		!(((PyExtensionClass*)class)->class_flags 
+		  & PERSISTENT_TYPE_FLAG)) {
+		Py_DECREF(class);
+		return NULL;
+	    }
+	}
+	else
+	    return NULL;
+    }
+    return class;
+}
+
+/* Return a two-tuple of the class's module and name.
+ */
+static PyObject *
+get_class_tuple(PyObject *class, PyObject *oid)
+{
+    PyObject *module = NULL, *name = NULL, *tuple;
+
+    module = PyObject_GetAttr(class, py___module__);
+    if (!module)
+	goto err;
+    if (!PyObject_IsTrue(module)) {
+	Py_DECREF(module);
+	/* XXX Handle degenerate 1.x ZClass case. */
+	return oid;
+    }
+
+    name = PyObject_GetAttr(class, py___name__);
+    if (!name)
+	goto err;
+
+    tuple = PyTuple_New(2);
+    if (!tuple)
+	goto err;
+    PyTuple_SET_ITEM(tuple, 0, module);
+    PyTuple_SET_ITEM(tuple, 1, name);
+
+    return tuple;
+ err:
+    Py_XDECREF(module);
+    Py_XDECREF(name);
+    return NULL;
+}
+
+static PyObject *
+set_oid(persistent_id *self, PyObject *object)
+{
+    PyObject *oid;
+
+    if (!self->new_oid) {
+	self->new_oid = PyObject_GetAttr(self->jar, py_new_oid);
+	if (!self->new_oid)
+	    return NULL;
+    }
+    oid = PyObject_CallObject(self->new_oid, NULL);
+    if (!oid)
+	return NULL;
+    if (PyObject_SetAttr(object, py__p_oid, oid) < 0) 
+	goto err;
+    if (PyObject_SetAttr(object, py__p_jar, self->jar) < 0) 
+	goto err;
+    if (PyList_Append(self->stack, object) < 0)
+	goto err;
+    return oid;
+ err:
+    Py_DECREF(oid);
+    return NULL;
 }
 
 static PyObject *
 persistent_id_call(persistent_id *self, PyObject *args, PyObject *kwargs)
 {
-  PyObject *object, *oid, *jar=NULL, *r=NULL, *klass=NULL;
+    PyObject *object, *oid, *klass=NULL;
+    PyObject *t1, *t2;
+    int setjar = 0;
+
+    if (!PyArg_ParseTuple(args, "O", &object))
+	return NULL;
+
+    klass = get_class(object);
+    if (!klass)
+	goto return_none;
+
+    oid = PyObject_GetAttr(object, py__p_oid);
+    if (!oid) {
+	PyErr_Clear();
+	Py_DECREF(klass);
+	goto return_none;
+    }
 
-  /*
-  def persistent_id(object, self=self,stackup=stackup):
-  */
-  UNLESS (PyArg_ParseTuple(args, "O", &object)) return NULL;
-
-  /*
-    if (not hasattr(object, '_p_oid') or
-        type(object) is ClassType): return None
-   */
-
-
-  /* Filter out most objects with low-level test. 
-     Yee ha! 
-     (Also get klass along the way.)
-  */
-  if (! PyExtensionClass_Check(object)) {
-    if (PyExtensionInstance_Check(object))
-      {
-	UNLESS (klass=PyObject_GetAttr(object, py___class__)) 
-	  {
+    if (oid != Py_None) {
+	PyObject *jar = PyObject_GetAttr(object, py__p_jar);
+	if (!jar)
 	    PyErr_Clear();
-	    goto not_persistent;
-	  }
-	UNLESS (
-		PyExtensionClass_Check(klass) &&
-		(((PyExtensionClass*)klass)->class_flags 
-		 & PERSISTENT_TYPE_FLAG)
-		)
-	  goto not_persistent;
-
-      }
-    else 
-      goto not_persistent;
-  }
-
-  UNLESS (oid=PyObject_GetAttr(object, py__p_oid)) 
-    {
-      PyErr_Clear();
-      goto not_persistent;
-    }
-
-  /*
-      if oid is None or object._p_jar is not self:
-   */
-  if (oid != Py_None)
-    {
-      UNLESS (jar=PyObject_GetAttr(object, py__p_jar)) PyErr_Clear();
-      if (jar && jar != Py_None && jar != self->jar)
-	{
-	  PyErr_SetString(InvalidObjectReference, 
-			  "Attempt to store an object from a foreign "
-			  "database connection");
-	  return NULL;
+	else {
+	    if (jar != Py_None && jar != self->jar) {
+		PyErr_SetString(InvalidObjectReference, 
+				"Attempt to store an object from a foreign "
+				"database connection");
+		goto err;
+	    }
+	    /* Ignore the oid of the unknown jar and assign a new one. */
+	    if (jar == Py_None)
+		setjar = 1;
+	    Py_DECREF(jar);
 	}
     }
 
-  if (oid == Py_None || jar != self->jar)
-    {
-      /*
-          oid = self.new_oid()
-          object._p_jar=self
-          object._p_oid=oid
-          stackup(object)
-      */
-      UNLESS (self->new_oid ||
-	      (self->new_oid=PyObject_GetAttr(self->jar, py_new_oid)))
+    if (oid == Py_None || setjar) {
+	Py_DECREF(oid);
+	oid = set_oid(self, object);
+	if (!oid)
 	    goto err;
-      ASSIGN(oid, PyObject_CallObject(self->new_oid, NULL));
-      UNLESS (oid) goto null_oid;
-      if (PyObject_SetAttr(object, py__p_jar, self->jar) < 0) goto err;
-      if (PyObject_SetAttr(object, py__p_oid, oid) < 0) goto err;
-      UNLESS (r=PyTuple_New(1)) goto err;
-      PyTuple_SET_ITEM(r, 0, object);
-      Py_INCREF(object);
-      ASSIGN(r, PyObject_CallObject(self->stackup, r));
-      UNLESS (r) goto err;
-      Py_DECREF(r);
-    }
-
-  /*
-      klass=object.__class__
-
-      if klass is ExtensionKlass: return oid
-  */
-  
-  if (PyExtensionClass_Check(object)) goto return_oid;
-
-  /*
-      if hasattr(klass, '__getinitargs__'): return oid
-  */
-
-  if ((r=PyObject_GetAttr(klass, py___getinitargs__)))
-    {
-      Py_DECREF(r);
-      goto return_oid;
-    }
-  PyErr_Clear();
-
-  /*
-      module=getattr(klass,'__module__','')
-      if module: klass=module, klass.__name__
-      else: return oid # degenerate 1.x ZClass case
-  */
-  UNLESS (jar=PyObject_GetAttr(klass, py___module__)) goto err;
-
-  UNLESS (PyObject_IsTrue(jar)) goto return_oid;
-
-  ASSIGN(klass, PyObject_GetAttr(klass, py___name__));
-  UNLESS (klass) goto err;
-
-  UNLESS (r=PyTuple_New(2)) goto err;
-  PyTuple_SET_ITEM(r, 0, jar);
-  PyTuple_SET_ITEM(r, 1, klass);
-  klass=r;
-  jar=NULL;
-
-  /*      
-      return oid, klass
-  */
-  UNLESS (r=PyTuple_New(2)) goto err;
-  PyTuple_SET_ITEM(r, 0, oid);
-  PyTuple_SET_ITEM(r, 1, klass);
-  return r;
-
-not_persistent:
-  Py_INCREF(Py_None);
-  return Py_None;
-
-err:
-  Py_DECREF(oid);
-  oid=NULL;
-
-null_oid:
-return_oid:
-  Py_XDECREF(jar);
-  Py_XDECREF(klass);
-  return oid;
+    }
+
+    if (PyExtensionClass_Check(object)
+	|| PyObject_HasAttr(klass, py___getinitargs__))
+	goto return_oid;
+
+    t2 = get_class_tuple(klass, oid);
+    if (!t2)
+	goto err;
+    if (t2 == oid) /* pass through ZClass special case */
+	goto return_oid;
+    t1 = PyTuple_New(2);
+    if (!t1) {
+	Py_DECREF(t2);
+	goto err;
+    }
+    /* use borrowed references to oid and t2 */
+    PyTuple_SET_ITEM(t1, 0, oid);
+    PyTuple_SET_ITEM(t1, 1, t2);
+
+    Py_DECREF(klass);
+
+    return t1;
+
+ err:
+    Py_XDECREF(oid);
+    oid = NULL;
+
+ return_oid:
+    Py_XDECREF(klass);
+    return oid;
+
+ return_none:
+    Py_INCREF(Py_None);
+    return Py_None;
 }
 
 
 static PyTypeObject persistent_idType = {
-  PyObject_HEAD_INIT(NULL)
-  0,				/*ob_size*/
-  "persistent_id",			/*tp_name*/
-  sizeof(persistent_id),		/*tp_basicsize*/
-  0,				/*tp_itemsize*/
-  /* methods */
-  (destructor)persistent_id_dealloc,	/*tp_dealloc*/
-  (printfunc)0,	/*tp_print*/
-  (getattrfunc)0,		/*obsolete tp_getattr*/
-  (setattrfunc)0,		/*obsolete tp_setattr*/
-  (cmpfunc)0,	/*tp_compare*/
-  (reprfunc)0,		/*tp_repr*/
-  0,		/*tp_as_number*/
-  0,		/*tp_as_sequence*/
-  0,		/*tp_as_mapping*/
-  (hashfunc)0,		/*tp_hash*/
-  (ternaryfunc)persistent_id_call,	/*tp_call*/
-  (reprfunc)0,		/*tp_str*/
-  (getattrofunc)0,	/*tp_getattro*/
-  (setattrofunc)0,	/*tp_setattro*/
-  
-  /* Space for future expansion */
-  0L,0L,
-  "C implementation of the persistent_id function defined in Connection.py"
+    PyObject_HEAD_INIT(NULL)
+    0,				/*ob_size*/
+    "persistent_id",			/*tp_name*/
+    sizeof(persistent_id),		/*tp_basicsize*/
+    0,				/*tp_itemsize*/
+    /* methods */
+    (destructor)persistent_id_dealloc,	/*tp_dealloc*/
+    (printfunc)0,	/*tp_print*/
+    (getattrfunc)0,		/*obsolete tp_getattr*/
+    (setattrfunc)0,		/*obsolete tp_setattr*/
+    (cmpfunc)0,	/*tp_compare*/
+    (reprfunc)0,		/*tp_repr*/
+    0,		/*tp_as_number*/
+    0,		/*tp_as_sequence*/
+    0,		/*tp_as_mapping*/
+    (hashfunc)0,		/*tp_hash*/
+    (ternaryfunc)persistent_id_call,	/*tp_call*/
+    (reprfunc)0,		/*tp_str*/
+    (getattrofunc)0,	/*tp_getattro*/
+    (setattrofunc)0,	/*tp_setattro*/
+    
+    /* Space for future expansion */
+    0L,0L,
+    "C implementation of the persistent_id function defined in Connection.py"
 };
 
 /* End of code for persistent_id objects */
@@ -243,45 +268,40 @@
 /* List of methods defined in the module */
 
 static struct PyMethodDef Module_Level__methods[] = {
-  {"new_persistent_id", (PyCFunction)newpersistent_id, METH_VARARGS,
-   "new_persistent_id(jar, stackup, new_oid)"
-   " -- get a new persistent_id function"},
-  {NULL, (PyCFunction)NULL, 0, NULL}		/* sentinel */
+    {"new_persistent_id", (PyCFunction)newpersistent_id, METH_VARARGS,
+     "new_persistent_id(jar, stack) -- get a new persistent_id function"},
+    {NULL, NULL}		/* sentinel */
 };
 
 void
 initcoptimizations(void)
 {
-  PyObject *m, *d;
+    PyObject *m, *d;
 
 #define make_string(S) if (! (py_ ## S=PyString_FromString(#S))) return
-  make_string(_p_oid);
-  make_string(_p_jar);
-  make_string(__getinitargs__);
-  make_string(__module__);
-  make_string(__class__);
-  make_string(__name__);
-  make_string(new_oid);
+    make_string(_p_oid);
+    make_string(_p_jar);
+    make_string(__getinitargs__);
+    make_string(__module__);
+    make_string(__class__);
+    make_string(__name__);
+    make_string(new_oid);
 			
-  /* Get InvalidObjectReference error */
-  UNLESS (m=PyString_FromString("POSException")) return;
-  ASSIGN(m, PyImport_Import(m));
-  UNLESS (m) return;
-  ASSIGN(m, PyObject_GetAttrString(m, "InvalidObjectReference"));
-  UNLESS (m) return;
-  InvalidObjectReference=m;
-
-  UNLESS (ExtensionClassImported) return;
-
-  m = Py_InitModule4("coptimizations", Module_Level__methods,
-		     coptimizations_doc_string,
-		     (PyObject*)NULL,PYTHON_API_VERSION);
-  d = PyModule_GetDict(m);
-
-  persistent_idType.ob_type=&PyType_Type;
-  PyDict_SetItemString(d,"persistent_idType", OBJECT(&persistent_idType));
-
-  /* Check for errors */
-  if (PyErr_Occurred())
-    Py_FatalError("can't initialize module coptimizations");
+    /* Get InvalidObjectReference error */
+    UNLESS (m=PyString_FromString("ZODB.POSException")) return;
+    ASSIGN(m, PyImport_Import(m));
+    UNLESS (m) return;
+    ASSIGN(m, PyObject_GetAttrString(m, "InvalidObjectReference"));
+    UNLESS (m) return;
+    InvalidObjectReference=m;
+
+    if (!ExtensionClassImported) 
+	return;
+
+    m = Py_InitModule3("coptimizations", Module_Level__methods,
+		       coptimizations_doc_string);
+    d = PyModule_GetDict(m);
+
+    persistent_idType.ob_type = &PyType_Type;
+    PyDict_SetItemString(d,"persistent_idType", OBJECT(&persistent_idType));
 }


=== Zope/lib/python/ZODB/fsdump.py 1.3 => 1.3.70.1 ===
--- Zope/lib/python/ZODB/fsdump.py:1.3	Mon Feb 11 14:38:09 2002
+++ Zope/lib/python/ZODB/fsdump.py	Tue Nov 12 16:13:58 2002
@@ -64,8 +64,14 @@
                 version = "version=%s " % rec.version
             else:
                 version = ''
-            print >> file, "  data #%05d oid=%016x %sclass=%s" % \
-                  (j, U64(rec.oid), version, fullclass)
+            if rec.data_txn:
+                # XXX It would be nice to print the transaction number
+                # (i) but it would be too expensive to keep track of.
+                bp = "bp=%016x" % U64(rec.data_txn)
+            else:
+                bp = ""
+            print >> file, "  data #%05d oid=%016x %sclass=%s %s" % \
+                  (j, U64(rec.oid), version, fullclass, bp)
             j += 1
         print >> file
         i += 1