[ZODB-Dev] Inconsistent use of ConflictError

Greg Ward gward@mems-exchange.org
Fri, 17 Aug 2001 09:26:55 -0400


--0OAP2g/MAC+5xKAE
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

On 16 August 2001, Chris McDonough said:
> The conflict errors raised in the Connection class' getstate method are
> read conflicts... nothing else (AFAIK) is.

OK, thanks.  You meant *setstate*, right?  There is no
Connection.getstate().

Anyways, I've fixed that, and then I actually tested the patch.  Fixed
some bugs that turned up in ConflictError.__init__() and __str__(), and
now it seems to be working well.  Here are some sample error messages
generated by real conflicts:

  ZODB.POSException.ConflictError: database conflict error (oid 0000000000005a3d, class mems.run.run_database.RunDatabase)

  ZODB.POSException.ReadConflictError: database read conflict error (oid 0000000000005a3d, class mems.run.run_database.RunDatabase)

... ie. a bit wordy, but you get the information you need -- plus that
information is available programmatically through the ConflictError
exception object.

The ConflictErrors raised in bsddb3Storage tend to be like this:
  ConflictError(serial=(oserial, serial))
and the resulting error messages look like:
  database conflict error (serial was 0000000000000000, now 0000000000000000)
or this, if you also supply an object to ConflictError:
  database conflict error (oid 00000000000019f6, class mems.process.process_library.ProcessLibrary, serial was 0000000000000000, now 0000000000000000)

I think the main point of controversy is how to format OIDs and serial
numbers.  My inclination is that 64-bit numbers should be printed as
numbers, preferably as 16 hexadecimal characters for consistency and
clarity.  I could easily be talked into changing the "%016x" to just
"%x" though.

I don't much care for the `oid` formatting that would lead to error
messages like
  database conflict error (oid '\0\0\0\0\0\0\x19\xf6', class ...)
But that form is somewhat useful because you can cut and paste from an
error message to an interactive session, using the packed string OID to
lookup the actual object.

I also think that OIDs and serial numbers should be formatted
consistently -- ie. I can live with any of
  "%016x" % U64(oid)
  "%x" % U64(oid)
  `oid`
but I think serial numbers should be treated the same way.  They're
all 64-bit numbers, after all.

I'll attach a revised patch.

        Greg
-- 
Greg Ward - software developer                gward@mems-exchange.org
MEMS Exchange                            http://www.mems-exchange.org

--0OAP2g/MAC+5xKAE
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="POSException.patch"

Index: ZODB/POSException.py
===================================================================
RCS file: /cvs-repository/StandaloneZODB/ZODB/POSException.py,v
retrieving revision 1.7
diff -u -1 -r1.7 POSException.py
--- ZODB/POSException.py	12 Apr 2001 20:47:00 -0000	1.7
+++ ZODB/POSException.py	17 Aug 2001 13:15:51 -0000
@@ -90,2 +90,4 @@
 from string import join
+from ZODB import utils
+
 StringType=type('')
@@ -102,6 +104,76 @@
 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)
+
 

--0OAP2g/MAC+5xKAE
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="ZODB.patch"

Index: ZODB/Connection.py
===================================================================
RCS file: /cvs-repository/StandaloneZODB/ZODB/Connection.py,v
retrieving revision 1.58
diff -u -1 -r1.58 Connection.py
--- ZODB/Connection.py	4 Jun 2001 12:34:57 -0000	1.58
+++ ZODB/Connection.py	17 Aug 2001 13:15:52 -0000
@@ -90,3 +90,3 @@
 from cPickleCache import PickleCache
-from POSException import ConflictError, ExportError
+from POSException import ConflictError, ReadConflictError, ExportError
 from cStringIO import StringIO
@@ -323,3 +323,3 @@
                 ):
-                raise ConflictError, `oid`
+                raise ConflictError(object=object)
             self._invalidating.append(oid)
@@ -390,3 +390,3 @@
                     ):
-                    raise ConflictError, `oid`
+                    raise ConflictError(object=object)
                 self._invalidating.append(oid)
@@ -534,3 +534,3 @@
                     get_transaction().register(self)
-                    raise ConflictError(`oid`, `object.__class__`)
+                    raise ReadConflictError(object=object)
                 invalid=1
@@ -559,3 +559,3 @@
                     get_transaction().register(self)
-                    raise ConflictError(`oid`, `object.__class__`)
+                    raise ConflictError(object=object)
 
@@ -619,3 +619,3 @@
         if self._invalid(None): # Some nitwit invalidated everything!
-            raise ConflictError, "transaction already invalidated"
+            raise ConflictError("transaction already invalidated")
         self._invalidating=[]
Index: ZODB/DemoStorage.py
===================================================================
RCS file: /cvs-repository/StandaloneZODB/ZODB/DemoStorage.py,v
retrieving revision 1.6
diff -u -1 -r1.6 DemoStorage.py
--- ZODB/DemoStorage.py	20 Feb 2001 15:00:07 -0000	1.6
+++ ZODB/DemoStorage.py	17 Aug 2001 13:15:52 -0000
@@ -297,3 +297,4 @@
 
