def atomic_lock(lock_dir, lock_name, lifetime=60): """A atomic, NFS safe implementation of a file lock. Uses `flufl.lock` under the hood. Can be used as a context manager:: with atomic_lock(settings.TMP_PATH, 'extraction-1234'): extract_xpi(...) :return: `True` if the lock was attained, we are owning the lock, `False` if there is an already existing lock. """ lock_name = lock_name + '.lock' count = _lock_count.get(lock_name, 0) log.debug('Acquiring lock %s, count is %d.' % (lock_name, count)) lock_name = os.path.join(lock_dir, lock_name) lock = flufl.lock.Lock(lock_name, lifetime=timedelta(seconds=lifetime)) try: # set `timeout=0` to avoid any process blocking but catch the # TimeOutError raised instead. lock.lock(timeout=timedelta(seconds=0)) except flufl.lock.AlreadyLockedError: # This process already holds the lock yield False except flufl.lock.TimeOutError: # Some other process holds the lock. # Let's break the lock if it has expired. Unfortunately # there's a bug in flufl.lock so let's do this manually. # Bug: https://gitlab.com/warsaw/flufl.lock/merge_requests/1 release_time = lock._releasetime max_release_time = release_time + flufl.lock._lockfile.CLOCK_SLOP if (release_time != -1 and datetime.now() > max_release_time): # Break the lock and try to aquire again lock._break() lock.lock(timeout=timedelta(seconds=0)) yield lock.is_locked else: # Already locked yield False else: # Is usually `True` but just in case there were some weird `lifetime` # values set we return the check if we really attained the lock. yield lock.is_locked if lock.is_locked: log.debug('Releasing lock %s.' % lock.details[2]) lock.unlock()