[Zope-Checkins] SVN: Zope/branches/Zope-2_8-branch/lib/python/tempstorage/ Allow tempstorage to participate in MVCC. Also fix minor bug where conflict cache could be poisoned by a transaction that failed after store was called.

Chris McDonough chrism at plope.com
Thu Jun 16 17:49:51 EDT 2005


Log message for revision 30823:
  Allow tempstorage to participate in MVCC.  Also fix minor bug where conflict cache could be poisoned by a transaction that failed after store was called.
  
  

Changed:
  U   Zope/branches/Zope-2_8-branch/lib/python/tempstorage/TemporaryStorage.py
  U   Zope/branches/Zope-2_8-branch/lib/python/tempstorage/tests/testTemporaryStorage.py

-=-
Modified: Zope/branches/Zope-2_8-branch/lib/python/tempstorage/TemporaryStorage.py
===================================================================
--- Zope/branches/Zope-2_8-branch/lib/python/tempstorage/TemporaryStorage.py	2005-06-16 21:42:33 UTC (rev 30822)
+++ Zope/branches/Zope-2_8-branch/lib/python/tempstorage/TemporaryStorage.py	2005-06-16 21:49:50 UTC (rev 30823)
@@ -28,6 +28,7 @@
 from ZODB.BaseStorage import BaseStorage
 from ZODB.ConflictResolution import ConflictResolvingStorage, ResolvedSerial
 import time
+import bisect
 
 # keep old object revisions for CONFLICT_CACHE_MAXAGE seconds
 CONFLICT_CACHE_MAXAGE = 60
@@ -135,6 +136,31 @@
         finally:
             self._lock_release()
 
+    def loadBefore(self, oid, tid):
+        """Return most recent revision of oid before tid committed
+        (for MVCC)
+        ."""
+        # implementation stolen from ZODB.test_storage.MinimalMemoryStorage
+        self._lock_acquire()
+        try:
+            tids = [stid for soid, stid in self._conflict_cache if soid == oid]
+            if not tids:
+                raise KeyError, oid
+            tids.sort()
+            i = bisect.bisect_left(tids, tid) -1
+            if i == -1:
+                return None
+            start_tid = tids[i]
+            j = i + 1
+            if j == len(tids):
+                end_tid = None
+            else:
+                end_tid = tids[j]
+            data = self.loadSerial(oid, start_tid)
+            return data, start_tid, end_tid
+        finally:
+            self._lock_release()
+
     def store(self, oid, serial, data, version, transaction):
         if transaction is not self._transaction:
             raise POSException.StorageTransactionError(self, transaction)
@@ -163,8 +189,6 @@
                 oserial = serial
             newserial=self._tid
             self._tmp.append((oid, data))
-            now = time.time()
-            self._conflict_cache[(oid, newserial)] = data, now
             return serial == oserial and newserial or ResolvedSerial
         finally:
             self._lock_release()
@@ -238,6 +262,8 @@
 
             index[oid] =  serial
             opickle[oid] = data
+            now = time.time()
+            self._conflict_cache[(oid, serial)] = data, now
 
         if zeros:
             for oid in zeros.keys():

Modified: Zope/branches/Zope-2_8-branch/lib/python/tempstorage/tests/testTemporaryStorage.py
===================================================================
--- Zope/branches/Zope-2_8-branch/lib/python/tempstorage/tests/testTemporaryStorage.py	2005-06-16 21:42:33 UTC (rev 30822)
+++ Zope/branches/Zope-2_8-branch/lib/python/tempstorage/tests/testTemporaryStorage.py	2005-06-16 21:49:50 UTC (rev 30823)
@@ -7,6 +7,11 @@
      Synchronization, ConflictResolution, \
      Corruption, RevisionStorage, MTStorage
 
+from persistent import Persistent
+import transaction
+from ZODB.DB import DB
+from ZODB.POSException import ReadConflictError
+
 class TemporaryStorageTests(
     StorageTestBase.StorageTestBase,
 ##    RevisionStorage.RevisionStorage, # not a revision storage, but passes
@@ -49,6 +54,48 @@
             TemporaryStorage.CONFLICT_CACHE_GCEVERY = old_gcevery
             TemporaryStorage.CONFLICT_CACHE_MAXAGE =  old_maxage
 
+    def doreadconflict(self, db, mvcc):
+        tm1 = transaction.TransactionManager()
+        conn = db.open(mvcc=mvcc, transaction_manager=tm1)
+        r1 = conn.root()
+        obj = MinPO('root')
+        r1["p"] = obj
+        obj = r1["p"]
+        obj.child1 = MinPO('child1')
+        tm1.get().commit()
+
+        # start a new transaction with a new connection
+        tm2 = transaction.TransactionManager()
+        cn2 = db.open(mvcc=mvcc, transaction_manager=tm2)
+        r2 = cn2.root()
+
+        self.assertEqual(r1._p_serial, r2._p_serial)
+
+        obj.child2 = MinPO('child2')
+        tm1.get().commit()
+
+        # resume the transaction using cn2
+        obj = r2["p"]
+
+        # An attempt to access obj.child1 should fail with an RCE
+        # below if conn isn't using mvcc, because r2 was read earlier
+        # in the transaction and obj was modified by the other
+        # transaction.
+
+        obj.child1 
+        return obj
+
+    def checkWithoutMVCCRaisesReadConflict(self):
+        db = DB(self._storage)
+        self.assertRaises(ReadConflictError, self.doreadconflict, db, False)
+
+    def checkWithMVCCDoesntRaiseReadConflict(self):
+        db = DB(self._storage)
+        ob = self.doreadconflict(db, True)
+        self.assertEquals(ob.__class__, MinPO)
+        self.assertEquals(getattr(ob, 'child1', MinPO()).value, 'child1')
+        self.failIf(getattr(ob, 'child2', None))
+
 def test_suite():
     suite = unittest.makeSuite(TemporaryStorageTests, 'check')
     suite2 = unittest.makeSuite(Corruption.FileStorageCorruptTests, 'check')



More information about the Zope-Checkins mailing list