[Zope-Checkins] CVS: Packages/BTrees/tests - testConflict.py:1.16.6.2

Tim Peters tim.one at comcast.net
Sat Mar 26 00:07:04 EST 2005


Update of /cvs-repository/Packages/BTrees/tests
In directory cvs.zope.org:/tmp/cvs-serv23963/BTrees/tests

Modified Files:
      Tag: Zope-2_7-branch
	testConflict.py 
Log Message:
Backport from ZODB 3.3.

Collector #1734.  Critical bug in BTree conflict resolution.

Stop silent data loss in some BTree cases where a transaction adds
a new key to a bucket while a concurrent transaction deletes all
keys from the same bucket.


=== Packages/BTrees/tests/testConflict.py 1.16.6.1 => 1.16.6.2 ===
--- Packages/BTrees/tests/testConflict.py:1.16.6.1	Thu Apr 29 17:51:50 2004
+++ Packages/BTrees/tests/testConflict.py	Sat Mar 26 00:07:03 2005
@@ -175,7 +175,7 @@
 
         test_merge(base, b1, b2, bm, 'merge insert from empty')
 
-    def testMergeEmptyAndFill(self):
+    def testFailMergeEmptyAndFill(self):
         base, b1, b2, bm, e1, e2, items = self._setupConflict()
 
         b1.clear()
@@ -183,7 +183,7 @@
         b2.update(e2)
         bm.update(e2)
 
-        test_merge(base, b1, b2, bm, 'merge insert from empty')
+        test_merge(base, b1, b2, bm, 'merge insert from empty', should_fail=1)
 
     def testMergeEmpty(self):
         base, b1, b2, bm, e1, e2, items = self._setupConflict()
@@ -273,7 +273,7 @@
 
         test_merge(base, b1, b2, bm, 'merge insert from empty')
 
-    def testMergeEmptyAndFill(self):
+    def testFailMergeEmptyAndFill(self):
         base, b1, b2, bm, e1, e2, items = self._setupConflict()
 
         b1.clear()
@@ -281,7 +281,7 @@
         b2.update(e2)
         bm.update(e2)
 
-        test_merge(base, b1, b2, bm, 'merge insert from empty')
+        test_merge(base, b1, b2, bm, 'merge insert from empty', should_fail=1)
 
     def testMergeEmpty(self):
         base, b1, b2, bm, e1, e2, items = self._setupConflict()
@@ -688,6 +688,91 @@
         # If the commit() segfaults, the C code is still wrong for this case.
         self.assertRaises(ConflictError, get_transaction().commit)
         get_transaction().abort()
+
+    def testConflictWithOneEmptyBucket(self):
+        # If one transaction empties a bucket, while another adds an item
+        # to the bucket, all the changes "look resolvable":  bucket conflict
+        # resolution returns a bucket containing (only) the item added by
+        # the latter transaction, but changes from the former transaction
+        # removing the bucket are uncontested:  the bucket is removed from
+        # the BTree despite that resolution thinks it's non-empty!  This
+        # was first reported by Dieter Maurer, to zodb-dev on 22 Mar 2005.
+        b = self.t
+        for i in range(0, 200, 4):
+            b[i] = i
+        # bucket 0 has 15 values: 0, 4 .. 56
+        # bucket 1 has 15 values: 60, 64 .. 116
+        # bucket 2 has 20 values: 120, 124 .. 196
+        state = b.__getstate__()
+        # Looks like:  ((bucket0, 60, bucket1, 120, bucket2), firstbucket)
+        # If these fail, the *preconditions* for running the test aren't
+        # satisfied -- the test itself hasn't been run yet.
+        self.assertEqual(len(state), 2)
+        self.assertEqual(len(state[0]), 5)
+        self.assertEqual(state[0][1], 60)
+        self.assertEqual(state[0][3], 120)
+
+        # Set up database connections to provoke conflict.
+        self.openDB()
+        r1 = self.db.open().root()
+        r1["t"] = self.t
+        get_transaction().commit()
+
+        r2 = self.db.open().root()
+        copy = r2["t"]
+        # Make sure all of copy is loaded.
+        list(copy.values())
+
+        self.assertEqual(self.t._p_serial, copy._p_serial)
+
+        # Now one transaction empties the first bucket, and another adds a
+        # key to the first bucket.
+
+        for k in range(0, 60, 4):
+            del self.t[k]
+        get_transaction().commit()
+
+        copy[1] = 1
+
+        try:
+            get_transaction().commit()
+        except ConflictError, detail:
+            self.assert_(str(detail).startswith('database conflict error'))
+            get_transaction().abort()
+        else:
+            self.fail("expected ConflictError")
+
+        # Same thing, except commit the transactions in the opposite order.
+        b = OOBTree()
+        for i in range(0, 200, 4):
+            b[i] = i
+
+        r1 = self.db.open().root()
+        r1["t"] = b
+        get_transaction().commit()
+
+        r2 = self.db.open().root()
+        copy = r2["t"]
+        # Make sure all of copy is loaded.
+        list(copy.values())
+
+        self.assertEqual(b._p_serial, copy._p_serial)
+
+        # Now one transaction empties the first bucket, and another adds a
+        # key to the first bucket.
+        b[1] = 1
+        get_transaction().commit()
+
+        for k in range(0, 60, 4):
+            del copy[k]
+        try:
+            get_transaction().commit()
+        except ConflictError, detail:
+            self.assert_(str(detail).startswith('database conflict error'))
+            get_transaction().abort()
+        else:
+            self.fail("expected ConflictError")
+
 
 def test_suite():
     suite = TestSuite()



More information about the Zope-Checkins mailing list