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")
def undo(self, undone_tid, txn): 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 getConnForNode = self.cp.getConnForNode 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.involved_nodes.get(cell.getUUID(), 0) < 2 ] 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 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 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, txn_oid_list