Exemplo n.º 1
0
 def undo(i):
     info = s.undoInfo()
     t = TransactionMetaData()
     s.tpc_begin(t)
     base = i * OBJECTS + i
     for j in range(OBJECTS):
         tid = info[base + j]['id']
         s.undo(tid, t)
     s.tpc_vote(t)
     s.tpc_finish(t)
Exemplo n.º 2
0
    def checkWriteMethods(self):
        self._make_readonly()
        self.assertRaises(ReadOnlyError, self._storage.new_oid)
        t = TransactionMetaData()
        self.assertRaises(ReadOnlyError, self._storage.tpc_begin, t)

        self.assertRaises(ReadOnlyError, self._storage.store, b'\000' * 8,
                          None, b'', '', t)

        self.assertRaises(ReadOnlyError, self._storage.undo, b'\000' * 8, t)
Exemplo n.º 3
0
 def checkWriteAfterAbort(self):
     oid = self._storage.new_oid()
     t = TransactionMetaData()
     self._storage.tpc_begin(t)
     self._storage.store(oid, ZERO, zodb_pickle(MinPO(5)), '', t)
     # Now abort this transaction
     self._storage.tpc_abort(t)
     # Now start all over again
     oid = self._storage.new_oid()
     self._dostore(oid=oid, data=MinPO(6))
Exemplo n.º 4
0
Arquivo: util.py Projeto: yws/ZODB
def store(storage, oid, value='x', serial=ZODB.utils.z64):
    if not isinstance(oid, bytes):
        oid = ZODB.utils.p64(oid)
    if not isinstance(serial, bytes):
        serial = ZODB.utils.p64(serial)
    t = TransactionMetaData()
    storage.tpc_begin(t)
    storage.store(oid, serial, value, '', t)
    storage.tpc_vote(t)
    storage.tpc_finish(t)
Exemplo n.º 5
0
    def checkTransactionIdIncreases(self):
        import time
        from ZODB.utils import newTid
        from ZODB.TimeStamp import TimeStamp
        t = TransactionMetaData()
        self._storage.tpc_begin(t)
        self._storage.tpc_vote(t)
        self._storage.tpc_finish(t)

        # Add a fake transaction
        transactions = self._storage._transactions
        self.assertEqual(1, len(transactions))
        fake_timestamp = b'zzzzzzzy'  # the year 5735 ;-)
        transactions[fake_timestamp] = transactions.values()[0]

        # Verify the next transaction comes after the fake transaction
        t = TransactionMetaData()
        self._storage.tpc_begin(t)
        self.assertEqual(self._storage._tid, b'zzzzzzzz')
Exemplo n.º 6
0
 def __read_current_and_lock(self, storage, read_current_oid, lock_oid, tid,
                             begin=True, tx=None):
     tx = tx if tx is not None else TransactionMetaData()
     if begin:
         storage.tpc_begin(tx)
     if read_current_oid is not None:
         storage.checkCurrentSerialInTransaction(read_current_oid, tid, tx)
     storage.store(lock_oid, tid, b'bad pickle2', '', tx)
     storage.tpc_vote(tx)
     return tx
Exemplo n.º 7
0
    def checkanalyze(self):
        import types
        from BTrees.OOBTree import OOBTree
        from ZODB.scripts import analyze

        # Set up a module to act as a broken import
        module_name = 'brokenmodule'
        module = types.ModuleType(module_name)
        sys.modules[module_name] = module

        class Broken(MinPO):
            __module__ = module_name

        module.Broken = Broken

        oids = [[self._storage.new_oid(), None] for i in range(3)]

        def store(i, data):
            oid, revid = oids[i]
            self._storage.store(oid, revid, data, "", t)

        for i in range(2):
            t = TransactionMetaData()
            self._storage.tpc_begin(t)

            # sometimes data is in this format
            store(0, dumps(OOBTree, _protocol))
            # and it could be from a broken module
            store(1, dumps(Broken, _protocol))
            # but mostly it looks like this
            store(2, zodb_pickle(MinPO(2)))

            self._storage.tpc_vote(t)
            tid = self._storage.tpc_finish(t)
            for oid_revid in oids:
                oid_revid[1] = tid

        # now break the import of the Broken class
        del sys.modules[module_name]

        # from ZODB.scripts.analyze.analyze
        fsi = self._storage.iterator()
        rep = analyze.Report()
        for txn in fsi:
            analyze.analyze_trans(rep, txn)

        # from ZODB.scripts.analyze.report
        typemap = sorted(rep.TYPEMAP.keys())
        cumpct = 0.0
        for t in typemap:
            pct = rep.TYPESIZE[t] * 100.0 / rep.DBYTES
            cumpct += pct

        self.assertAlmostEqual(cumpct, 100.0, 0,
                               "Failed to analyze some records")