-                if serial != oserial: raise POSException.ConflictError
+                if serial != oserial:
+                    raise POSException.ConflictError(serials=(oserial, serial))
                 
Index: ZODB/FileStorage.py
===================================================================
RCS file: /cvs-repository/StandaloneZODB/ZODB/FileStorage.py,v
retrieving revision 1.62
diff -u -1 -r1.62 FileStorage.py
--- ZODB/FileStorage.py	19 Jul 2001 12:03:43 -0000	1.62
+++ ZODB/FileStorage.py	17 Aug 2001 13:15:54 -0000
@@ -662,4 +662,4 @@
                     if not data:
-                        raise POSException.ConflictError, (
-                            serial, oserial)
+                        raise POSException.ConflictError(
+                            serials=(oserial, serial))
             else:
Index: ZODB/MappingStorage.py
===================================================================
RCS file: /cvs-repository/StandaloneZODB/ZODB/MappingStorage.py,v
retrieving revision 1.3
diff -u -1 -r1.3 MappingStorage.py
--- ZODB/MappingStorage.py	11 Jul 1999 21:51:42 -0000	1.3
+++ ZODB/MappingStorage.py	17 Aug 2001 13:15:54 -0000
@@ -210,3 +210,4 @@
                 oserial=old[:8]
-                if serial != oserial: raise POSException.ConflictError
+                if serial != oserial:
+                    raise POSException.ConflictError(serials=(oserial, serial))
                 

--0OAP2g/MAC+5xKAE
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="bsddb3Storage.patch"

Index: bsddb3Storage/bsddb3Storage/Full.py
===================================================================
RCS file: /cvs-repository/StandaloneZODB/bsddb3Storage/bsddb3Storage/Full.py,v
retrieving revision 1.29
diff -u -1 -r1.29 Full.py
--- bsddb3Storage/bsddb3Storage/Full.py	9 Jul 2001 18:38:42 -0000	1.29
+++ bsddb3Storage/bsddb3Storage/Full.py	17 Aug 2001 13:15:55 -0000
@@ -573,4 +573,3 @@
                     raise POSException.ConflictError(
-                        'serial number mismatch (was: %s, has: %s)' %
-                        (utils.U64(oserial), utils.U64(serial)))
+                        serials=(oserial, serial))
             # Do we already know about this version?  If not, we need to
Index: bsddb3Storage/bsddb3Storage/Minimal.py
===================================================================
RCS file: /cvs-repository/StandaloneZODB/bsddb3Storage/bsddb3Storage/Minimal.py,v
retrieving revision 1.9
diff -u -1 -r1.9 Minimal.py
--- bsddb3Storage/bsddb3Storage/Minimal.py	27 Apr 2001 23:28:59 -0000	1.9
+++ bsddb3Storage/bsddb3Storage/Minimal.py	17 Aug 2001 13:15:55 -0000
@@ -178,4 +178,3 @@
                 raise POSException.ConflictError(
-                    'serial number mismatch (was: %s, has: %s)' %
-                    (utils.U64(oserial), utils.U64(serial)))
+                    serials=(oserial, serial))
             # Our serial number is updated in BaseStorage's tpc_begin() call,
Index: bsddb3Storage/bsddb3Storage/MinimalReplicated.py
===================================================================
RCS file: /cvs-repository/StandaloneZODB/bsddb3Storage/bsddb3Storage/MinimalReplicated.py,v
retrieving revision 1.1
diff -u -1 -r1.1 MinimalReplicated.py
--- bsddb3Storage/bsddb3Storage/MinimalReplicated.py	9 Nov 2000 16:47:31 -0000	1.1
+++ bsddb3Storage/bsddb3Storage/MinimalReplicated.py	17 Aug 2001 13:15:55 -0000
@@ -30,3 +30,4 @@
                 oserial=self._index[oid]
-                if serial != oserial: raise POSException.ConflictError
+                if serial != oserial:
+                    raise POSException.ConflictError(serials=(oserial, serial))
                 
Index: bsddb3Storage/bsddb3Storage/Packless.py
===================================================================
RCS file: /cvs-repository/StandaloneZODB/bsddb3Storage/bsddb3Storage/Packless.py,v
retrieving revision 1.5
diff -u -1 -r1.5 Packless.py
--- bsddb3Storage/bsddb3Storage/Packless.py	27 Mar 2001 21:26:16 -0000	1.5
+++ bsddb3Storage/bsddb3Storage/Packless.py	17 Aug 2001 13:15:56 -0000
@@ -157,3 +157,4 @@
                 oserial=self._index[oid]
-                if serial != oserial: raise POSException.ConflictError
+                if serial != oserial:
+                    raise POSException.ConflictError(serials=(oserial, serial))
                 

--0OAP2g/MAC+5xKAE--