Esempio n. 1
0
    def verify_undoable(self, cursor, undo_tid):
        """Raise UndoError if it is not safe to undo the specified txn."""
        stmt = """
        SELECT 1
        FROM transaction
        WHERE tid = %(undo_tid)s
            AND packed = %(FALSE)s
        """
        self.runner.run_script_stmt(cursor, stmt, {'undo_tid': undo_tid})
        if not cursor.fetchall():
            raise UndoError("Transaction not found or packed")

        # Rule: we can undo an object if the object's state in the
        # transaction to undo matches the object's current state.
        # If any object in the transaction does not fit that rule,
        # refuse to undo.
        # (Note that this prevents conflict-resolving undo as described
        # by ZODB.tests.ConflictResolution.ConflictResolvingTransUndoStorage.
        # Do people need that? If so, we can probably support it, but it
        # will require additional code.)
        stmt = """
        SELECT prev_os.zoid, current_object.tid
        FROM object_state prev_os
        INNER JOIN object_state cur_os
            ON (prev_os.zoid = cur_os.zoid)
        INNER JOIN current_object
            ON (cur_os.zoid = current_object.zoid
                AND cur_os.tid = current_object.tid)
        WHERE prev_os.tid = %(undo_tid)s
            AND cur_os.md5 != prev_os.md5
        ORDER BY prev_os.zoid
        """
        self.runner.run_script_stmt(cursor, stmt, {'undo_tid': undo_tid})
        if cursor.fetchmany():
            raise UndoError("Some data were modified by a later transaction")

        # Rule: don't allow the creation of the root object to
        # be undone.  It's hard to get it back.
        stmt = """
        SELECT 1
        FROM object_state
        WHERE tid = %(undo_tid)s
            AND zoid = 0
            AND prev_tid = 0
        """
        self.runner.run_script_stmt(cursor, stmt, {'undo_tid': undo_tid})
        if cursor.fetchall():
            raise UndoError("Can't undo the creation of the root object")
Esempio n. 2
0
    def undo(self, cursor, undo_tid, self_tid):
        """Undo a transaction.

        Parameters: "undo_tid", the integer tid of the transaction to undo,
        and "self_tid", the integer tid of the current transaction.

        Returns the list of OIDs undone.
        """
        raise UndoError("Undo is not supported by this storage")
Esempio n. 3
0
 def verify_undoable(self, cursor, undo_tid):
     """Raise UndoError if it is not safe to undo the specified txn."""
     raise UndoError("Undo is not supported by this storage")
Esempio n. 4
0
    def undo(self, undone_tid, txn, tryToResolveConflict):
        txn_context = self._txn_container.get(txn)
        txn_info, txn_ext = self._getTransactionInformation(undone_tid)
        txn_oid_list = txn_info['oids']

        # Regroup objects per partition, to ask a minimum set of storage.
        partition_oid_dict = {}
        for oid in txn_oid_list:
            partition = self.pt.getPartition(oid)
            try:
                oid_list = partition_oid_dict[partition]
            except KeyError:
                oid_list = partition_oid_dict[partition] = []
            oid_list.append(oid)

        # Ask storage the undo serial (serial at which object's previous data
        # is)
        getCellList = self.pt.getCellList
        getCellSortKey = self.cp.getCellSortKey
        getConnForCell = self.cp.getConnForCell
        queue = self._thread_container.queue
        ttid = txn_context['ttid']
        undo_object_tid_dict = {}
        snapshot_tid = p64(u64(self.last_tid) + 1)
        for partition, oid_list in partition_oid_dict.iteritems():
            cell_list = getCellList(partition, readable=True)
            # We do want to shuffle before getting one with the smallest
            # key, so that all cells with the same (smallest) key has
            # identical chance to be chosen.
            shuffle(cell_list)
            storage_conn = getConnForCell(min(cell_list, key=getCellSortKey))
            storage_conn.ask(Packets.AskObjectUndoSerial(
                ttid, snapshot_tid, undone_tid, oid_list),
                             queue=queue,
                             undo_object_tid_dict=undo_object_tid_dict)

        # Wait for all AnswerObjectUndoSerial. We might get OidNotFoundError,
        # meaning that objects in transaction's oid_list do not exist any
        # longer. This is the symptom of a pack, so forbid undoing transaction
        # when it happens.
        try:
            self.waitResponses(queue)
        except NEOStorageNotFoundError:
            self.dispatcher.forget_queue(queue)
            raise UndoError('non-undoable transaction')

        # Send undo data to all storage nodes.
        for oid in txn_oid_list:
            current_serial, undo_serial, is_current = undo_object_tid_dict[oid]
            if is_current:
                data = None
            else:
                # Serial being undone is not the latest version for this
                # object. This is an undo conflict, try to resolve it.
                try:
                    # Load the latest version we are supposed to see
                    data = self.load(oid, current_serial)[0]
                    # Load the version we were undoing to
                    undo_data = self.load(oid, undo_serial)[0]
                except NEOStorageNotFoundError:
                    raise UndoError('Object not found while resolving undo '
                                    'conflict')
                # Resolve conflict
                try:
                    data = tryToResolveConflict(oid, current_serial,
                                                undone_tid, undo_data, data)
                except ConflictError:
                    raise UndoError('Some data were modified by a later ' \
                        'transaction', oid)
                undo_serial = None
            self._store(txn_context, oid, current_serial, data, undo_serial)

        return None, txn_oid_list