Exemplo n.º 8
0
    def run(self):
        tname = self.getName()
        testcase = self.testcase

        # Create client connections to each server
        clients = self.clients
        for i in range(len(testcase.addr)):
            c = testcase.openClientStorage(addr=testcase.addr[i])
            c.__name = "C%d" % i
            clients.append(c)

        for i in range(testcase.ntrans):
            # Because we want a transaction spanning all storages,
            # we can't use _dostore().  This is several _dostore() calls
            # expanded in-line (mostly).

            # Create oid->serial mappings
            for c in clients:
                c.__oids = []
                c.__serials = {}

            # Begin a transaction
            t = TransactionMetaData()
            for c in clients:
                #print("%s.%s.%s begin" % (tname, c.__name, i))
                c.tpc_begin(t)

            for j in range(testcase.nobj):
                for c in clients:
                    # Create and store a new object on each server
                    oid = c.new_oid()
                    c.__oids.append(oid)
                    data = MinPO("%s.%s.t%d.o%d" % (tname, c.__name, i, j))
                    #print(data.value)
                    data = zodb_pickle(data)
                    c.store(oid, ZERO, data, '', t)

            # Vote on all servers and handle serials
            for c in clients:
                #print("%s.%s.%s vote" % (tname, c.__name, i))
                c.tpc_vote(t)

            # Finish on all servers
            for c in clients:
                #print("%s.%s.%s finish\n" % (tname, c.__name, i))
                c.tpc_finish(t)

            for c in clients:
                # Check that we got serials for all oids
                for oid in c.__oids:
                    testcase.failUnless(oid in c.__serials)
                # Check that we got serials for no other oids
                for oid in c.__serials.keys():
                    testcase.failUnless(oid in c.__oids)
Exemplo n.º 9
0
    def checkTwoObjectUndoAtOnce(self):
        # Convenience
        eq = self.assertEqual
        unless = self.assertTrue
        p30, p31, p32, p50, p51, p52 = map(
            zodb_pickle, map(MinPO, (30, 31, 32, 50, 51, 52)))
        oid1 = self._storage.new_oid()
        oid2 = self._storage.new_oid()
        # Store two objects in the same transaction
        tid = self._multi_obj_transaction([
            (oid1, ZERO, p30),
            (oid2, ZERO, p50),
        ])
        # Update those same two objects
        tid = self._multi_obj_transaction([
            (oid1, tid, p31),
            (oid2, tid, p51),
        ])
        # Update those same two objects
        tid = self._multi_obj_transaction([
            (oid1, tid, p32),
            (oid2, tid, p52),
        ])
        # Make sure the objects have the current value
        data, revid1 = load_current(self._storage, oid1)
        eq(zodb_unpickle(data), MinPO(32))
        data, revid2 = load_current(self._storage, oid2)
        eq(zodb_unpickle(data), MinPO(52))
        # Now attempt to undo the transaction containing two objects
        info = self._storage.undoInfo()
        tid = info[0]['id']
        tid1 = info[1]['id']
        t = TransactionMetaData()
        oids = self._begin_undos_vote(t, tid, tid1)
        serial = self._storage.tpc_finish(t)
        # We may get the finalization stuff called an extra time,
        # depending on the implementation.
        if serial is None:
            self.assertEqual(oids, {oid1, oid2})
        data, revid1 = load_current(self._storage, oid1)
        eq(zodb_unpickle(data), MinPO(30))
        data, revid2 = load_current(self._storage, oid2)
        eq(zodb_unpickle(data), MinPO(50))

        # Now try to undo the one we just did to undo, whew
        info = self._storage.undoInfo()
        self._undo(info[0]['id'], [oid1, oid2])
        data, revid1 = load_current(self._storage, oid1)
        eq(zodb_unpickle(data), MinPO(32))
        data, revid2 = load_current(self._storage, oid2)
        eq(zodb_unpickle(data), MinPO(52))
        self._iterate()
