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")
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")
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, 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
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)