__init__.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. import collections
  2. import errno
  3. import logging
  4. import os
  5. import struct
  6. import sys
  7. import time
  8. import library.python.windows
  9. logger = logging.getLogger(__name__)
  10. # python2 compat
  11. os_O_CLOEXEC = getattr(os, 'O_CLOEXEC', 1 << 19)
  12. class AbstractFileLock(object):
  13. def __init__(self, path):
  14. self.path = path
  15. def acquire(self, blocking=True):
  16. raise NotImplementedError
  17. def release(self):
  18. raise NotImplementedError
  19. def __enter__(self):
  20. self.acquire()
  21. return self
  22. def __exit__(self, type, value, traceback):
  23. self.release()
  24. class _NixFileLock(AbstractFileLock):
  25. def __init__(self, path):
  26. super(_NixFileLock, self).__init__(path)
  27. from fcntl import flock, LOCK_EX, LOCK_UN, LOCK_NB
  28. self._locker = lambda lock, blocking: flock(lock, LOCK_EX if blocking else LOCK_EX | LOCK_NB)
  29. self._unlocker = lambda lock: flock(lock, LOCK_UN)
  30. # nonbuffered random access rw mode
  31. self._lock = os.fdopen(os.open(self.path, os.O_RDWR | os.O_CREAT | os_O_CLOEXEC), 'r+b', 0)
  32. def acquire(self, blocking=True):
  33. import errno
  34. try:
  35. self._locker(self._lock, blocking)
  36. except IOError as e:
  37. if e.errno in (errno.EAGAIN, errno.EACCES) and not blocking:
  38. return False
  39. raise
  40. return True
  41. def release(self):
  42. self._unlocker(self._lock)
  43. def __del__(self):
  44. if hasattr(self, "_lock"):
  45. self._lock.close()
  46. class _WinFileLock(AbstractFileLock):
  47. """
  48. Based on LockFile / UnlockFile from win32 API
  49. https://msdn.microsoft.com/en-us/library/windows/desktop/aa365202(v=vs.85).aspx
  50. """
  51. _LOCKED_BYTES_NUM = 1
  52. def __init__(self, path):
  53. super(_WinFileLock, self).__init__(path)
  54. # nonbuffered random access rw mode
  55. self._lock = os.fdopen(os.open(self.path, os.O_RDWR | os.O_CREAT | os.O_BINARY | os.O_NOINHERIT), 'r+b', 0)
  56. try:
  57. self._lock.write(b' ' * self._LOCKED_BYTES_NUM)
  58. except IOError as e:
  59. if e.errno != errno.EACCES or not os.path.isfile(path):
  60. raise
  61. def acquire(self, blocking=True):
  62. locked = False
  63. while not locked:
  64. locked = library.python.windows.lock_file(self._lock, 0, self._LOCKED_BYTES_NUM, raises=False)
  65. if locked:
  66. return True
  67. if blocking:
  68. time.sleep(0.5)
  69. else:
  70. return False
  71. def release(self):
  72. if self._lock:
  73. library.python.windows.unlock_file(self._lock, 0, self._LOCKED_BYTES_NUM, raises=False)
  74. def __del__(self):
  75. if getattr(self, '_lock', False):
  76. self._lock.close()
  77. class FileLock(AbstractFileLock):
  78. def __init__(self, path):
  79. super(FileLock, self).__init__(path)
  80. if sys.platform.startswith('win'):
  81. self._lock = _WinFileLock(path)
  82. else:
  83. self._lock = _NixFileLock(path)
  84. def acquire(self, blocking=True):
  85. logger.debug('Acquiring %s (blocking=%s): %s', type(self).__name__, blocking, self.path)
  86. return self._lock.acquire(blocking)
  87. def release(self):
  88. logger.debug('Ensuring %s released: %s', type(self).__name__, self.path)
  89. return self._lock.release()
  90. _LockInfo = collections.namedtuple('LockInfo', ['pid', 'time'])
  91. class _PidLockMixin(object):
  92. _LockedBytes = 0
  93. _InfoFormat = 'QQ'
  94. _InfoFmtSize = struct.calcsize(_InfoFormat)
  95. def _register_lock(self):
  96. self._lock.seek(self._LockedBytes, os.SEEK_SET)
  97. self._lock.write(struct.pack(self._InfoFormat, os.getpid(), int(time.time())))
  98. @property
  99. def info(self):
  100. self._lock.seek(self._LockedBytes, os.SEEK_SET)
  101. try:
  102. data = struct.unpack(self._InfoFormat, self._lock.read(self._InfoFmtSize))
  103. except struct.error:
  104. data = 0, 0
  105. return _LockInfo(*data)
  106. class _NixPidFileLock(_NixFileLock, _PidLockMixin):
  107. def acquire(self, blocking=True):
  108. if super(_NixPidFileLock, self).acquire(blocking):
  109. self._register_lock()
  110. return True
  111. return False
  112. class _WinPidFileLock(_WinFileLock, _PidLockMixin):
  113. _LockedBytes = _WinFileLock._LOCKED_BYTES_NUM
  114. def acquire(self, blocking=True):
  115. if super(_WinPidFileLock, self).acquire(blocking):
  116. self._register_lock()
  117. return True
  118. return False
  119. class PidFileLock(FileLock):
  120. def __init__(self, path):
  121. AbstractFileLock.__init__(self, path)
  122. if sys.platform.startswith('win'):
  123. self._lock = _WinPidFileLock(path)
  124. else:
  125. self._lock = _NixPidFileLock(path)
  126. @property
  127. def info(self):
  128. return self._lock.info