[Zope-Checkins] CVS: StandaloneZODB/ZODB - FileStorage.py:1.89

Jeremy Hylton jeremy@zope.com
Sat, 30 Mar 2002 01:05:09 -0500


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

Modified Files:
	FileStorage.py 
Log Message:
Simplify transactional undo implementation.

Use the file position stored in the transaction_id whenever possible.
It is possible when _check_txn_pos() returns true; i.e. when the file
position does point to the transaction record header.

Remove ostloc and here arguments to _txn_undo_write(), as they can be
computed after the call.

Omit redundant tests in _txn_undo_write().  All the paths that lead
here verify that the file position is valid.

Remove some attribute lookup optimizations.


=== StandaloneZODB/ZODB/FileStorage.py 1.88 => 1.89 ===
                     'Undo is currently disabled for database maintenance.<p>')
             pos = self._pos
-            # BAW: Why 39 please?  This makes no sense (see also below).
-            if pos < 39:
-                return []
-            file=self._file
-            seek=file.seek
-            read=file.read
             r = []
             i = 0
+            # BAW: Why 39 please?  This makes no sense (see also below).
             while i < last and pos > 39:
                 self._file.seek(pos - 8)
                 pos = pos - U64(self._file.read(8)) - 8
@@ -1097,7 +1092,7 @@
                         e = loads(read(el))
                     except:
                         pass
-                next = read(8)
+                next = self._file.read(8)
                 # next is either the redundant txn length - 8, or an oid
                 if next == tl:
                     # There were no objects in this txn
@@ -1136,64 +1131,72 @@
         
         self._lock_acquire()
         try:
-            return self._transactional_undo(transaction_id)
+            return self._txn_undo(transaction_id)
         finally:
             self._lock_release()
 
-    def _transactional_undo(self, transaction_id):
-        # As seen in undoLog() below, transaction_id encodes the tid and
-        # possibly the oid of the first object in the transaction record.
-        # transaction_id will be of length 16 if there were objects
-        # affected by the txn, and 8 if there weren't (e.g. abortVersion()
-        # and commitVersion()).  In the latter case, we could still find
-        # the transaction through an expensive search of the file, but
-        # we're punting on that for now.
+    def _txn_undo(self, transaction_id):
+        # Find the right transaction to undo and call _txn_undo_write().
         transaction_id = base64.decodestring(transaction_id + '\n')
         tid = transaction_id[:8]
-        pos = U64(transaction_id[8:16])
-        # XXX
-        oid = transaction_id[16:]
-        if oid == '' or not self._index.has_key(oid):
-            # We can't get the position of the transaction easily.
-            # Note that there is a position encoded in the
-            # transaction_id at [8:16], but it can't be used reliably
-            # across multiple file storages and thus breaks
-            # transactional integrity.
-            raise UndoError, 'Undoing a non-object affecting transaction'
-        # Find the file position for the current revision of this object,
-        # and search back for the beginning of its transaction record
-        pos = self._index[oid]
-        ostloc = p64(self._pos)
-        here = self._pos + (self._tfile.tell() + self._thl)
-        while 1:
-            self._file.seek(pos)
-            h = self._file.read(DATA_HDR_LEN)
-            doid, serial, prev, tpos, vlen, plen = \
-                  unpack('>8s8s8s8sH8s', h)
-            tpos = U64(tpos)
-            self._file.seek(tpos)
-            # Read transaction id to see if we've got a match
-            thistid = self._file.read(8)
-            if thistid == tid:
-                break # Yeee ha!
-            # Keep looking
-            pos = U64(prev)
-            if not pos:
-                # We never found the right transaction
-                raise UndoError('Invalid undo transaction id')
-        # We're sitting at the transaction we want to undo, but let's move
-        # the file pointer back to the start of the txn record.
-        tindex = self._txn_undo_write(tpos, tid, ostloc, here)
+        tpos = U64(transaction_id[8:16])
+        if not self._check_txn_pos(tpos, tid):
+            # If the pos and tid don't match, we must use the oid to
+            # find the transaction record.  Find the file position for
+            # the current revision of this object, and search back for
+            # the beginning of its transaction record
+            oid = transaction_id[16:]
+            if oid == '' or not self._index.has_key(oid):
+                # XXX Is this the right error message?
+                raise UndoError('Undoing a non-object affecting transaction')
+            pos = self._index[oid]
+            while 1:
+                self._file.seek(pos)
+                h = self._file.read(DATA_HDR_LEN)
+                doid, serial, prev, tpos, vlen, plen = \
+                      unpack('>8s8s8s8sH8s', h)
+                tpos = U64(tpos)
+                self._file.seek(tpos)
+                # Read transaction id to see if we've got a match
+                thistid = self._file.read(8)
+                if thistid == tid:
+                    break # Yeee ha!
+                # Keep looking
+                pos = U64(prev)
+                if not pos:
+                    # We never found the right transaction
+                    raise UndoError('Invalid undo transaction id')
+        tindex = self._txn_undo_write(tpos, tid)
         self._tindex.update(tindex)
         return tindex.keys()            
 
-    def _txn_undo_write(self, tpos, tid, ostloc, here):
+    def _check_txn_pos(self, pos, tid):
+        "Return true if pos is location of the transaction record for tid."
+        self._file.seek(pos)
+        this_tid = self._file.read(8)
+        if this_tid != tid:
+            return 0
+        # be extra cautious: Check the record length makes sense, to
+        # guard against a random file location that happens to have
+        # the right 8-byte pattern.
+        stlen = self._file.read(8)
+        tlen = U64(stlen)
+        self._file.seek(tlen, 1)
+        redundant_stlen = self._file.read(8)
+        if len(redundant_stlen) != 8:
+            return 0
+        if redundant_stlen != stlen:
+            return 0
+        return 1
+
+    def _txn_undo_write(self, tpos, tid):
         # a helper function to write the data records for transactional undo
+
+        ostloc = p64(self._pos)
+        here = self._pos + (self._tfile.tell() + self._thl)
+        # Let's move the file pointer back to the start of the txn record.
         self._file.seek(tpos)
         h = self._file.read(TRANS_HDR_LEN)
-        # XXX jer: don't think the second test is needed at this point
-        if len(h) != TRANS_HDR_LEN or h[:8] != tid:
-            raise UndoError('Invalid undo transaction id')
         if h[16] == 'u':
             return
         if h[16] != ' ':
@@ -1230,7 +1233,7 @@
                 # Don't fail right away. We may be redeemed later!
                 failures[oid] = v
             else:
-                plen =len(p)                
+                plen = len(p)                
                 self._tfile.write(pack(">8s8s8s8sH8s",
                                        oid, self._serial, p64(ipos),
                                        ostloc, len(v), p64(plen)))