[Zope3-checkins] CVS: ZODB4/src/zodb/storage - base.py:1.17 bdbfull.py:1.14 bdbminimal.py:1.13 file.py:1.12 fsdump.py:1.4 interfaces.py:1.10 mapping.py:1.6

Barry Warsaw barry@wooz.org
Thu, 13 Mar 2003 16:32:59 -0500


Update of /cvs-repository/ZODB4/src/zodb/storage
In directory cvs.zope.org:/tmp/cvs-serv27419/src/zodb/storage

Modified Files:
	base.py bdbfull.py bdbminimal.py file.py fsdump.py 
	interfaces.py mapping.py 
Log Message:
> I believe we're ready to merge back to the head.

merging the opaque-pickles-branch back into the head



=== ZODB4/src/zodb/storage/base.py 1.16 => 1.17 ===
--- ZODB4/src/zodb/storage/base.py:1.16	Thu Feb 27 18:20:13 2003
+++ ZODB4/src/zodb/storage/base.py	Thu Mar 13 16:32:28 2003
@@ -1,4 +1,3 @@
-
 ##############################################################################
 #
 # Copyright (c) 2001 Zope Corporation and Contributors.
@@ -41,14 +40,13 @@
     berkeley_is_available = False
 
 from zodb.timestamp import newTimeStamp, TimeStamp
-from zodb.interfaces import ITransactionAttrs
+from zodb.interfaces import ITransactionAttrs, ZERO
 from zodb.storage.interfaces import StorageTransactionError, ReadOnlyError
 # BaseStorage provides primitives for lock acquisition and release, and a host
 # of other methods, some of which are overridden here, some of which are not.
 from zodb.lockfile import LockFile
 from zodb.serialize import findrefs
 
-ZERO = '\0'*8
 GBYTES = 1024 * 1024 * 1000
 JOIN_TIME = 10
 
@@ -335,11 +333,25 @@
             for r in transaction:
                 if verbose:
                     print `r.oid`, r.version, len(r.data)
