Mercurial > repos > shellac > sam_consensus_v3
comparison env/lib/python3.9/site-packages/filelock.py @ 0:4f3585e2f14b draft default tip
"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
| author | shellac |
|---|---|
| date | Mon, 22 Mar 2021 18:12:50 +0000 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:4f3585e2f14b |
|---|---|
| 1 # This is free and unencumbered software released into the public domain. | |
| 2 # | |
| 3 # Anyone is free to copy, modify, publish, use, compile, sell, or | |
| 4 # distribute this software, either in source code form or as a compiled | |
| 5 # binary, for any purpose, commercial or non-commercial, and by any | |
| 6 # means. | |
| 7 # | |
| 8 # In jurisdictions that recognize copyright laws, the author or authors | |
| 9 # of this software dedicate any and all copyright interest in the | |
| 10 # software to the public domain. We make this dedication for the benefit | |
| 11 # of the public at large and to the detriment of our heirs and | |
| 12 # successors. We intend this dedication to be an overt act of | |
| 13 # relinquishment in perpetuity of all present and future rights to this | |
| 14 # software under copyright law. | |
| 15 # | |
| 16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| 17 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 18 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
| 19 # IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR | |
| 20 # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | |
| 21 # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
| 22 # OTHER DEALINGS IN THE SOFTWARE. | |
| 23 # | |
| 24 # For more information, please refer to <http://unlicense.org> | |
| 25 | |
| 26 """ | |
| 27 A platform independent file lock that supports the with-statement. | |
| 28 """ | |
| 29 | |
| 30 | |
| 31 # Modules | |
| 32 # ------------------------------------------------ | |
| 33 import logging | |
| 34 import os | |
| 35 import threading | |
| 36 import time | |
| 37 try: | |
| 38 import warnings | |
| 39 except ImportError: | |
| 40 warnings = None | |
| 41 | |
| 42 try: | |
| 43 import msvcrt | |
| 44 except ImportError: | |
| 45 msvcrt = None | |
| 46 | |
| 47 try: | |
| 48 import fcntl | |
| 49 except ImportError: | |
| 50 fcntl = None | |
| 51 | |
| 52 | |
| 53 # Backward compatibility | |
| 54 # ------------------------------------------------ | |
| 55 try: | |
| 56 TimeoutError | |
| 57 except NameError: | |
| 58 TimeoutError = OSError | |
| 59 | |
| 60 | |
| 61 # Data | |
| 62 # ------------------------------------------------ | |
| 63 __all__ = [ | |
| 64 "Timeout", | |
| 65 "BaseFileLock", | |
| 66 "WindowsFileLock", | |
| 67 "UnixFileLock", | |
| 68 "SoftFileLock", | |
| 69 "FileLock" | |
| 70 ] | |
| 71 | |
| 72 __version__ = "3.0.12" | |
| 73 | |
| 74 | |
| 75 _logger = None | |
| 76 def logger(): | |
| 77 """Returns the logger instance used in this module.""" | |
| 78 global _logger | |
| 79 _logger = _logger or logging.getLogger(__name__) | |
| 80 return _logger | |
| 81 | |
| 82 | |
| 83 # Exceptions | |
| 84 # ------------------------------------------------ | |
| 85 class Timeout(TimeoutError): | |
| 86 """ | |
| 87 Raised when the lock could not be acquired in *timeout* | |
| 88 seconds. | |
| 89 """ | |
| 90 | |
| 91 def __init__(self, lock_file): | |
| 92 """ | |
| 93 """ | |
| 94 #: The path of the file lock. | |
| 95 self.lock_file = lock_file | |
| 96 return None | |
| 97 | |
| 98 def __str__(self): | |
| 99 temp = "The file lock '{}' could not be acquired."\ | |
| 100 .format(self.lock_file) | |
| 101 return temp | |
| 102 | |
| 103 | |
| 104 # Classes | |
| 105 # ------------------------------------------------ | |
| 106 | |
| 107 # This is a helper class which is returned by :meth:`BaseFileLock.acquire` | |
| 108 # and wraps the lock to make sure __enter__ is not called twice when entering | |
| 109 # the with statement. | |
| 110 # If we would simply return *self*, the lock would be acquired again | |
| 111 # in the *__enter__* method of the BaseFileLock, but not released again | |
| 112 # automatically. | |
| 113 # | |
| 114 # :seealso: issue #37 (memory leak) | |
| 115 class _Acquire_ReturnProxy(object): | |
| 116 | |
| 117 def __init__(self, lock): | |
| 118 self.lock = lock | |
| 119 return None | |
| 120 | |
| 121 def __enter__(self): | |
| 122 return self.lock | |
| 123 | |
| 124 def __exit__(self, exc_type, exc_value, traceback): | |
| 125 self.lock.release() | |
| 126 return None | |
| 127 | |
| 128 | |
| 129 class BaseFileLock(object): | |
| 130 """ | |
| 131 Implements the base class of a file lock. | |
| 132 """ | |
| 133 | |
| 134 def __init__(self, lock_file, timeout = -1): | |
| 135 """ | |
| 136 """ | |
| 137 # The path to the lock file. | |
| 138 self._lock_file = lock_file | |
| 139 | |
| 140 # The file descriptor for the *_lock_file* as it is returned by the | |
| 141 # os.open() function. | |
| 142 # This file lock is only NOT None, if the object currently holds the | |
| 143 # lock. | |
| 144 self._lock_file_fd = None | |
| 145 | |
| 146 # The default timeout value. | |
| 147 self.timeout = timeout | |
| 148 | |
| 149 # We use this lock primarily for the lock counter. | |
| 150 self._thread_lock = threading.Lock() | |
| 151 | |
| 152 # The lock counter is used for implementing the nested locking | |
| 153 # mechanism. Whenever the lock is acquired, the counter is increased and | |
| 154 # the lock is only released, when this value is 0 again. | |
| 155 self._lock_counter = 0 | |
| 156 return None | |
| 157 | |
| 158 @property | |
| 159 def lock_file(self): | |
| 160 """ | |
| 161 The path to the lock file. | |
| 162 """ | |
| 163 return self._lock_file | |
| 164 | |
| 165 @property | |
| 166 def timeout(self): | |
| 167 """ | |
| 168 You can set a default timeout for the filelock. It will be used as | |
| 169 fallback value in the acquire method, if no timeout value (*None*) is | |
| 170 given. | |
| 171 | |
| 172 If you want to disable the timeout, set it to a negative value. | |
| 173 | |
| 174 A timeout of 0 means, that there is exactly one attempt to acquire the | |
| 175 file lock. | |
| 176 | |
| 177 .. versionadded:: 2.0.0 | |
| 178 """ | |
| 179 return self._timeout | |
| 180 | |
| 181 @timeout.setter | |
| 182 def timeout(self, value): | |
| 183 """ | |
| 184 """ | |
| 185 self._timeout = float(value) | |
| 186 return None | |
| 187 | |
| 188 # Platform dependent locking | |
| 189 # -------------------------------------------- | |
| 190 | |
| 191 def _acquire(self): | |
| 192 """ | |
| 193 Platform dependent. If the file lock could be | |
| 194 acquired, self._lock_file_fd holds the file descriptor | |
| 195 of the lock file. | |
| 196 """ | |
| 197 raise NotImplementedError() | |
| 198 | |
| 199 def _release(self): | |
| 200 """ | |
| 201 Releases the lock and sets self._lock_file_fd to None. | |
| 202 """ | |
| 203 raise NotImplementedError() | |
| 204 | |
| 205 # Platform independent methods | |
| 206 # -------------------------------------------- | |
| 207 | |
| 208 @property | |
| 209 def is_locked(self): | |
| 210 """ | |
| 211 True, if the object holds the file lock. | |
| 212 | |
| 213 .. versionchanged:: 2.0.0 | |
| 214 | |
| 215 This was previously a method and is now a property. | |
| 216 """ | |
| 217 return self._lock_file_fd is not None | |
| 218 | |
| 219 def acquire(self, timeout=None, poll_intervall=0.05): | |
| 220 """ | |
| 221 Acquires the file lock or fails with a :exc:`Timeout` error. | |
| 222 | |
| 223 .. code-block:: python | |
| 224 | |
| 225 # You can use this method in the context manager (recommended) | |
| 226 with lock.acquire(): | |
| 227 pass | |
| 228 | |
| 229 # Or use an equivalent try-finally construct: | |
| 230 lock.acquire() | |
| 231 try: | |
| 232 pass | |
| 233 finally: | |
| 234 lock.release() | |
| 235 | |
| 236 :arg float timeout: | |
| 237 The maximum time waited for the file lock. | |
| 238 If ``timeout < 0``, there is no timeout and this method will | |
| 239 block until the lock could be acquired. | |
| 240 If ``timeout`` is None, the default :attr:`~timeout` is used. | |
| 241 | |
| 242 :arg float poll_intervall: | |
| 243 We check once in *poll_intervall* seconds if we can acquire the | |
| 244 file lock. | |
| 245 | |
| 246 :raises Timeout: | |
| 247 if the lock could not be acquired in *timeout* seconds. | |
| 248 | |
| 249 .. versionchanged:: 2.0.0 | |
| 250 | |
| 251 This method returns now a *proxy* object instead of *self*, | |
| 252 so that it can be used in a with statement without side effects. | |
| 253 """ | |
| 254 # Use the default timeout, if no timeout is provided. | |
| 255 if timeout is None: | |
| 256 timeout = self.timeout | |
| 257 | |
| 258 # Increment the number right at the beginning. | |
| 259 # We can still undo it, if something fails. | |
| 260 with self._thread_lock: | |
| 261 self._lock_counter += 1 | |
| 262 | |
| 263 lock_id = id(self) | |
| 264 lock_filename = self._lock_file | |
| 265 start_time = time.time() | |
| 266 try: | |
| 267 while True: | |
| 268 with self._thread_lock: | |
| 269 if not self.is_locked: | |
| 270 logger().debug('Attempting to acquire lock %s on %s', lock_id, lock_filename) | |
| 271 self._acquire() | |
| 272 | |
| 273 if self.is_locked: | |
| 274 logger().info('Lock %s acquired on %s', lock_id, lock_filename) | |
| 275 break | |
| 276 elif timeout >= 0 and time.time() - start_time > timeout: | |
| 277 logger().debug('Timeout on acquiring lock %s on %s', lock_id, lock_filename) | |
| 278 raise Timeout(self._lock_file) | |
| 279 else: | |
| 280 logger().debug( | |
| 281 'Lock %s not acquired on %s, waiting %s seconds ...', | |
| 282 lock_id, lock_filename, poll_intervall | |
| 283 ) | |
| 284 time.sleep(poll_intervall) | |
| 285 except: | |
| 286 # Something did go wrong, so decrement the counter. | |
| 287 with self._thread_lock: | |
| 288 self._lock_counter = max(0, self._lock_counter - 1) | |
| 289 | |
| 290 raise | |
| 291 return _Acquire_ReturnProxy(lock = self) | |
| 292 | |
| 293 def release(self, force = False): | |
| 294 """ | |
| 295 Releases the file lock. | |
| 296 | |
| 297 Please note, that the lock is only completly released, if the lock | |
| 298 counter is 0. | |
| 299 | |
| 300 Also note, that the lock file itself is not automatically deleted. | |
| 301 | |
| 302 :arg bool force: | |
| 303 If true, the lock counter is ignored and the lock is released in | |
| 304 every case. | |
| 305 """ | |
| 306 with self._thread_lock: | |
| 307 | |
| 308 if self.is_locked: | |
| 309 self._lock_counter -= 1 | |
| 310 | |
| 311 if self._lock_counter == 0 or force: | |
| 312 lock_id = id(self) | |
| 313 lock_filename = self._lock_file | |
| 314 | |
| 315 logger().debug('Attempting to release lock %s on %s', lock_id, lock_filename) | |
| 316 self._release() | |
| 317 self._lock_counter = 0 | |
| 318 logger().info('Lock %s released on %s', lock_id, lock_filename) | |
| 319 | |
| 320 return None | |
| 321 | |
| 322 def __enter__(self): | |
| 323 self.acquire() | |
| 324 return self | |
| 325 | |
| 326 def __exit__(self, exc_type, exc_value, traceback): | |
| 327 self.release() | |
| 328 return None | |
| 329 | |
| 330 def __del__(self): | |
| 331 self.release(force = True) | |
| 332 return None | |
| 333 | |
| 334 | |
| 335 # Windows locking mechanism | |
| 336 # ~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| 337 | |
| 338 class WindowsFileLock(BaseFileLock): | |
| 339 """ | |
| 340 Uses the :func:`msvcrt.locking` function to hard lock the lock file on | |
| 341 windows systems. | |
| 342 """ | |
| 343 | |
| 344 def _acquire(self): | |
| 345 open_mode = os.O_RDWR | os.O_CREAT | os.O_TRUNC | |
| 346 | |
| 347 try: | |
| 348 fd = os.open(self._lock_file, open_mode) | |
| 349 except OSError: | |
| 350 pass | |
| 351 else: | |
| 352 try: | |
| 353 msvcrt.locking(fd, msvcrt.LK_NBLCK, 1) | |
| 354 except (IOError, OSError): | |
| 355 os.close(fd) | |
| 356 else: | |
| 357 self._lock_file_fd = fd | |
| 358 return None | |
| 359 | |
| 360 def _release(self): | |
| 361 fd = self._lock_file_fd | |
| 362 self._lock_file_fd = None | |
| 363 msvcrt.locking(fd, msvcrt.LK_UNLCK, 1) | |
| 364 os.close(fd) | |
| 365 | |
| 366 try: | |
| 367 os.remove(self._lock_file) | |
| 368 # Probably another instance of the application | |
| 369 # that acquired the file lock. | |
| 370 except OSError: | |
| 371 pass | |
| 372 return None | |
| 373 | |
| 374 # Unix locking mechanism | |
| 375 # ~~~~~~~~~~~~~~~~~~~~~~ | |
| 376 | |
| 377 class UnixFileLock(BaseFileLock): | |
| 378 """ | |
| 379 Uses the :func:`fcntl.flock` to hard lock the lock file on unix systems. | |
| 380 """ | |
| 381 | |
| 382 def _acquire(self): | |
| 383 open_mode = os.O_RDWR | os.O_CREAT | os.O_TRUNC | |
| 384 fd = os.open(self._lock_file, open_mode) | |
| 385 | |
| 386 try: | |
| 387 fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) | |
| 388 except (IOError, OSError): | |
| 389 os.close(fd) | |
| 390 else: | |
| 391 self._lock_file_fd = fd | |
| 392 return None | |
| 393 | |
| 394 def _release(self): | |
| 395 # Do not remove the lockfile: | |
| 396 # | |
| 397 # https://github.com/benediktschmitt/py-filelock/issues/31 | |
| 398 # https://stackoverflow.com/questions/17708885/flock-removing-locked-file-without-race-condition | |
| 399 fd = self._lock_file_fd | |
| 400 self._lock_file_fd = None | |
| 401 fcntl.flock(fd, fcntl.LOCK_UN) | |
| 402 os.close(fd) | |
| 403 return None | |
| 404 | |
| 405 # Soft lock | |
| 406 # ~~~~~~~~~ | |
| 407 | |
| 408 class SoftFileLock(BaseFileLock): | |
| 409 """ | |
| 410 Simply watches the existence of the lock file. | |
| 411 """ | |
| 412 | |
| 413 def _acquire(self): | |
| 414 open_mode = os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_TRUNC | |
| 415 try: | |
| 416 fd = os.open(self._lock_file, open_mode) | |
| 417 except (IOError, OSError): | |
| 418 pass | |
| 419 else: | |
| 420 self._lock_file_fd = fd | |
| 421 return None | |
| 422 | |
| 423 def _release(self): | |
| 424 os.close(self._lock_file_fd) | |
| 425 self._lock_file_fd = None | |
| 426 | |
| 427 try: | |
| 428 os.remove(self._lock_file) | |
| 429 # The file is already deleted and that's what we want. | |
| 430 except OSError: | |
| 431 pass | |
| 432 return None | |
| 433 | |
| 434 | |
| 435 # Platform filelock | |
| 436 # ~~~~~~~~~~~~~~~~~ | |
| 437 | |
| 438 #: Alias for the lock, which should be used for the current platform. On | |
| 439 #: Windows, this is an alias for :class:`WindowsFileLock`, on Unix for | |
| 440 #: :class:`UnixFileLock` and otherwise for :class:`SoftFileLock`. | |
| 441 FileLock = None | |
| 442 | |
| 443 if msvcrt: | |
| 444 FileLock = WindowsFileLock | |
| 445 elif fcntl: | |
| 446 FileLock = UnixFileLock | |
| 447 else: | |
| 448 FileLock = SoftFileLock | |
| 449 | |
| 450 if warnings is not None: | |
| 451 warnings.warn("only soft file lock is available") |
