[Zodb-checkins] CVS: StandaloneZODB/ZODB - Connection.py:1.60.12.2 DemoStorage.py:1.7.30.2 FileStorage.py:1.75.16.3 MappingStorage.py:1.3.254.2 POSException.py:1.7.94.2

Jeremy Hylton jeremy@zope.com
Wed, 28 Nov 2001 15:53:17 -0500


Update of /cvs-repository/StandaloneZODB/ZODB
In directory cvs.zope.org:/tmp/cvs-serv7623/ZODB

Modified Files:
      Tag: StandaloneZODB-1_0-branch
	Connection.py DemoStorage.py FileStorage.py MappingStorage.py 
	POSException.py 
Log Message:
Greg Ward's long-awaited ConflictError patch

Change the use of ConflictError so that there is some rationale way to
examine the exception object and figure out what went wrong.  The key
change is to the defintion of the ConfictError class:

    Two transactions tried to modify the same object at once.  This
    transaction should be resubmitted.

    Instance attributes:
      oid : string
        the OID (8-byte packed string) of the object in conflict
      class_name : string
        the fully-qualified name of that object's class
      message : string
        a human-readable explanation of the error
      serials : (string, string)
        a pair of 8-byte packed strings; these are the serial numbers
        (old and new) of the object in conflict.  (Serial numbers are
        closely related [equal?] to transaction IDs; a ConflictError may
        be triggered by a serial number mismatch.)

Also add ReadConflictError and BTreesConflictError to cover two
special cases.  The latter deals with the apparently undocumented
values raised by the BTrees code when a failure occurs during conflict
resolution.

Also change all places where ConflictError is raised to special
whatever information is available object the conflict.


=== StandaloneZODB/ZODB/Connection.py 1.60.12.1 => 1.60.12.2 ===
 
 from cPickleCache import PickleCache
-from POSException import ConflictError
+from POSException import ConflictError, ReadConflictError
 from ExtensionClass import Base
 import ExportImport, TmpStore
 from zLOG import LOG, ERROR, BLATHER
@@ -245,7 +245,7 @@
                 or
                 invalid(None)
                 ):
-                raise ConflictError, `oid`
+                raise ConflictError(object=object)
             self._invalidating.append(oid)
 
         else:
@@ -312,7 +312,7 @@
                     or
                     invalid(None)
                     ):
-                    raise ConflictError, `oid`
+                    raise ConflictError(object=object)
                 self._invalidating.append(oid)
                 
             klass = object.__class__
@@ -456,7 +456,7 @@
             if invalid(oid) or invalid(None):
                 if not hasattr(object.__class__, '_p_independent'):
                     get_transaction().register(self)
-                    raise ConflictError(`oid`, `object.__class__`)
+                    raise ReadConflictError(object=object)
                 invalid=1
             else:
                 invalid=0
@@ -481,7 +481,7 @@
                     except KeyError: pass
                 else:
                     get_transaction().register(self)
-                    raise ConflictError(`oid`, `object.__class__`)
+                    raise ConflictError(object=object)
 
         except ConflictError:
             raise
@@ -541,7 +541,7 @@
 
     def tpc_begin(self, transaction, sub=None):
         if self._invalid(None): # Some nitwit invalidated everything!
-            raise ConflictError, "transaction already invalidated"
+            raise ConflictError("transaction already invalidated")
         self._invalidating=[]
         self._creating=[]
 


=== StandaloneZODB/ZODB/DemoStorage.py 1.7.30.1 => 1.7.30.2 ===
                     nv=old
 
-                if serial != oserial: raise POSException.ConflictError
+                if serial != oserial:
+                    raise POSException.ConflictError(serials=(oserial, serial))
                 
             serial=self._serial
             r=[oid, serial, old, version and (version, nv) or None, data]


=== StandaloneZODB/ZODB/FileStorage.py 1.75.16.2 => 1.75.16.3 ===
                     data=self.tryToResolveConflict(oid, oserial, serial, data)
                     if not data:
-                        raise POSException.ConflictError, (
-                            serial, oserial)
+                        raise POSException.ConflictError(
+                            serials=(oserial, serial))
             else:
                 oserial=serial
                     


=== StandaloneZODB/ZODB/MappingStorage.py 1.3.254.1 => 1.3.254.2 ===
                 old=self._index[oid]
                 oserial=old[:8]
-                if serial != oserial: raise POSException.ConflictError
+                if serial != oserial:
+                    raise POSException.ConflictError(serials=(oserial, serial))
                 
             serial=self._serial
             self._tindex.append((oid,serial+data))


=== StandaloneZODB/ZODB/POSException.py 1.7.94.1 => 1.7.94.2 ===
 
 from string import join
+from ZODB import utils
+
 StringType=type('')
 DictType=type({})
 
@@ -25,10 +27,86 @@
     """
 
 class ConflictError(TransactionError):
-    """Two transactions tried to modify the same object at once
+    """Two transactions tried to modify the same object at once.  This
+    transaction should be resubmitted.
+
+    Instance attributes:
+      oid : string
+        the OID (8-byte packed string) of the object in conflict
+      class_name : string
+        the fully-qualified name of that object's class
+      message : string
+        a human-readable explanation of the error
+      serials : (string, string)
+        a pair of 8-byte packed strings; these are the serial numbers
+        (old and new) of the object in conflict.  (Serial numbers are
+        closely related [equal?] to transaction IDs; a ConflictError may
+        be triggered by a serial number mismatch.)
+    """
+
+    def __init__(self, message=None, object=None, serials=None):
+        if message is None:
+            self.message = "database conflict error"
+        else:
+            self.message = message
+
+        if object is not None:
+            self.oid = object._p_oid
+            klass = object.__class__
+            self.class_name = klass.__module__ + "." + klass.__name__
+        else:
+            self.oid = None
+            self.class_name = None
+
+        self.serials = serials
+
+    def __str__(self):
+        extras = []
+        if self.oid:
+            extras.append("oid %016x" % utils.U64(self.oid))
+        if self.class_name:
+            extras.append("class %s" % self.class_name)
+        if self.serials:
+            extras.append("serial was %016x, now %016x" %
+                          tuple(map(utils.U64, self.serials)))
+        if extras:
+            return "%s (%s)" % (self.message, ", ".join(extras))
+        else:
+            return self.message
 
-    This transaction should be resubmitted.
+    def get_oid(self):
+        return self.oid
+
+    def get_class_name(self):
+        return self.class_name
+
+    def get_old_serial(self):
+        return self.serials[0]
+
+    def get_new_serial(self):
+        return self.serials[1]
+
+    def get_serials(self):
+        return self.serials
+
+
+class ReadConflictError(ConflictError):
+    """A conflict detected at read time -- attempt to read an object
+    that has changed in another transaction (eg. another thread
+    or process).
     """
+    def __init__(self, message=None, object=None, serials=None):
+        if message is None:
+            message = "database read conflict error"
+        ConflictError.__init__(self, message=message, object=object,
+                               serials=serials)
+
+class BTreesConflictError(ConflictError):
+    """A special subclass for BTrees conflict errors, which return
+    an undocumented four-tuple."""
+    def __init__(self, *btree_args):
+        ConflictError.__init__(self, message="BTrees conflict error")
+        self.btree = btree_args
 
 class VersionError(POSError):
     """An error in handling versions occurred