Esempio n. 5
0
    def undo(self, undone_tid, txn):
        txn_context = self._txn_container.get(txn)
        txn_info, txn_ext = self._getTransactionInformation(undone_tid)

        # Regroup objects per partition, to ask a minimum set of storage.
        partition_oid_dict = defaultdict(list)
        for oid in txn_info['oids']:
            partition_oid_dict[self.pt.getPartition(oid)].append(oid)

        # Ask storage the undo serial (serial at which object's previous data
        # is)
        getCellList = self.pt.getCellList
        getCellSortKey = self.getCellSortKey
        getConnForNode = self.getStorageConnection
        queue = self._thread_container.queue
        ttid = txn_context.ttid
        undo_object_tid_dict = {}
        snapshot_tid = p64(u64(self.last_tid) + 1)
        kw = {
            'queue': queue,
            'partition_oid_dict': partition_oid_dict,
            'undo_object_tid_dict': undo_object_tid_dict,
        }
        while partition_oid_dict:
            for partition, oid_list in partition_oid_dict.iteritems():
                cell_list = [cell
                    for cell in getCellList(partition, readable=True)
                    # Exclude nodes that may have missed previous resolved
                    # conflicts. For example, if a network failure happened
                    # only between the client and the storage, the latter would
                    # still be readable until we commit.
                    if txn_context.conn_dict.get(cell.getUUID(), 0) is not None]
                storage_conn = getConnForNode(
                    min(cell_list, key=getCellSortKey).getNode())
                storage_conn.ask(Packets.AskObjectUndoSerial(ttid,
                    snapshot_tid, undone_tid, oid_list),
                    partition=partition, **kw)

            # Wait for all AnswerObjectUndoSerial. We might get
            # OidNotFoundError, meaning that objects in transaction's oid_list
            # do not exist any longer. This is the symptom of a pack, so forbid
            # undoing transaction when it happens.
            try:
                self.waitResponses(queue)
            except NEOStorageNotFoundError:
                self.dispatcher.forget_queue(queue)
                raise UndoError('non-undoable transaction')

        # Send undo data to all storage nodes.
        for oid, (current_serial, undo_serial, is_current) in \
                undo_object_tid_dict.iteritems():
            if is_current:
                data = None
            else:
                # Serial being undone is not the latest version for this
                # object. This is an undo conflict, try to resolve it.
                try:
                    # Load the latest version we are supposed to see
                    if current_serial == ttid:
                        # XXX: see TODO below
                        data = txn_context.cache_dict[oid]
                    else:
                        data = self.load(oid, current_serial)[0]
                    # Load the version we were undoing to
                    undo_data = self.load(oid, undo_serial)[0]
                except NEOStorageNotFoundError:
                    raise UndoError('Object not found while resolving undo '
                        'conflict')
                # Resolve conflict
                try:
                    data = txn_context.Storage.tryToResolveConflict(
                        oid, current_serial, undone_tid, undo_data, data)
                except ConflictError:
                    raise UndoError('Some data were modified by a later ' \
                        'transaction', oid)
                undo_serial = None
                # TODO: The situation is similar to deadlock avoidance.
                #       Reenable the cache size limit to avoid OOM when there's
                #       a huge amount conflicting data, and get the data back
                #       from the storage when it's not in cache_dict anymore.
                txn_context.cache_size = - float('inf')
            self._store(txn_context, oid, current_serial, data, undo_serial)

        self.waitStoreResponses(txn_context)
        return None, list(undo_object_tid_dict)