import errno import logging import os import sys import library.python.windows logger = logging.getLogger(__name__) def set_close_on_exec(stream): if library.python.windows.on_win(): library.python.windows.set_handle_information(stream, inherit=False) else: import fcntl fcntl.fcntl(stream, fcntl.F_SETFD, fcntl.FD_CLOEXEC) class AbstractFileLock(object): def __init__(self, path): self.path = path def acquire(self, blocking=True): raise NotImplementedError def release(self): raise NotImplementedError def __enter__(self): self.acquire() return self def __exit__(self, type, value, traceback): self.release() class _NixFileLock(AbstractFileLock): def __init__(self, path): super(_NixFileLock, self).__init__(path) from fcntl import flock, LOCK_EX, LOCK_UN, LOCK_NB self._locker = lambda lock, blocking: flock(lock, LOCK_EX if blocking else LOCK_EX | LOCK_NB) self._unlocker = lambda lock: flock(lock, LOCK_UN) self._lock = open(self.path, 'a') set_close_on_exec(self._lock) def acquire(self, blocking=True): import errno try: self._locker(self._lock, blocking) except IOError as e: if e.errno in (errno.EAGAIN, errno.EACCES) and not blocking: return False raise return True def release(self): self._unlocker(self._lock) def __del__(self): if hasattr(self, "_lock"): self._lock.close() class _WinFileLock(AbstractFileLock): """ Based on LockFile / UnlockFile from win32 API https://msdn.microsoft.com/en-us/library/windows/desktop/aa365202(v=vs.85).aspx """ _LOCKED_BYTES_NUM = 1 def __init__(self, path): super(_WinFileLock, self).__init__(path) self._lock = None try: with open(path, 'w') as lock_file: lock_file.write(" " * self._LOCKED_BYTES_NUM) except IOError as e: if e.errno != errno.EACCES or not os.path.isfile(path): raise def acquire(self, blocking=True): self._lock = open(self.path) set_close_on_exec(self._lock) import time locked = False while not locked: locked = library.python.windows.lock_file(self._lock, 0, self._LOCKED_BYTES_NUM, raises=False) if locked: return True if blocking: time.sleep(.5) else: return False def release(self): if self._lock: library.python.windows.unlock_file(self._lock, 0, self._LOCKED_BYTES_NUM, raises=False) self._lock.close() self._lock = None class FileLock(AbstractFileLock): def __init__(self, path): super(FileLock, self).__init__(path) if sys.platform.startswith('win'): self._lock = _WinFileLock(path) else: self._lock = _NixFileLock(path) def acquire(self, blocking=True): logger.debug('Acquiring filelock (blocking=%s): %s', blocking, self.path) return self._lock.acquire(blocking) def release(self): logger.debug('Ensuring filelock released: %s', self.path) return self._lock.release()