Exemplo n.º 10
0
 def helper(tid, revid, x):
     data = zodb_pickle(MinPO(x))
     t = TransactionMetaData()
     try:
         self._storage.tpc_begin(t, p64(tid))
         self._storage.store(oid, revid, data, '', t)
         # Finish the transaction
         self._storage.tpc_vote(t)
         newrevid = self._storage.tpc_finish(t)
     except:
         self._storage.tpc_abort(t)
         raise
     return newrevid
Exemplo n.º 11
0
 def _undo(self, tid, expected_oids=None, note=None):
     # Undo a tid that affects a single object (oid).
     # This is very specialized.
     t = TransactionMetaData()
     t.note(note or u"undo")
     self._storage.tpc_begin(t)
     undo_result = self._storage.undo(tid, t)
     vote_result = self._storage.tpc_vote(t)
     if expected_oids is not None:
         oids = set(undo_result[1]) if undo_result else set()
         if vote_result:
             oids.update(vote_result)
         self.assertEqual(oids, set(expected_oids))
     return self._storage.tpc_finish(t)
Exemplo n.º 12
0
    def checkBasics(self):
        self.assertEqual(self._storage.lastTransaction(), ZERO)

        t = TransactionMetaData()
        self._storage.tpc_begin(t)
        self.assertRaises(POSException.StorageTransactionError,
                          self._storage.tpc_begin, t)
        # Aborting is easy
        self._storage.tpc_abort(t)
        # Test a few expected exceptions when we're doing operations giving a
        # different Transaction object than the one we've begun on.
        self._storage.tpc_begin(t)
        self.assertRaises(POSException.StorageTransactionError,
                          self._storage.store, ZERO, ZERO, b'', '',
                          TransactionMetaData())

        self.assertRaises(POSException.StorageTransactionError,
                          self._storage.store, ZERO, 1, b'2', '',
                          TransactionMetaData())

        self.assertRaises(POSException.StorageTransactionError,
                          self._storage.tpc_vote, TransactionMetaData())
        self._storage.tpc_abort(t)
Exemplo n.º 13
0
 def checkSerialIsNoneForInitialRevision(self):
     eq = self.assertEqual
     oid = self._storage.new_oid()
     txn = TransactionMetaData()
     self._storage.tpc_begin(txn)
     # Use None for serial.  Don't use _dostore() here because that coerces
     # serial=None to serial=ZERO.
     self._storage.store(oid, None, zodb_pickle(MinPO(11)), '', txn)
     self._storage.tpc_vote(txn)
     newrevid = self._storage.tpc_finish(txn)
     data, revid = utils.load_current(self._storage, oid)
     value = zodb_unpickle(data)
     eq(value, MinPO(11))
     eq(revid, newrevid)
Exemplo n.º 14
0
    def checkTwoObjectUndo(self):
        eq = self.assertEqual
        # Convenience
        p31, p32, p51, p52 = map(zodb_pickle, map(MinPO, (31, 32, 51, 52)))
        oid1 = self._storage.new_oid()
        oid2 = self._storage.new_oid()
        revid1 = revid2 = ZERO
        # Store two objects in the same transaction
        t = TransactionMetaData()
        self._storage.tpc_begin(t)
        self._storage.store(oid1, revid1, p31, '', t)
        self._storage.store(oid2, revid2, p51, '', t)
        # Finish the transaction
        self._storage.tpc_vote(t)
        tid = self._storage.tpc_finish(t)
        # Update those same two objects
        t = TransactionMetaData()
        self._storage.tpc_begin(t)
        self._storage.store(oid1, tid, p32, '', t)
        self._storage.store(oid2, tid, p52, '', t)
        # Finish the transaction
        self._storage.tpc_vote(t)
        self._storage.tpc_finish(t)
        # Make sure the objects have the current value
        data, revid1 = load_current(self._storage, oid1)
        eq(zodb_unpickle(data), MinPO(32))
        data, revid2 = load_current(self._storage, oid2)
        eq(zodb_unpickle(data), MinPO(52))

        # Now attempt to undo the transaction containing two objects
        info = self._storage.undoInfo()
        self._undo(info[0]['id'], [oid1, oid2])
        data, revid1 = load_current(self._storage, oid1)
        eq(zodb_unpickle(data), MinPO(31))
        data, revid2 = load_current(self._storage, oid2)
        eq(zodb_unpickle(data), MinPO(51))
        self._iterate()
