[Zope-Checkins] CVS: ZODB3/BTrees/tests - testConflict.py:1.13.8.2

Tim Peters tim.one@comcast.net
Fri, 17 Jan 2003 12:29:24 -0500


Update of /cvs-repository/ZODB3/BTrees/tests
In directory cvs.zope.org:/tmp/cvs-serv23721/BTrees/tests

Modified Files:
      Tag: ZODB3-3_1-branch
	testConflict.py 
Log Message:
Fixing a rare BTree conflict resolution error.

+ Transaction T1 deletes some of the keys in bucket B,
  but not all of the keys.

+ Transaction T2 deletes (exactly) the keys in B that
  aren't deleted by T1.

The version of B each transaction sees is then non-empty,
but conflict resolution creates an empty bucket.  However,
conflict resolution doesn't have enough info to unlink an
empty bucket from its containing BTree correctly.

The result is that an empty bucket is left in the BTree,
which violates a BTree invariant.  The most probable
symptom is a segfault, when later & unrelated code tries
to access this bucket:  an empty bucket has NULL
pointers where the vectors of keys and values should be,
and accessing code tries to dereference the NULL pointers.

I don't know that this error has been seen in real life.
It was provoked by a randomized multithreaded simulation
program that was trying to provoke errors.  This error was
provoked frequently by that program; no other kinds of
errors have come out of it.


=== ZODB3/BTrees/tests/testConflict.py 1.13.8.1 => 1.13.8.2 ===
--- ZODB3/BTrees/tests/testConflict.py:1.13.8.1	Thu Jan 16 17:51:21 2003
+++ ZODB3/BTrees/tests/testConflict.py	Fri Jan 17 12:29:22 2003
@@ -190,7 +190,7 @@
         b1.clear()
         bm.clear()
 
-        test_merge(base, b1, b2, bm, 'empty one and not other')
+        test_merge(base, b1, b2, bm, 'empty one and not other', should_fail=1)
 
     def testFailMergeInsert(self):
         base, b1, b2, bm, e1, e2, items = self._setupConflict()
@@ -288,7 +288,7 @@
         b1.clear()
         bm.clear()
 
-        test_merge(base, b1, b2, bm, 'empty one and not other')
+        test_merge(base, b1, b2, bm, 'empty one and not other', should_fail=1)
 
     def testFailMergeInsert(self):
         base, b1, b2, bm, e1, e2, items = self._setupConflict()
@@ -585,22 +585,12 @@
         self.assertEqual(state[0][1], 60)
         self.assertEqual(state[0][3], 120)
 
-        # Conflict resolution empties bucket1 entirely.
-
-        # XXX This is broken:  it doesn't raise ConflictError now.
-        ### XXX The ConflictError imported at the top of this module isn't
-        ### XXX the ConflictError that gets raised here.
-        ##from zodb.interfaces import ConflictError
-        ##self.assertRaises(ConflictError, get_transaction().commit)
-        ##get_transaction().abort()   # horrible things happen w/o this
-
-        # XXX Instead it creates an insane BTree (with an empty bucket
-        # XXX still linked in.  Remove the remaining lines and uncomment
-        # XXX the lines above when this is fixed.
-        # XXX    AssertionError: Bucket length < 1
-        get_transaction().commit()
-        self.assertRaises(AssertionError, b._check)
-
+        # Conflict resolution empties bucket1 entirely.  This used to
+        # create an "insane" BTree (a legit BTree cannot contain an empty
+        # bucket -- it contains NULL pointers the BTree code doesn't
+        # expect, and segfaults result).
+        self.assertRaises(ConflictError, get_transaction().commit)
+        get_transaction().abort()   # horrible things happen w/o this
 
     def testEmptyBucketNoConflict(self):
         # Tests that a plain empty bucket (on input) is not viewed as a