-                self.restore(r.oid, r.serial, r.data, r.version,
+                self.restore(r.oid, r.serial, r.data, r.refs, r.version,
                              r.data_txn, transaction)
             self.tpcVote(transaction)
             self.tpcFinish(transaction)
 
+# A couple of convenience methods
+def splitrefs(refstr, oidlen=8):
+    # refstr is a packed string of reference oids.  Always return a list of
+    # oid strings.  Most storages use fixed oid lengths of 8 bytes, but if
+    # the oids in refstr are a different size, use oidlen to specify.  This
+    # does /not/ support variable length oids in refstr.
+    if not refstr:
+        return []
+    num, extra = divmod(len(refstr), oidlen)
+    fmt = '%ds' % oidlen
+    assert extra == 0, refstr
+    return list(struct.unpack('>' + (fmt * num), refstr))
+
+
 
 class BerkeleyConfig:
     """Bag of attributes for configuring Berkeley based storages.
@@ -497,6 +509,7 @@
         self._serials = self._setupDB('serials', db.DB_DUP)
         self._pickles = self._setupDB('pickles')
         self._refcounts = self._setupDB('refcounts')
+        self._references = self._setupDB('references')
         self._oids = self._setupDB('oids')
         self._pending = self._setupDB('pending')
         self._packmark = self._setupDB('packmark')
@@ -695,8 +708,8 @@
         self._env.close()
 
     # A couple of convenience methods
-    def _update(self, deltas, data, incdec):
-        for oid in findrefs(data):
+    def _update(self, deltas, references, incdec):
+        for oid in splitrefs(references):
             rc = deltas.get(oid, 0) + incdec
             if rc == 0:
                 # Save space in the dict by zapping zeroes


=== ZODB4/src/zodb/storage/bdbfull.py 1.13 => 1.14 === (487/587 lines abridged)
--- ZODB4/src/zodb/storage/bdbfull.py:1.13	Tue Feb 11 10:59:27 2003
+++ ZODB4/src/zodb/storage/bdbfull.py	Thu Mar 13 16:32:28 2003
@@ -14,7 +14,7 @@
 
 """Berkeley storage with full undo and versioning support.
 
-$Revision$
+$Id$
 """
 
 import time
@@ -24,35 +24,28 @@
 from zodb.interfaces import *
 from zodb.storage.interfaces import *
 from zodb.utils import p64, u64
-from zodb.serialize import findrefs
 from zodb.timestamp import TimeStamp
 from zodb.conflict import ConflictResolvingStorage, ResolvedSerial
 from zodb.interfaces import ITransactionAttrs
 from zodb.storage.interfaces import StorageSystemError
-from zodb.storage.base import db, ZERO, BerkeleyBase, PackStop, _WorkThread
+from zodb.storage.base import db, BerkeleyBase, PackStop, _WorkThread, \
+     splitrefs
 from zodb.storage._helper import incr
 
 ABORT = 'A'
 COMMIT = 'C'
 PRESENT = 'X'
 
-BDBFULL_SCHEMA_VERSION = 'BF01'
+BDBFULL_SCHEMA_VERSION = 'BF02'
 
+EMPTYSTRING = ''
 # Special flag for uncreated objects (i.e. Does Not Exist)
-DNE = '\377'*8
+DNE = MAXTID
 # DEBUGGING
 #DNE = 'nonexist'
 
 
 
-def DB(name, config):
-    """Create a new object database using BDBFullStorage."""
-    import zodb.db
-    storage = BDBFullStorage(name, config=config)
-    return zodb.db.DB(storage)
-
-
-
 class BDBFullStorage(BerkeleyBase, ConflictResolvingStorage):

[-=- -=- -=- 487 lines omitted -=- -=- -=-]

+                self._cursor = self._table.cursor()
+                try:
+                    self._rec = self._cursor.set(self.tid)
+                except db.DBNotFoundError:
+                    pass
+            # Cursor exhausted?
+            if self._rec is None:
+                self.close()
+                raise IndexError
+            oid = self._rec[1]
+            self._rec = self._cursor.next_dup()
+            data, version, lrevid, refs = self._storage._loadSerialEx(
+                oid, self.tid)
+            return _Record(oid, self.tid, version, data, lrevid, refs)
+        except:
+            self.close()
+            raise
+
+    def close(self):
+        if self._cursor:
+            self._cursor.close()
+            self._cursor = None
 
 
 
 class _Record:
+
+    __implements__ = IDataRecord
+
     # Object Id
     oid = None
     # Object serial number (i.e. revision id)
@@ -1794,13 +1854,16 @@
     data = None
     # The pointer to the transaction containing the pickle data, if not None
     data_txn = None
+    # The list of oids of objects referred to by this object
+    refs = []
 
-    def __init__(self, oid, serial, version, data, data_txn):
+    def __init__(self, oid, serial, version, data, data_txn, refs):
         self.oid = oid
         self.serial = serial
         self.version = version
         self.data = data
         self.data_txn = data_txn
+        self.refs = refs
 
 
 


=== ZODB4/src/zodb/storage/bdbminimal.py 1.12 => 1.13 ===
--- ZODB4/src/zodb/storage/bdbminimal.py:1.12	Tue Feb 11 10:59:27 2003
+++ ZODB4/src/zodb/storage/bdbminimal.py	Thu Mar 13 16:32:28 2003
@@ -14,28 +14,22 @@
 
 """Berkeley storage without undo or versioning.
 
-$Revision$
+$Id$
 """
 
+from zodb.interfaces import ZERO
 from zodb.storage.interfaces import *
 from zodb.utils import p64, u64
-from zodb.serialize import findrefs
 from zodb.conflict import ConflictResolvingStorage, ResolvedSerial
-from zodb.storage.base import db, ZERO, BerkeleyBase, PackStop, _WorkThread
+from zodb.storage.base import db, BerkeleyBase, PackStop, _WorkThread
+from zodb.storage.base import splitrefs
 
 ABORT = 'A'
 COMMIT = 'C'
 PRESENT = 'X'
+EMPTYSTRING = ''
 
-BDBMINIMAL_SCHEMA_VERSION = 'BM01'
-
-
-
-def DB(name, config):
-    """Create a new object database using BDBMinimalStorage."""
-    import zodb.db
-    storage = BDBMinimalStorage(name, config=config)
-    return zodb.db.DB(storage)
+BDBMINIMAL_SCHEMA_VERSION = 'BM02'
 
 
 
@@ -78,6 +72,11 @@
         #     reference count is updated during the _finish() call.  When it
         #     goes to zero, the object is automatically deleted.
         #
+        # references -- {oid+tid -> oid+oid+...}
+        #     For each revision of the object, these are the oids of the
+        #     objects referred to in the data record, as a list of 8-byte
+        #     oids, concatenated together.
+        #
         # oids -- [oid]
         #     This is a list of oids of objects that are modified in the
         #     current uncommitted transaction.
@@ -149,8 +148,11 @@
                     pass
                 else:
                     cs.delete()
-                # And delete the pickle table entry for this revision.
-                self._pickles.delete(oid+tid, txn=txn)
+                # Clean up revision-indexed tables
+                revid = oid+tid
+                self._pickles.delete(revid, txn=txn)
+                if self._references.has_key(revid):
+                    self._references.delete(revid, txn=txn)
         finally:
             # There's a small window of opportunity for leaking a cursor here,
             # if co.close() were to fail.  In practice this shouldn't happen.
@@ -185,19 +187,22 @@
                     if soid <> oid:
                         break
                     if stid <> tid:
+                        revid = oid+stid
                         # This is the previous revision of the object, so
-                        # decref its referents and clean up its pickles.
+                        # decref its references and clean up its pickles.
                         cs.delete()
-                        data = self._pickles.get(oid+stid, txn=txn)
-                        assert data is not None
-                        self._update(deltas, data, -1)
-                        self._pickles.delete(oid+stid, txn=txn)
+                        references = self._references.get(revid, txn=txn)
+                        if references:
+                            self._update(deltas, references, -1)
+                        self._pickles.delete(revid, txn=txn)
+                        if self._references.has_key(revid):
+                            self._references.delete(revid, txn=txn)
                     srec = cs.next_dup()
                 # Now add incref deltas for all objects referenced by the new
                 # revision of this object.
-                data = self._pickles.get(oid+tid, txn=txn)
-                assert data is not None
-                self._update(deltas, data, 1)
+                references = self._references.get(oid+tid, txn=txn)
+                if references:
+                    self._update(deltas, references, 1)
         finally:
             # There's a small window of opportunity for leaking a cursor here,
             # if co.close() were to fail.  In practice this shouldn't happen.
@@ -231,8 +236,9 @@
                 # pickles and refcounts table.  Note that before we remove its
                 # pickle, we need to decref all the objects referenced by it.
                 current = self._getCurrentSerial(oid)
-                data = self._pickles.get(oid+current, txn=txn)
-                self._update(newdeltas, data, -1)
+                references = self._references.get(oid+current, txn=txn)
+                if references:
+                    self._update(newdeltas, references, -1)
                 # And delete the serials, pickle and refcount entries.  At
                 # this point, I believe we should have just one serial entry.
                 self._serials.delete(oid, txn=txn)
@@ -258,7 +264,7 @@
         else:
             txn.commit()
 
-    def _dostore(self, txn, oid, serial, data):
+    def _dostore(self, txn, oid, serial, data, refs):
         conflictresolved = False
         oserial = self._getCurrentSerial(oid)
         if oserial is not None and serial <> oserial:
@@ -267,11 +273,15 @@
             # number.  Raise a ConflictError.
             data = self.resolveConflict(oid, oserial, serial, data)
             conflictresolved = True
-        # Optimistically write to the serials and pickles table.  Be sure
-        # to also update the oids table for this object too.
+        # Optimistically write to the various tables.
         newserial = self._serial
+        revid = oid+newserial
         self._serials.put(oid, newserial, txn=txn)
-        self._pickles.put(oid+newserial, data, txn=txn)
+        self._pickles.put(revid, data, txn=txn)
+        if refs:
+            references = EMPTYSTRING.join(refs)
+            assert len(references) % 8 == 0
+            self._references.put(revid, references, txn=txn)
         self._oids.put(oid, PRESENT, txn=txn)
         # If we're in the middle of a pack, we need to add these objects to
         # the packmark, so a specific race condition won't collect them.
@@ -284,7 +294,7 @@
             return ResolvedSerial
         return newserial
 
-    def store(self, oid, serial, data, version, transaction):
+    def store(self, oid, serial, data, refs, version, transaction):
         if transaction is not self._transaction:
             raise StorageTransactionError(self, transaction)
         # We don't support versions
@@ -293,7 +303,7 @@
         # All updates must be done with the application lock acquired
         self._lock_acquire()
         try:
-            return self._withtxn(self._dostore, oid, serial, data)
+            return self._withtxn(self._dostore, oid, serial, data, refs)
         finally:
             self._lock_release()
 
@@ -434,9 +444,12 @@
                 # unit tests), and we're looking up oid ZERO.  Then serial
                 # will be None.
                 if tid is not None:
-                    data = self._pickles[oid+tid]
-                    for oid in findrefs(data):
-                        self._oidqueue.append(oid, txn)
+                    # Now get the oids of all the objects referenced by this
+                    # object revision
+                    references = self._references.get(oid+tid)
+                    if references:
+                        for oid in splitrefs(references):
+                            self._oidqueue.append(oid, txn)
             # Pop the next oid off the queue and do it all again
             rec = self._oidqueue.consume(txn)
             oid = rec and rec[1]
@@ -487,7 +500,7 @@
                     pass
             finally:
                 c.close()
-            # Now collect the pickle data and do reference counting
+            # Collect the pickle data
             c = self._pickles.cursor(txn)
             try:
                 try:
@@ -497,17 +510,33 @@
                 while rec and rec[0][:8] == oid:
                     if self._stop:
                         raise PackStop, 'stopped in _collect_objs() loop 2'
-                    data = rec[1]
                     c.delete()
                     rec = c.next()
-                    deltas = {}
-                    self._update(deltas, data, -1)
-                    for oid, delta in deltas.items():
-                        refcount = u64(self._refcounts.get(oid, ZERO)) + delta
-                        if refcount <= 0:
-                            self._oidqueue.append(oid, txn)
-                        else:
-                            self._refcounts.put(oid, p64(refcount), txn=txn)
+            finally:
+                c.close()
+            # Collect references and do reference counting
+            c = self._references.cursor(txn)
+            try:
+                try:
+                    rec = c.set_range(oid)
+                except db.DBNotFoundError:
+                    rec = None
+                while rec and rec[0][:8] == oid:
+                    if self._stop:
+                        raise PackStop, 'stopped in _collect_objs() loop 3'
+                    references = rec[1]
+                    if references:
+                        deltas = {}
+                        self._update(deltas, references, -1)
+                        for oid, delta in deltas.items():
+                            rc = u64(self._refcounts.get(oid, ZERO)) + delta
+                            if rc <= 0:
+                                self._oidqueue.append(oid, txn)
+                            else:
+                                self._refcounts.put(oid, p64(rc), txn=txn)
+                        # Delete table entry
+                        c.delete()
+                        rec = c.next()
             finally:
                 c.close()
             # We really do want this down here, since _decrefPickle() could


=== ZODB4/src/zodb/storage/file.py 1.11 => 1.12 === (1431/1531 lines abridged)
--- ZODB4/src/zodb/storage/file.py:1.11	Thu Feb 27 18:19:20 2003
+++ ZODB4/src/zodb/storage/file.py	Thu Mar 13 16:32:28 2003
@@ -19,7 +19,7 @@
 
   In this section, the first two bytes are the characters F and S.
 
-  The next two bytes are a storage format version id, currently "01".
+  The next two bytes are a storage format version id, currently "42".
 
   The next section is a four-byte database version string, encoded as
   byte 0: major version number
@@ -67,6 +67,8 @@
 
   - 2-byte version length
 
+  - 4-byte number of object references (oids)
+
   - 8-byte data length
 
   ? 8-byte position of non-version data
@@ -75,11 +77,11 @@
   ? 8-byte position of previous record in this version
     (if version length > 0)
 
-  ?   version string
-    (if version length > 0)
+  ? version string (if version length > 0)
 
-  ?   data
-    (data length > 0)
+  ? reference oids (length == # of oids * 8)
+
+  ? data (if data length > 0)
 
   ? 8-byte position of data record containing data
     (data length == 0)
@@ -146,26 +148,25 @@
     fsync = None
 
 import zodb.db
-from zodb.storage.base import BaseStorage
+from zodb.storage.base import BaseStorage, splitrefs
 from zodb import conflict
 from zodb import interfaces
-from zodb.interfaces import UndoError, POSKeyError, MultipleUndoErrors
-from zodb.serialize import findrefs
+from zodb.interfaces import _fmt_oid
+from zodb.interfaces import *
 from zodb.timestamp import TimeStamp, newTimeStamp, timeStampFromTime
 from zodb.lockfile import LockFile

[-=- -=- -=- 1431 lines omitted -=- -=- -=-]

+    def __init__(self, oid, serial, version, data, data_txn, refs):
+        self.oid = oid
+        self.serial = serial
+        self.version = version
+        self.data = data
+        self.data_txn = data_txn
+        self.refs = refs
 
 class UndoSearch:
 
@@ -2261,19 +2111,22 @@
 class DataHeader:
     """Header for a data record."""
 
-    __slots__ = ("oid", "serial", "prev", "tloc", "vlen", "plen", "back",
-                 # These three attributes are only defined when vlen > 0
-                 "pnv", "vprev", "version")
+    __slots__ = (
+        "oid", "serial", "prev", "tloc", "vlen", "plen", "nrefs", "back",
+        # These three attributes are only defined when vlen > 0
+        "pnv", "vprev", "version")
 
     version = ""
     back = 0
 
-    def __init__(self, oid, serial, prev, tloc, vlen, plen):
+    def __init__(self, oid, serial, prev, tloc, vlen, nrefs, plen):
         self.oid = oid
         self.serial = serial
         self.prev = prev
         self.tloc = tloc
+
         self.vlen = vlen
+        self.nrefs = nrefs
         self.plen = plen
 
     def fromString(cls, s):
@@ -2284,6 +2137,12 @@
     def parseVersion(self, buf):
         self.pnv, self.vprev = struct.unpack(">QQ", buf[:16])
         self.version = buf[16:]
+
+    def recordlen(self):
+        rlen = DATA_HDR_LEN + (self.nrefs * 8) + (self.plen or 8)
+        if self.version:
+            rlen += 16 + self.vlen
+        return rlen
 
 
 def cleanup(filename):


=== ZODB4/src/zodb/storage/fsdump.py 1.3 => 1.4 ===
--- ZODB4/src/zodb/storage/fsdump.py:1.3	Fri Jan 24 18:20:52 2003
+++ ZODB4/src/zodb/storage/fsdump.py	Thu Mar 13 16:32:28 2003
@@ -17,6 +17,8 @@
 from zodb.storage.file \
      import TRANS_HDR, TRANS_HDR_LEN, DATA_HDR, DATA_HDR_LEN
 from zodb.utils import u64
+from zodb.storage.base import splitrefs
+from zodb.storage.tests.base import zodb_unpickle
 
 def fmt(p64):
     # Return a nicely formatted string for a packaged 64-bit value
@@ -74,7 +76,7 @@
         pos = self.file.tell()
         h = self.file.read(DATA_HDR_LEN)
         assert len(h) == DATA_HDR_LEN
-        oid, revid, prev, tloc, vlen, dlen = struct.unpack(DATA_HDR, h)
+        oid, revid, prev, tloc, vlen, nrefs, dlen = struct.unpack(DATA_HDR, h)
         print >> self.dest, "-" * 60
         print >> self.dest, "offset: %d" % pos
         print >> self.dest, "oid: %s" % fmt(oid)
@@ -89,8 +91,15 @@
             print >> self.dest, "non-version data offset: %d" % u64(pnv)
             print >> self.dest, \
                   "previous version data offset: %d" % u64(sprevdata)
+        print >> self.dest, 'numrefs:', nrefs
+        for ref in splitrefs(self.file.read(nrefs * 8)):
+            print >> self.dest, '\t%s' % fmt(refs)
+        # XXX print out the oids?
         print >> self.dest, "len(data): %d" % dlen
-        self.file.read(dlen)
+        data = self.file.read(dlen)
+        # A debugging feature for use with the test suite.
+        if data.startswith("(czodb.storage.tests.minpo\nMinPO\n"):
+            print >> self.dest, "value: %r" % zodb_unpickle(data).value
         if not dlen:
             sbp = self.file.read(8)
             print >> self.dest, "backpointer: %d" % u64(sbp)


=== ZODB4/src/zodb/storage/interfaces.py 1.9 => 1.10 ===
--- ZODB4/src/zodb/storage/interfaces.py:1.9	Thu Mar  6 15:33:52 2003
+++ ZODB4/src/zodb/storage/interfaces.py	Thu Mar 13 16:32:28 2003
@@ -57,7 +57,7 @@
     data.  A load() method can not run at the same time as tpcFinish()
     if it would be possible to read inconsistent data.  XXX Need to
     flesh out the details here.
-    
+
     """
 
     def close():
@@ -109,13 +109,15 @@
     def getSerial(oid):
         """Return the current serial number for oid."""
 
-    def store(oid, serial, data, version, txn):
+    def store(oid, serial, data, refs, version, txn):
         """Store an object and returns a new serial number.
 
         Arguments:
         oid -- the object id, a string
         serial -- the serial number of the revision read by txn, a string
-        data -- the data record, a string
+        data -- a 2-tuple of the data record (string), and the oids of the
+                objects referenced by the this object, as a list
+        refs -- the list of object ids of objects referenced by the data
         version -- the version, a string, typically the empty string
         txn -- the current transaction
 
@@ -135,11 +137,11 @@
         protocol that complicates the return value.  Maybe we can fix that.
         """
 
-    def restore(oid, serial, data, version, prev_txn, txn):
+    def restore(oid, serial, data, refs, version, prev_txn, txn):
         """Store an object with performing consistency checks.
 
         The arguments are the same as store() except for prev_txn.
-        If prev_txn is not None, then prev_txn is the
+        If prev_txn is not None, then prev_txn is the XXX ...?
         """
         pass
 
@@ -182,13 +184,13 @@
         pass
 
 class IUndoStorage(Interface):
-    
+
     def loadSerial(oid, serial):
         """Return data record for revision `serial` of `oid.`
 
         Raises POSKeyError if the revisions is not available.
         """
-    
+
     def undo(txnid, txn):
         pass
 
@@ -215,8 +217,8 @@
     def versionEmpty(version):
         pass
 
-    def versions(max=None): 
-       pass
+    def versions():
+        pass
 
 class IStorageIterator(Interface):
 
@@ -244,7 +246,7 @@
 
         Raises IndexError if there are no more.
         """
-    
+
 class IDataRecord(Interface):
 
     oid = Attribute("oid", "object id")
@@ -259,6 +261,8 @@
                          wrote the data.  The current transaction contains
                          a logical copy of that data.
                          """)