Exemplo n.º 15
0
 def checkStoreBumpsOid(self):
     # If .store() is handed an oid bigger than the storage knows
     # about already, it's crucial that the storage bump its notion
     # of the largest oid in use.
     t = TransactionMetaData()
     self._storage.tpc_begin(t)
     giant_oid = b'\xee' * 8
     # Store an object.
     # oid, serial, data, version, transaction
     r1 = self._storage.store(giant_oid, b'\0'*8, b'data', b'', t)
     # Finish the transaction.
     r2 = self._storage.tpc_vote(t)
     self._storage.tpc_finish(t)
     # Before ZODB 3.2.6, this failed, with ._oid == z64.
     self.assertEqual(self._storage._oid, giant_oid)
Exemplo n.º 16
0
 def _initroot(self):
     try:
         load_current(self._storage, ZERO)
     except KeyError:
         from ZODB.Connection import TransactionMetaData
         file = BytesIO()
         p = Pickler(file, _protocol)
         p.dump((PersistentMapping, None))
         p.dump({'_container': {}})
         t = TransactionMetaData()
         t.description = u'initial database creation'
         self._storage.tpc_begin(t)
         self._storage.store(ZERO, None, file.getvalue(), '', t)
         self._storage.tpc_vote(t)
         self._storage.tpc_finish(t)
Exemplo n.º 17
0
 def checkDisconnectedAbort(self):
     self._storage = self.openClientStorage()
     self._dostore()
     oids = [self._storage.new_oid() for i in range(5)]
     txn = TransactionMetaData()
     self._storage.tpc_begin(txn)
     for oid in oids:
         data = zodb_pickle(MinPO(oid))
         self._storage.store(oid, None, data, '', txn)
     self.shutdownServer()
     with short_timeout(self):
         self.assertRaises(ClientDisconnected, self._storage.tpc_vote, txn)
     self.startServer(create=0)
     self._storage.tpc_abort(txn)
     self._dostore()
Exemplo n.º 18
0
    def checkIteratorGCStorageTPCAborting(self):
        # The odd little jig we do below arises from the fact that the
        # CS iterator may not be constructed right away if the CS is wrapped.
        # We need to actually do some iteration to get the iterator created.
        # We do a store to make sure the iterator isn't exhausted right away.
        self._dostore()
        six.advance_iterator(self._storage.iterator())

        iid = list(self._storage._iterator_ids)[0]

        t = TransactionMetaData()
        self._storage._iterators._last_gc = -1
        self._storage.tpc_begin(t)
        self._storage.tpc_abort(t)
        self._assertIteratorIdsEmpty()
        self.assertRaises(KeyError, self._storage._call, 'iterator_next', iid)
Exemplo n.º 19
0
 def checkRestoreBumpsOid(self):
     # As above, if .restore() is handed an oid bigger than the storage
     # knows about already, it's crucial that the storage bump its notion
     # of the largest oid in use.  Because copyTransactionsFrom(), and
     # ZRS recovery, use the .restore() method, this is plain critical.
     t = TransactionMetaData()
     self._storage.tpc_begin(t)
     giant_oid = b'\xee' * 8
     # Store an object.
     # oid, serial, data, version, prev_txn, transaction
     r1 = self._storage.restore(giant_oid, b'\0'*8, b'data', b'', None, t)
     # Finish the transaction.
     r2 = self._storage.tpc_vote(t)
     self._storage.tpc_finish(t)
     # Before ZODB 3.2.6, this failed, with ._oid == z64.
     self.assertEqual(self._storage._oid, giant_oid)
Exemplo n.º 20
0
    def checkIteratorGCStorageDisconnect(self):

        # The odd little jig we do below arises from the fact that the
        # CS iterator may not be constructed right away if the CS is wrapped.
        # We need to actually do some iteration to get the iterator created.
        # We do a store to make sure the iterator isn't exhausted right away.
        self._dostore()
        six.advance_iterator(self._storage.iterator())

        iid = list(self._storage._iterator_ids)[0]
        t = TransactionMetaData()
        self._storage.tpc_begin(t)
        # Show that after disconnecting, the client side GCs the iterators
        # as well. I'm calling this directly to avoid accidentally
        # calling tpc_abort implicitly.
        self._storage.notify_disconnected()
        self.assertEquals(0, len(self._storage._iterator_ids))
