| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
|
|
| """ |
| A platform independent file lock that supports the with-statement. |
| """ |
|
|
|
|
| |
| |
| import logging |
| import os |
| import threading |
| import time |
| try: |
| import warnings |
| except ImportError: |
| warnings = None |
|
|
| try: |
| import msvcrt |
| except ImportError: |
| msvcrt = None |
|
|
| try: |
| import fcntl |
| except ImportError: |
| fcntl = None |
|
|
|
|
| |
| |
| try: |
| TimeoutError |
| except NameError: |
| TimeoutError = OSError |
|
|
|
|
| |
| |
| __all__ = [ |
| "Timeout", |
| "BaseFileLock", |
| "WindowsFileLock", |
| "UnixFileLock", |
| "SoftFileLock", |
| "FileLock" |
| ] |
|
|
| __version__ = "3.0.12" |
|
|
|
|
| _logger = None |
| def logger(): |
| """Returns the logger instance used in this module.""" |
| global _logger |
| _logger = _logger or logging.getLogger(__name__) |
| return _logger |
|
|
|
|
| |
| |
| class Timeout(TimeoutError): |
| """ |
| Raised when the lock could not be acquired in *timeout* |
| seconds. |
| """ |
|
|
| def __init__(self, lock_file): |
| """ |
| """ |
| |
| self.lock_file = lock_file |
| return None |
|
|
| def __str__(self): |
| temp = "The file lock '{}' could not be acquired."\ |
| .format(self.lock_file) |
| return temp |
|
|
|
|
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| class _Acquire_ReturnProxy: |
|
|
| def __init__(self, lock): |
| self.lock = lock |
| return None |
|
|
| def __enter__(self): |
| return self.lock |
|
|
| def __exit__(self, _exc_type, _exc_value, _traceback): |
| self.lock.release() |
| return None |
|
|
|
|
| class BaseFileLock: |
| """ |
| Implements the base class of a file lock. |
| """ |
|
|
| def __init__(self, lock_file, timeout = -1): |
| """ |
| """ |
| |
| self._lock_file = lock_file |
|
|
| |
| |
| |
| |
| self._lock_file_fd = None |
|
|
| |
| self.timeout = timeout |
|
|
| |
| self._thread_lock = threading.Lock() |
|
|
| |
| |
| |
| self._lock_counter = 0 |
| return None |
|
|
| @property |
| def lock_file(self): |
| """ |
| The path to the lock file. |
| """ |
| return self._lock_file |
|
|
| @property |
| def timeout(self): |
| """ |
| You can set a default timeout for the filelock. It will be used as |
| fallback value in the acquire method, if no timeout value (*None*) is |
| given. |
| |
| If you want to disable the timeout, set it to a negative value. |
| |
| A timeout of 0 means, that there is exactly one attempt to acquire the |
| file lock. |
| |
| .. versionadded:: 2.0.0 |
| """ |
| return self._timeout |
|
|
| @timeout.setter |
| def timeout(self, value): |
| """ |
| """ |
| self._timeout = float(value) |
| return None |
|
|
| |
| |
|
|
| def _acquire(self): |
| """ |
| Platform dependent. If the file lock could be |
| acquired, self._lock_file_fd holds the file descriptor |
| of the lock file. |
| """ |
| raise NotImplementedError() |
|
|
| def _release(self): |
| """ |
| Releases the lock and sets self._lock_file_fd to None. |
| """ |
| raise NotImplementedError() |
|
|
| |
| |
|
|
| @property |
| def is_locked(self): |
| """ |
| True, if the object holds the file lock. |
| |
| .. versionchanged:: 2.0.0 |
| |
| This was previously a method and is now a property. |
| """ |
| return self._lock_file_fd is not None |
|
|
| def acquire(self, timeout=None, poll_intervall=0.05): |
| """ |
| Acquires the file lock or fails with a :exc:`Timeout` error. |
| |
| .. code-block:: python |
| |
| # You can use this method in the context manager (recommended) |
| with lock.acquire(): |
| pass |
| |
| # Or use an equivalent try-finally construct: |
| lock.acquire() |
| try: |
| pass |
| finally: |
| lock.release() |
| |
| :arg float timeout: |
| The maximum time waited for the file lock. |
| If ``timeout < 0``, there is no timeout and this method will |
| block until the lock could be acquired. |
| If ``timeout`` is None, the default :attr:`~timeout` is used. |
| |
| :arg float poll_intervall: |
| We check once in *poll_intervall* seconds if we can acquire the |
| file lock. |
| |
| :raises Timeout: |
| if the lock could not be acquired in *timeout* seconds. |
| |
| .. versionchanged:: 2.0.0 |
| |
| This method returns now a *proxy* object instead of *self*, |
| so that it can be used in a with statement without side effects. |
| """ |
| |
| if timeout is None: |
| timeout = self.timeout |
|
|
| |
| |
| with self._thread_lock: |
| self._lock_counter += 1 |
|
|
| lock_id = id(self) |
| lock_filename = self._lock_file |
| start_time = time.time() |
| try: |
| while True: |
| with self._thread_lock: |
| if not self.is_locked: |
| logger().debug('Attempting to acquire lock %s on %s', lock_id, lock_filename) |
| self._acquire() |
|
|
| if self.is_locked: |
| logger().debug('Lock %s acquired on %s', lock_id, lock_filename) |
| break |
| elif timeout >= 0 and time.time() - start_time > timeout: |
| logger().debug('Timeout on acquiring lock %s on %s', lock_id, lock_filename) |
| raise Timeout(self._lock_file) |
| else: |
| logger().debug( |
| 'Lock %s not acquired on %s, waiting %s seconds ...', |
| lock_id, lock_filename, poll_intervall |
| ) |
| time.sleep(poll_intervall) |
| except: |
| |
| with self._thread_lock: |
| self._lock_counter = max(0, self._lock_counter - 1) |
|
|
| raise |
| return _Acquire_ReturnProxy(lock = self) |
|
|
| def release(self, force = False): |
| """ |
| Releases the file lock. |
| |
| Please note, that the lock is only completely released, if the lock |
| counter is 0. |
| |
| Also note, that the lock file itself is not automatically deleted. |
| |
| :arg bool force: |
| If true, the lock counter is ignored and the lock is released in |
| every case. |
| """ |
| with self._thread_lock: |
|
|
| if self.is_locked: |
| self._lock_counter -= 1 |
|
|
| if self._lock_counter == 0 or force: |
| lock_id = id(self) |
| lock_filename = self._lock_file |
|
|
| logger().debug('Attempting to release lock %s on %s', lock_id, lock_filename) |
| self._release() |
| self._lock_counter = 0 |
| logger().debug('Lock %s released on %s', lock_id, lock_filename) |
|
|
| return None |
|
|
| def __enter__(self): |
| self.acquire() |
| return self |
|
|
| def __exit__(self, _exc_type, _exc_value, _traceback): |
| self.release() |
| return None |
|
|
| def __del__(self): |
| self.release(force = True) |
| return None |
|
|
|
|
| |
| |
|
|
| class WindowsFileLock(BaseFileLock): |
| """ |
| Uses the :func:`msvcrt.locking` function to hard lock the lock file on |
| windows systems. |
| """ |
|
|
| def _acquire(self): |
| open_mode = os.O_RDWR | os.O_CREAT | os.O_TRUNC |
|
|
| try: |
| fd = os.open(self._lock_file, open_mode) |
| except OSError: |
| pass |
| else: |
| try: |
| msvcrt.locking(fd, msvcrt.LK_NBLCK, 1) |
| except (IOError, OSError): |
| os.close(fd) |
| else: |
| self._lock_file_fd = fd |
| return None |
|
|
| def _release(self): |
| fd = self._lock_file_fd |
| self._lock_file_fd = None |
| msvcrt.locking(fd, msvcrt.LK_UNLCK, 1) |
| os.close(fd) |
|
|
| try: |
| os.remove(self._lock_file) |
| |
| |
| except OSError: |
| pass |
| return None |
|
|
| |
| |
|
|
| class UnixFileLock(BaseFileLock): |
| """ |
| Uses the :func:`fcntl.flock` to hard lock the lock file on unix systems. |
| """ |
|
|
| def _acquire(self): |
| open_mode = os.O_RDWR | os.O_CREAT | os.O_TRUNC |
| fd = os.open(self._lock_file, open_mode) |
|
|
| try: |
| fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) |
| except (IOError, OSError): |
| os.close(fd) |
| else: |
| st = os.fstat(fd); |
| if st.st_nlink == 0: |
| |
| |
| |
| |
| |
| os.close(fd) |
| else: |
| self._lock_file_fd = fd |
| return None |
|
|
| def _release(self): |
| fd = self._lock_file_fd |
| self._lock_file_fd = None |
| os.unlink(self._lock_file) |
| fcntl.flock(fd, fcntl.LOCK_UN) |
| os.close(fd) |
| return None |
|
|
| |
| |
|
|
| class SoftFileLock(BaseFileLock): |
| """ |
| Simply watches the existence of the lock file. |
| """ |
|
|
| def _acquire(self): |
| open_mode = os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_TRUNC |
| try: |
| fd = os.open(self._lock_file, open_mode) |
| except (IOError, OSError): |
| pass |
| else: |
| self._lock_file_fd = fd |
| return None |
|
|
| def _release(self): |
| os.close(self._lock_file_fd) |
| self._lock_file_fd = None |
|
|
| try: |
| os.remove(self._lock_file) |
| |
| except OSError: |
| pass |
| return None |
|
|
|
|
| |
| |
|
|
| |
| |
| |
| FileLock = None |
|
|
| if msvcrt: |
| FileLock = WindowsFileLock |
| elif fcntl: |
| FileLock = UnixFileLock |
| else: |
| FileLock = SoftFileLock |
|
|
| if warnings is not None: |
| warnings.warn("only soft file lock is available") |
|
|