def _write_mt(self, mt, txid): """Write out the minitransaction intent to RAMCloud. Uses the table identified by C{txramcloud.tx_table} and the transaction id stored in C{mt}. @param mt: the minitransaction @type mt: L{MiniTransaction} @param txid: the object ID at which to write the minitransaction @type txid: C{int} @return: the version of the object written @rtype: L{int} @raise TransactionExpired: A tombstone is in the way. """ rr = ramcloud.RejectRules(object_exists=True) try: return RAMCloud.write_rr(self, self.tx_table, txid, serialize(mt), rr) except ramcloud.ObjectExistsError: # looks like a tombstone is here raise self.TransactionExpired()
def _apply_op(self, table_id, key, txid, op): """Apply the operation to its masked object. @param table_id: The table containing the possibly masked object. @type table_id: C{int} @param key: The object ID of the possibly masked object. @type key: C{int} @param op: the operation to apply @type op: L{MTOperation} """ read_rr = ramcloud.RejectRules(object_doesnt_exist=True) try: blob, version = RAMCloud.read_rr(self, table_id, key, read_rr) except ramcloud.NoObjectError: # does not exist, so not masked by txid return otxid, otimeout, odata = unpack(blob) if otxid == 0 or otxid != txid: # not masked by txid return # So, txid is indeed masking the object we read. if type(op) == MTOperation: # no op # remove mask / delete seed data = odata elif type(op) == MTWrite: data = op.data elif type(op) == MTDelete: data = None else: raise TxRAMCloud.InconsistencyError("Unknown type in MT: %s" % type(op)) # Now, (data is None) determines whether we need to delete or update the # object at version. update_rr = ramcloud.RejectRules.exactly(version) if data is None: # delete the object at version try: RAMCloud.delete_rr(self, table_id, key, update_rr) except (ramcloud.NoObjectError, ramcloud.VersionError): # we must be racing another client to clean this up return else: # update the object at version with data blob = pack(0, 0, data) try: RAMCloud.write_rr(self, table_id, key, blob, update_rr) except (ramcloud.NoObjectError, ramcloud.VersionError): # we must be racing another client to clean this up return
def _unmask_object(self, table_id, key, txid): """Ensure an object is not masked by a particular transaction. This operation is idempotent. @param table_id: The table containing the possibly masked object. @type table_id: C{int} @param key: The object ID of the possibly masked object. @type key: C{int} @param txid: The transaction which may be masking the object. @type txid: C{int} """ for retry in RetryStrategy(): rr_read = ramcloud.RejectRules(object_doesnt_exist=True) try: blob, version = RAMCloud.read_rr(self, table_id, key, rr_read) except ramcloud.NoObjectError: # the object doesn't exist, so it's most certainly not masked return otxid, otimeout, data = unpack(blob) if otxid == 0 or otxid != txid: # the object is already not masked by this transaction return rr_change = ramcloud.RejectRules(version_gt_given=True, given_version=version) if data is None: # seed object try: RAMCloud.delete_rr(self, table_id, key, rr_change) except ramcloud.VersionError: retry.later() else: # full object blob = pack(0, 0, data) try: RAMCloud.write_rr(self, table_id, key, blob, rr_change) except ramcloud.VersionError: retry.later()
def _delete_tombstone(self, txid): """Ensure a tombstone has been deleted from RAMCloud. @warning: No one should ever commit a transaction under txid following this operation. @param txid: the transaction id of the tombstone to delete @type txid: C{int} """ rr = ramcloud.RejectRules() RAMCloud.delete_rr(self, self.tx_table, txid, rr)
def _write_unsafe(self, table_id, key, blob, user_reject_rules): assert not user_reject_rules.object_doesnt_exist assert not user_reject_rules.object_exists assert not user_reject_rules.version_gt_given for retry in RetryStrategy(): try: # self.read_rr() makes sure the object is not masked version = self.read_rr(table_id, key, user_reject_rules)[1] except ramcloud.NoObjectError: # If there was no object, we demand that there be no object. reject_rules = ramcloud.RejectRules(object_exists=True) else: # If there was an object, we demand that there be either no # object or this particular version. reject_rules = ramcloud.RejectRules(version_gt_given=True, given_version=version) try: return RAMCloud.write_rr(self, table_id, key, blob, reject_rules) except (ramcloud.ObjectExistsError, ramcloud.VersionError): retry.later()
def _delete_unsafe(self, table_id, key, user_reject_rules): # - Throws L{ramcloud.NoObjectError} if user_reject_rules specifies # object_doesnt_exist and there is no object to be deleted. # - Doesn't throw L{ramcloud.ObjectExists}. # - Throws L{ramcloud.VersionError} if user_reject_rules specifies # version_eq_given and the version read matches the version given # or is older than the version given. assert not user_reject_rules.object_exists assert not user_reject_rules.version_gt_given for retry in RetryStrategy(): try: # self.read_rr() makes sure there is object is not masked version = self.read_rr(table_id, key, user_reject_rules)[1] except (ramcloud.VersionError): raise except ramcloud.NoObjectError: # If there was no object, we're done. if user_reject_rules.object_doesnt_exist: raise else: return # We can stop worrying about the user's given version now: # If user_reject_rules.version_eq_given, there is a given version # that we must reject. But if we've made it this far, the version # we read is greater than user_reject_rules.given_version. # However, we still have to worry about NoObjectError iff # user_reject_rules.doesnt_exist. # There was an object when we called read. We're definitely good if # we delete the exact version we read. If there is no object to # delete now, we want our delete rejected iff user_reject_rules has # object_doesnt_exist. reject_rules = ramcloud.RejectRules( object_doesnt_exist=user_reject_rules.object_doesnt_exist, version_gt_given=True, given_version=version) try: return RAMCloud.delete_rr(self, table_id, key, reject_rules) except ramcloud.NoObjectError: if user_reject_rules.object_doesnt_exist: raise else: return except ramcloud.VersionError: retry.later()
def _delete_mt(self, txid, version): """Ensure a minitransaction intent has been deleted from RAMCloud. @precondition: No references exist to C{txid} in the RAMCloud. @param txid: the transaction id of the minitransaction to delete @type txid: C{int} @param version: the version number of the mt when it was read @type version: C{int} """ rr = ramcloud.RejectRules(version_gt_given=True, given_version=version) try: RAMCloud.delete_rr(self, self.tx_table, txid, rr) except ramcloud.VersionError: raise self.InconsistencyError("txid has been modified")
def _write_tombstone(self, txid): """Force a transaction to abort. Writes a tombstone at txid if no object exists there. @param txid: The ID of the transaction to abort. @type txid: C{int} @return: The version number of the tombstone written, or C{None} if an object already exists at txid. @rtype: C{int} or C{None} """ rr = ramcloud.RejectRules(object_exists=True) blob = serialize(Tombstone()) try: return RAMCloud.write_rr(self, self.tx_table, txid, blob, rr) except ramcloud.ObjectExistsError: return None
def _clean(self, table_id, key, txid, timeout): """Clean a mask off of an object. This method won't necessarily do anything productive every time it's called, so it should be called inside a retry loop. @param table_id: The table containing the masked object. @type table_id: C{int} @param key: The object ID of the masked object. @type key: C{int} @param txid: The transaction which is masking the object. @type txid: C{int} @param timeout: The time at which the mask expires. @type timeout: C{int} """ rr = ramcloud.RejectRules(object_doesnt_exist=True) try: mtdata, mtversion = RAMCloud.read_rr(self, self.tx_table, txid, rr) except ramcloud.NoObjectError: # txid doesn't exist, so wait or write tombstone if time.time() > timeout: # try to write a tombstone if self._write_tombstone(txid) is not None: self._unmask_object(table_id, key, txid) # caller will retry if the object still needs cleaning else: try: mt = unserialize(mtdata) except pickle.BadPickleGet: raise self.InconsistencyError("Not a pickled object") if type(mt) == Tombstone: self._unmask_object(table_id, key, txid) elif type(mt) == MiniTransaction: self._finish_mt(mt, txid, mtversion) else: raise self.InconsistencyError("Not a MiniTransaction or " + "Tombstone")
def _read_rr(self, table_id, key, user_reject_rules): # We can't reject for object_exists in RAMCloud.read_rr because of seed # objects, so we handle it later. reject_rules = ramcloud.RejectRules(object_doesnt_exist=True, version_eq_given=user_reject_rules.version_eq_given, version_gt_given=user_reject_rules.version_gt_given, given_version=user_reject_rules.given_version) start = time.time() for retry in RetryStrategy(): blob, version = RAMCloud.read_rr(self, table_id, key, reject_rules) txid, timeout, data = unpack(blob) if txid: # there's plenty of room for optimizing round trips here timeout = min(start + 1, timeout) self._clean(table_id, key, txid, timeout) retry.later() else: # not masked if user_reject_rules.object_exists: raise ramcloud.ObjectExistsError() else: return data, version
def _mask_object(self, table_id, key, txid, timeout, user_reject_rules): """Mask an object by a particular transaction. If the object is already masked, this method will wait for it to become unmasked. @warning: Unlike L{_unmask_object}, this operation is B{not} idempotent. @param table_id: The table containing the object to mask. @type table_id: C{int} @param key: The object ID to mask. @type key: C{int} @param txid: The transaction which will mask the object. @type txid: C{int} @param timeout: The time at which to expire the mask. @type timeout: C{int} @param user_reject_rules: The reasons for which to abort masking the object. @type user_reject_rules: L{ramcloud.RejectRules} @raise Exception: If the object could not be masked because of C{user_reject_rules}. Specifically, this is one of: L{ramcloud.NoObjectError}, L{ramcloud.ObjectExistsError}, or L{ramcloud.VersionError}. Additionally, the exception will have the attributes C{table} and C{oid} set to parameter C{table_id} and C{key}. @return: The new version of the newly masked object. @rtype: C{int} """ for retry in RetryStrategy(): # self.read_rr() requires object_doesnt_exist to be set, but we can # still use the user's other reject rules. rr_read = ramcloud.RejectRules( object_doesnt_exist=True, object_exists=user_reject_rules.object_exists, version_eq_given=user_reject_rules.version_eq_given, version_gt_given=user_reject_rules.version_gt_given, given_version=user_reject_rules.given_version) try: data, version = self.read_rr(table_id, key, rr_read) except (ramcloud.ObjectExistsError, ramcloud.VersionError) as e: # The user asked for a reject e.table = table_id e.oid = key raise except ramcloud.NoObjectError as e: if user_reject_rules.object_doesnt_exist: # The user asked for a reject e.table = table_id e.oid = key raise else: # The user didn't ask for a reject, but the object doesn't # exist. We need to create a seed and must fail if an # object is already present. rr_write = ramcloud.RejectRules(object_exists=True) blob = pack(txid, timeout, data=None) else: # There was an object when we called read. It has no mask, and # it isn't a seed. We need to write over the exact version we # read. rr_write = ramcloud.RejectRules.exactly(version) blob = pack(txid, timeout, data) # Now, blob and rr_write are set and we can attempt a write. try: return RAMCloud.write_rr(self, table_id, key, blob, rr_write) except (ramcloud.ObjectExistsError, ramcloud.NoObjectError, ramcloud.VersionError): retry.later()