Exemplo n.º 21
0
 def checkFlushAfterTruncate(self, fail=False):
     r0 = self._dostore(z64)
     storage = self._storage
     t = TransactionMetaData()
     storage.tpc_begin(t)
     storage.store(z64, r0, b'foo', b'', t)
     storage.tpc_vote(t)
     # Read operations are done with separate 'file' objects with their
     # own buffers: here, the buffer also includes voted data.
     load_current(storage, z64)
     # This must invalidate all read buffers.
     storage.tpc_abort(t)
     self._dostore(z64, r0, b'bar', 1)
     # In the case that read buffers were not invalidated, return value
     # is based on what was cached during the first load.
     self.assertEqual(load_current(storage, z64)[0],
                      b'foo' if fail else b'bar')
Exemplo n.º 22
0
    def checkAbortAfterVote(self):
        oid1 = self._storage.new_oid()
        revid1 = self._dostore(oid=oid1, data=MinPO(-2))
        oid = self._storage.new_oid()
        t = TransactionMetaData()
        self._storage.tpc_begin(t)
        self._storage.store(oid, ZERO, zodb_pickle(MinPO(5)), '', t)
        # Now abort this transaction
        self._storage.tpc_vote(t)
        self._storage.tpc_abort(t)
        # Now start all over again
        oid = self._storage.new_oid()
        revid = self._dostore(oid=oid, data=MinPO(6))

        for oid, revid in [(oid1, revid1), (oid, revid)]:
            data, _revid = utils.load_current(self._storage, oid)
            self.assertEqual(revid, _revid)
Exemplo n.º 23
0
    def checkTimeout(self):
        self._storage = storage = self.openClientStorage()
        txn = TransactionMetaData()
        storage.tpc_begin(txn)
        storage.tpc_vote(txn)
        time.sleep(2)
        with short_timeout(self):
            self.assertRaises(ClientDisconnected, storage.tpc_finish, txn)

        # Make sure it's logged as CRITICAL
        for line in open("server.log"):
            if (('Transaction timeout after' in line)
                    and ('CRITICAL ZEO.StorageServer' in line)):
                break
        else:
            self.assert_(False, 'bad logging')

        storage.close()
Exemplo n.º 24
0
 def checkIterationIntraTransaction(self):
     # XXX: This test overrides the broken version from
     # IteratorStorage; prior to
     # https://github.com/zopefoundation/ZODB/pull/281 it passed a
     # native str, not bytes, as the previous tid.
     oid = self._storage.new_oid()
     t = TransactionMetaData()
     data = zodb_pickle(MinPO(0))
     try:
         self._storage.tpc_begin(t)
         self._storage.store(oid, RevisionStorage.ZERO, data, '', t)
         self._storage.tpc_vote(t)
         # Don't do tpc_finish yet
         it = self._storage.iterator()
         for x in it:
             self.assertIsNotNone(x)
     finally:
         self._storage.tpc_finish(t)
Exemplo n.º 25
0
    def checkUndoUnresolvable(self):
        # This test is based on checkNotUndoable in the
        # TransactionalUndoStorage test suite.  Except here, conflict
        # resolution should allow us to undo the transaction anyway.

        obj = PCounter2()
        obj.inc()
        oid = self._storage.new_oid()
        revid_a = self._dostore(oid, data=obj)
        obj.inc()
        revid_b = self._dostore(oid, revid=revid_a, data=obj)
        obj.inc()
        revid_c = self._dostore(oid, revid=revid_b, data=obj)
        # Start the undo
        info = self._storage.undoInfo()
        tid = info[1]['id']
        t = TransactionMetaData()
        self.assertRaises(UndoError, self._begin_undos_vote, t, tid)
        self._storage.tpc_abort(t)
Exemplo n.º 26
0
    def dostore(self, i):
        data = zodb_pickle(MinPO((self.getName(), i)))
        t = TransactionMetaData()
        oid = self.oid()
        self.pause()

        self.storage.tpc_begin(t)
        self.pause()

        # Always create a new object, signified by None for revid
        self.storage.store(oid, None, data, '', t)
        self.pause()

        self.storage.tpc_vote(t)
        self.pause()

        revid = self.storage.tpc_finish(t)
        self.pause()
        self.oids[oid] = revid
