123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175 |
- import collections
- import errno
- import logging
- import os
- import struct
- import sys
- import time
- import library.python.windows
- logger = logging.getLogger(__name__)
- # python2 compat
- os_O_CLOEXEC = getattr(os, 'O_CLOEXEC', 1 << 19)
- 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)
- # nonbuffered random access rw mode
- self._lock = os.fdopen(os.open(self.path, os.O_RDWR | os.O_CREAT | os_O_CLOEXEC), 'r+b', 0)
- 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)
- # nonbuffered random access rw mode
- self._lock = os.fdopen(os.open(self.path, os.O_RDWR | os.O_CREAT | os.O_BINARY | os.O_NOINHERIT), 'r+b', 0)
- try:
- self._lock.write(b' ' * 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):
- 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(0.5)
- else:
- return False
- def release(self):
- if self._lock:
- library.python.windows.unlock_file(self._lock, 0, self._LOCKED_BYTES_NUM, raises=False)
- def __del__(self):
- if getattr(self, '_lock', False):
- self._lock.close()
- 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 %s (blocking=%s): %s', type(self).__name__, blocking, self.path)
- return self._lock.acquire(blocking)
- def release(self):
- logger.debug('Ensuring %s released: %s', type(self).__name__, self.path)
- return self._lock.release()
- _LockInfo = collections.namedtuple('LockInfo', ['pid', 'time'])
- class _PidLockMixin(object):
- _LockedBytes = 0
- _InfoFormat = 'QQ'
- _InfoFmtSize = struct.calcsize(_InfoFormat)
- def _register_lock(self):
- self._lock.seek(self._LockedBytes, os.SEEK_SET)
- self._lock.write(struct.pack(self._InfoFormat, os.getpid(), int(time.time())))
- @property
- def info(self):
- self._lock.seek(self._LockedBytes, os.SEEK_SET)
- try:
- data = struct.unpack(self._InfoFormat, self._lock.read(self._InfoFmtSize))
- except struct.error:
- data = 0, 0
- return _LockInfo(*data)
- class _NixPidFileLock(_NixFileLock, _PidLockMixin):
- def acquire(self, blocking=True):
- if super(_NixPidFileLock, self).acquire(blocking):
- self._register_lock()
- return True
- return False
- class _WinPidFileLock(_WinFileLock, _PidLockMixin):
- _LockedBytes = _WinFileLock._LOCKED_BYTES_NUM
- def acquire(self, blocking=True):
- if super(_WinPidFileLock, self).acquire(blocking):
- self._register_lock()
- return True
- return False
- class PidFileLock(FileLock):
- def __init__(self, path):
- AbstractFileLock.__init__(self, path)
- if sys.platform.startswith('win'):
- self._lock = _WinPidFileLock(path)
- else:
- self._lock = _NixPidFileLock(path)
- @property
- def info(self):
- return self._lock.info
|