def __init__(self, store, subject, lease_time=None, token=None): """Ensure we can take a lock on this subject.""" super(SqliteTransaction, self).__init__(store, utils.SmartUnicode(subject), lease_time=lease_time, token=token) if lease_time is None: lease_time = config_lib.CONFIG["Datastore.transaction_timeout"] self.lock_token = thread.get_ident() sqlite_connection = store.cache.Get(self.subject) # We first check if there is a lock on the subject. # Next we set our lease time and lock_token as identification. with sqlite_connection: locked_until, stored_token = sqlite_connection.GetLock(subject) # This is currently locked by another thread. if locked_until and time.time() < float(locked_until): raise data_store.TransactionError("Subject %s is locked" % subject) # Subject is not locked, we take a lease on it. self.expires = time.time() + lease_time sqlite_connection.SetLock(subject, self.expires, self.lock_token) # Check if the lock stuck. If the stored token is not ours # then probably someone was able to grab it before us. locked_until, stored_token = sqlite_connection.GetLock(subject) if stored_token != self.lock_token: raise data_store.TransactionError("Unable to lock subject %s" % subject) self.locked = True
def __init__(self, store, subject, lease_time=None, token=None): """Ensure we can take a lock on this subject.""" self.store = store self.token = token self.subject = utils.SmartUnicode(subject) self.object_id = objectid.ObjectId( hashlib.sha256(utils.SmartStr(self.subject)).digest()[:12]) self.to_set = {} self.to_delete = set() if lease_time is None: lease_time = config_lib.CONFIG["Datastore.transaction_timeout"] self.expires = time.time() + lease_time self.document = self.store.latest_collection.find_and_modify( query={ "_id": self.object_id, "expires": { "$lt": time.time() } }, update=dict(_id=self.object_id, expires=self.expires), upsert=False, new=True) if self.document: # Old transaction expired and we hold a lock now: self.locked = True return # Maybe the lock did not exist yet. To create it, we use a lock to reduce # the chance of deleting some other lock created at the same time. Note that # there still exists a very small race if this happens in multiple processes # at the same time. with self.lock_creation_lock: document = self.store.latest_collection.find( {"_id": self.object_id}) if not document.count(): self.UpdateLease(lease_time) cursor = self.store.latest_collection.find( {"_id": self.object_id}) if cursor.count() != 1: self._DeleteLock() logging.warn("Multiple lock rows for %s", subject) raise data_store.TransactionError( "Error while locking %s." % subject) self.document = cursor.next() if self.document["expires"] != self.expires: raise data_store.TransactionError("Subject %s is locked" % subject) # We hold a lock now: self.locked = True return raise data_store.TransactionError("Subject %s is locked" % subject)
def _CheckForLock(self): """Checks that the lock has stuck.""" query = ("SELECT lock_expiration, lock_owner FROM locks " "WHERE subject_hash=unhex(md5(%s))") args = [self.subject] rows = self.store.ExecuteQuery(query, args) for row in rows: # We own this lock now. if (row["lock_expiration"] == self.expires_lock and row["lock_owner"] == self.lock_token): return else: # Someone else owns this lock. raise data_store.TransactionError("Subject %s is locked" % self.subject) # If we get here the row does not exist: query = ("INSERT IGNORE INTO locks " "SET lock_expiration=%s, lock_owner=%s, " "subject_hash=unhex(md5(%s))") args = [self.expires_lock, self.lock_token, self.subject] self.store.ExecuteQuery(query, args) self._CheckForLock()
def _DeleteLock(self): # Deletes the lock entirely from the document. document = dict(_id=self.object_id, expires=self.expires) if not self.store.latest_collection.remove(query=document): raise data_store.TransactionError("Could not remove lock for %s." % self.subject) self.locked = False
def __init__(self, store, subject, lease_time=None, token=None): """Ensure we can take a lock on this subject.""" super(TDBTransaction, self).__init__(store, utils.SmartUnicode(subject), lease_time=lease_time, token=token) self.lock_key = utils.SmartUnicode(self.subject) + "_lock" if lease_time is None: lease_time = config_lib.CONFIG["Datastore.transaction_timeout"] # Note that we have the luxury of real file locking here so this will block # until we obtain the lock. with store.cache.Get(self.subject) as tdb_context: locked_until = tdb_context.Get(self.lock_key) # This is currently locked by another thread. if locked_until and time.time() < float(locked_until): raise data_store.TransactionError("Subject %s is locked" % subject) # Subject is not locked, we take a lease on it. self.expires = time.time() + lease_time tdb_context.Put(self.expires, self.lock_key) self.locked = True
def __init__(self, ds, subject, token=None): ds.security_manager.CheckDataStoreAccess(token, [subject], "w") self.collection = ds.collection self.subject = subject self.token = token # Bring all the data over to minimize round trips encoded_cache = self.collection.find_one(EscapeKey(subject)) or {} self._cache = {} for (k, v) in encoded_cache.items(): self._cache[DecodeKey(k)] = v # Initial lock number is a random number to avoid a race on creating new # documents. self.version = self._cache.get("_lock") if not self.version: # This object is not currently present or versioned, Create it try: self.collection.update( dict(_id=EscapeKey(subject)), {"$set": { "_lock": random.randint(1, 1e6) }}, upsert=True, safe=True) # Re-read the lock to ensure we do not have a race self._cache = self.collection.find_one(EscapeKey(subject)) self.version = self._cache.get("_lock") except errors.PyMongoError as e: logging.error(u"Mongo Error %s", utils.SmartUnicode(e)) raise data_store.TransactionError(utils.SmartUnicode(e)) self._to_update = {} self._committed = False
def _RemoveLock(self): # Remove the lock on the document. if not self.store.latest_collection.find_and_modify( query=self.document, update=dict(_id=self.object_id, expires=0)): raise data_store.TransactionError("Lock was overridden for %s." % self.subject) self.locked = False
def UpdateLease(self, duration): now = time.time() ret = self.store.ExtendSubjectLock(self.subject, self.transid, duration, self.token) if ret != self.transid: raise data_store.TransactionError("Unable to update the lease on %s" % self.subject) self.expires = now + duration
def __init__(self, store, subject, lease_time=None, token=None): """Ensure we can take a lock on this subject.""" super(HTTPTransaction, self).__init__( store, utils.SmartUnicode(subject), lease_time=lease_time, token=token) if lease_time is None: lease_time = config_lib.CONFIG["Datastore.transaction_timeout"] now = time.time() self.transid = store.LockSubject(self.subject, lease_time, self.token) if not self.transid: raise data_store.TransactionError("Unable to lock subject %s" % subject) self.expires = now + lease_time self.locked = True
def __init__(self, store, subject, lease_time=None, token=None): super(FakeTransaction, self).__init__(store, subject, lease_time=lease_time, token=token) self.locked = False if lease_time is None: lease_time = config_lib.CONFIG["Datastore.transaction_timeout"] self.expires = time.time() + lease_time with self.store.lock: expires = store.transactions.get(subject) if expires and time.time() < expires: raise data_store.TransactionError("Subject is locked") # Check expiry time. store.transactions[subject] = self.expires self.locked = True
def __init__(self, store, subject, lease_time=None, token=None): self.data_store = store self.subject = subject self.token = token self.locked = False self.to_set = {} self.to_delete = set() if lease_time is None: lease_time = config_lib.CONFIG["Datastore.transaction_timeout"] self.expires = time.time() + lease_time with self.data_store.lock: expires = store.transactions.get(subject) if expires and time.time() < expires: raise data_store.TransactionError("Subject is locked") # Check expiry time. store.transactions[subject] = self.expires self.locked = True
def CheckForLock(self, connection, subject): """Checks that the lock has stuck.""" for row in connection.Execute( "select * from `%s` where subject=%%s and hash=md5(%%s) and " "attribute='transaction'" % self.table_name, (subject, subject)): # We own this lock now. if row["value_integer"] == self.expires_lock: return # Someone else owns this lock. else: raise data_store.TransactionError("Subject %s is locked" % subject) # If we get here the row does not exist: connection.Execute( "insert ignore into `%s` set value_integer=%%s, " "attribute='transaction', subject=%%s, hash=md5(%%s) " % self.table_name, (self.expires_lock, self.subject, self.subject)) self.CheckForLock(connection, subject)
def Commit(self): """Commit the transaction.""" if self._committed: logging.error("Attempt to commit transaction multiple times...") return self._committed = True # We set the entire object now: if self._to_update: spec = dict(_id=URNEncode(EscapeKey(self.subject))) try: spec["_lock"] = self._cache["_lock"] except KeyError: pass self._to_update["_lock"] = self.version + 1 # There are basically three cases here: # 1) The document does not already exist. Nothing will match spec, and # this will insert a new document (because of the upsert=True). The # transaction succeeds. # 2) The document exists and matches spec - the same document will be # updated. The _lock version will be incremented. The transaction # succeeds. # 3) The document exists but does not match spec. This is likely because # someone else has modified it since we opened for read. We will try to # add a new document (as in 1 above), but this will raise because there # already is a document with the same ID. We therefore trap this # exception and emit a TransactionError. Transaction fails. try: self.collection.update(spec, {"$set": self._to_update}, upsert=True, safe=True) except errors.OperationFailure as e: # Transaction failed. raise data_store.TransactionError(utils.SmartUnicode(e))