Exemplo n.º 27
0
def main():
    if len(sys.argv) not in (3, 4):
        sys.stderr.write("Usage: timeout.py address delay [storage-name]\n" %
                         sys.argv[0])
        sys.exit(2)

    hostport = sys.argv[1]
    delay = float(sys.argv[2])
    if sys.argv[3:]:
        name = sys.argv[3]
    else:
        name = "1"

    if "/" in hostport:
        address = hostport
    else:
        if ":" in hostport:
            i = hostport.index(":")
            host, port = hostport[:i], hostport[i+1:]
        else:
            host, port = "", hostport
        port = int(port)
        address = (host, port)

    print("Connecting to %s..." % repr(address))
    storage = ClientStorage(address, name)
    print("Connected.  Now starting a transaction...")

    oid = storage.new_oid()
    revid = ZERO
    data = MinPO("timeout.py")
    pickled_data = zodb_pickle(data)
    t = TransactionMetaData()
    t.user = "******"
    storage.tpc_begin(t)
    storage.store(oid, revid, pickled_data, '', t)
    print("Stored.  Now voting...")
    storage.tpc_vote(t)

    print("Voted; now sleeping %s..." % delay)
    time.sleep(delay)
    print("Done.")
Exemplo n.º 28
0
    def _dostore(self,
                 oid=None,
                 revid=None,
                 data=None,
                 already_pickled=0,
                 user=None,
                 description=None):
        """Do a complete storage transaction.  The defaults are:

         - oid=None, ask the storage for a new oid
         - revid=None, use a revid of ZERO
         - data=None, pickle up some arbitrary data (the integer 7)

        Returns the object's new revision id.
        """
        if oid is None:
            oid = self._storage.new_oid()
        if revid is None:
            revid = ZERO
        if data is None:
            data = MinPO(7)
        if type(data) == int:
            data = MinPO(data)
        if not already_pickled:
            data = zodb_pickle(data)
        # Begin the transaction
        t = TransactionMetaData()
        if user is not None:
            t.user = user
        if description is not None:
            t.description = description
        try:
            self._storage.tpc_begin(t)
            # Store an object
            r1 = self._storage.store(oid, revid, data, '', t)
            # Finish the transaction
            r2 = self._storage.tpc_vote(t)
            revid = self._storage.tpc_finish(t)
        except:
            self._storage.tpc_abort(t)
            raise
        return revid
Exemplo n.º 29
0
    def checkUndoConflictResolution(self):
        # This test is based on checkNotUndoable in the
        # TransactionalUndoStorage test suite.  Except here, conflict
        # resolution should allow us to undo the transaction anyway.

        obj = PCounter()
        obj.inc()
        oid = self._storage.new_oid()
        revid_a = self._dostore(oid, data=obj)
        obj.inc()
        revid_b = self._dostore(oid, revid=revid_a, data=obj)
        obj.inc()
        revid_c = self._dostore(oid, revid=revid_b, data=obj)
        # Start the undo
        info = self._storage.undoInfo()
        tid = info[1]['id']
        t = TransactionMetaData()
        self._storage.tpc_begin(t)
        self._storage.undo(tid, t)
        self._storage.tpc_vote(t)
        self._storage.tpc_finish(t)
Exemplo n.º 30
0
    def test_check_refs_missing_after_prepack(self):
        from ZODB.Connection import TransactionMetaData
        expect_oids = self._create_initial_state()

        #         0    1
        #  T2: root -> A
        #          \-> B -> D -> C -> E
        #              2    4    3    5
        def hook(conn):
            conn.root.B['D']['C']['E'] = PersistentMapping()


        self._mutate_state(expect_oids, save_B=True, before_commit=hook)

        # Even if pack_gc is off, we find the references.
        self._storage._options.pack_gc = False

        self._storage.pack(None, referencesf, prepack_only=True)

        #         0    1
        #  T2: root -> A
        #          \-> B ->   -> C -> E
        #              2    4    3    5
        _state, tid = self._storage.load(expect_oids['D'], '')

        txn_meta = TransactionMetaData()
        self._storage.tpc_begin(txn_meta)
        self._storage.deleteObject(expect_oids['D'], tid, txn_meta)
        self._storage.tpc_vote(txn_meta)
        self._storage.tpc_finish(txn_meta)

        missing = self._storage.pack(None, referencesf, check_refs=True, skip_prepack=True)
        self.assertTrue(missing)
        # TODO: Define this data structure.
        self.assertEqual([(2, 4)], missing)

        missing = self._storage.pack(None, referencesf, check_refs=True)
        self.assertTrue(missing)
        self.assertEqual([(2, 4)], missing)