+    refs = Attribute("refs",
+                     """list of object ids referred to by this object""")
 
 class StorageError(POSError):
     """Base class for storage based exceptions."""
@@ -290,4 +294,3 @@
 
 class TransactionTooLargeError(StorageTransactionError):
     """The transaction exhausted some finite storage resource."""
-


=== ZODB4/src/zodb/storage/mapping.py 1.5 => 1.6 ===
--- ZODB4/src/zodb/storage/mapping.py:1.5	Tue Feb 25 13:55:03 2003
+++ ZODB4/src/zodb/storage/mapping.py	Thu Mar 13 16:32:28 2003
@@ -20,6 +20,7 @@
 
 The Mapping storage uses a single data structure to map object ids to
 data.
+
 $Id$
 """
 
@@ -27,9 +28,7 @@
 from zodb import interfaces, utils
 from zodb.storage.base import BaseStorage
 from zodb.storage.interfaces import *
-from zodb.serialize import findrefs
 from zodb.timestamp import TimeStamp
-from zodb.utils import z64
 
 class MappingStorage(BaseStorage):
 
@@ -50,12 +49,12 @@
     def load(self, oid, version):
         self._lock_acquire()
         try:
-            p = self._index[oid]
-            return p[8:], p[:8] # pickle, serial
+            serial, data, refs = self._index[oid]
+            return data, serial
         finally:
             self._lock_release()
 
-    def store(self, oid, serial, data, version, transaction):
+    def store(self, oid, serial, data, refs, version, transaction):
         if transaction is not self._transaction:
             raise StorageTransactionError(self, transaction)
 
@@ -65,22 +64,21 @@
         self._lock_acquire()
         try:
             if self._index.has_key(oid):
-                old = self._index[oid]
-                oserial = old[:8]
+                oserial, odata, orefs = self._index[oid]
                 if serial != oserial:
                     raise interfaces.ConflictError(serials=(oserial, serial))
-
-            self._tindex.append((oid, self._serial + data))
-        finally: self._lock_release()
-
-        return self._serial
+            serial = self._serial
+            self._tindex.append((oid, serial, data, refs))
+        finally:
+            self._lock_release()
+        return serial
 
     def _clear_temp(self):
         self._tindex = []
 
     def _finish(self, tid, user, desc, ext):
-        for oid, p in self._tindex:
-            self._index[oid] = p
+        for oid, serial, data, refs in self._tindex:
+            self._index[oid] = serial, data, refs
         self._ltid = self._serial
 
     def lastTransaction(self):
@@ -89,37 +87,33 @@
     def pack(self, t):
         self._lock_acquire()
         try:
-            # Build an index of *only* those objects reachable from the root.
-            rootl = ["\0\0\0\0\0\0\0\0"]
-            pindex = {}
+            # Build an index of those objects reachable from the root.
+            rootl = [ZERO]
+            packmark = {}
             while rootl:
                 oid = rootl.pop()
-                if pindex.has_key(oid):
+                if packmark.has_key(oid):
                     continue
-                # Scan non-version pickle for references
-                r = self._index[oid]
-                pindex[oid] = r
-                p = r[8:]
-                rootl.extend(findrefs(p))
-
+                # Register this oid and append the objects referenced by this
+                # object to the root search list.
+                rec = self._index[oid]
+                packmark[oid] = rec
+                rootl.extend(rec[3])
             # Now delete any unreferenced entries:
-            for oid in self._index.keys():
-                if not pindex.has_key(oid):
-                    del self._index[oid]
-
+            for oid in index.keys():
+                if not packmark.has_key(oid):
+                    del index[oid]
         finally:
             self._lock_release()
 
     def _splat(self):
         """Spit out a string showing state."""
-        o=[]
-        o.append("Index:")
-        index=self._index
-        keys=index.keys()
+        o = []
+        o.append('Index:')
+        keys = self._index.keys()
         keys.sort()
         for oid in keys:
-            r=index[oid]
-            o.append("  %s: %s, %s" %
+            r = self._index[oid]
+            o.append('  %s: %s, %s' %
                      (utils.u64(oid),TimeStamp(r[:8]),`r[8:]`))
-
         return "\n".join(o)