class file_mutex(object): """ This is mutex backed on the filesystem. It's cross thread and cross process. However its limited to the boundaries of a filesystem """ def __init__(self, name, wait=None): """ Creates a file mutex object """ self.name = name self._has_lock = False self._start = 0 self._logger = Logger('extensions') self._handle = open(self.key(), 'w') self._wait = wait try: os.chmod( self.key(), stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IROTH | stat.S_IWOTH) except OSError: pass def __call__(self, wait): self._wait = wait return self def __enter__(self): self.acquire() return self def __exit__(self, *args, **kwargs): _ = args, kwargs self.release() def acquire(self, wait=None): """ Acquire a lock on the mutex, optionally given a maximum wait timeout :param wait: Time to wait for lock """ if self._has_lock: return True self._start = time.time() if wait is None: wait = self._wait passed = 0 if wait is None: fcntl.flock(self._handle, fcntl.LOCK_EX) passed = time.time() - self._start else: while True: passed = time.time() - self._start if passed > wait: self._logger.error( 'Lock for {0} could not be acquired. {1} sec > {2} sec' .format(self.key(), passed, wait)) raise NoLockAvailableException( 'Could not acquire lock %s' % self.key()) try: fcntl.flock(self._handle, fcntl.LOCK_EX | fcntl.LOCK_NB) break except IOError: time.sleep(0.005) if passed > 1: # More than 1 s is a long time to wait! self._logger.warning('Waited {0} sec for lock {1}'.format( passed, self.key())) self._start = time.time() self._has_lock = True return True def release(self): """ Releases the lock """ if self._has_lock: fcntl.flock(self._handle, fcntl.LOCK_UN) passed = time.time() - self._start if passed > 2.5: # More than 2.5 s is a long time to hold a lock self._logger.warning( 'A lock on {0} was kept for {1} sec'.format( self.key(), passed)) self._has_lock = False def key(self): """ Lock key """ if '/' in self.name: return self.name # Assuming a path return '/var/lock/ovs_flock_{0}'.format(self.name) def __del__(self): """ __del__ hook, releasing the lock """ self.release() self._handle.close()
class volatile_mutex(object): """ This is a volatile, distributed mutex to provide cross thread, cross process and cross node locking. However, this mutex is volatile and thus can fail. You want to make sure you don't lock for longer than a few hundred milliseconds to prevent this. """ def __init__(self, name, wait=None): """ Creates a volatile mutex object """ self.name = name self._wait = wait self._start = 0 self._logger = Logger('extensions') # Instantiated by classes inheriting this class self._has_lock = False self._volatile = self._get_volatile_client() def __call__(self, wait): self._wait = wait return self def __enter__(self): self.acquire() return self def __exit__(self, *args, **kwargs): _ = args, kwargs self.release() def acquire(self, wait=None): """ Acquire a lock on the mutex, optionally given a maximum wait timeout :param wait: Time to wait for lock """ if self._has_lock: return True self._start = time.time() if wait is None: wait = self._wait while not self._volatile.add(self.key(), 1, 60): time.sleep(0.005) passed = time.time() - self._start if wait is not None and passed > wait: if self._logger is not None: self._logger.error('Lock for {0} could not be acquired. {1} sec > {2} sec'.format(self.key(), passed, wait)) raise NoLockAvailableException('Could not acquire lock {0}'.format(self.key())) passed = time.time() - self._start if passed > 0.2: # More than 200 ms is a long time to wait if self._logger is not None: self._logger.warning('Waited {0} sec for lock {1}'.format(passed, self.key())) self._start = time.time() self._has_lock = True return True def release(self): """ Releases the lock """ if self._has_lock: self._volatile.delete(self.key()) passed = time.time() - self._start if passed > 0.5: # More than 500 ms is a long time to hold a lock if self._logger is not None: self._logger.warning('A lock on {0} was kept for {1} sec'.format(self.key(), passed)) self._has_lock = False def key(self): """ Lock key """ return 'ovs_lock_%s' % self.name def __del__(self): """ __del__ hook, releasing the lock """ self.release() @classmethod def _get_volatile_client(cls): raise NotImplementedError()