A Lock for a resource that can be accessed in a shared or exclusive way. Typically, write access to a resource needs an exclusive lock (1 writer, no one is allowed reading) and read access to a resource needs a shared lock (multiple readers are allowed). If possible, try to use
| 372 | |
| 373 | |
| 374 | class Lock: |
| 375 | """ |
| 376 | A Lock for a resource that can be accessed in a shared or exclusive way. |
| 377 | Typically, write access to a resource needs an exclusive lock (1 writer, |
| 378 | no one is allowed reading) and read access to a resource needs a shared |
| 379 | lock (multiple readers are allowed). |
| 380 | |
| 381 | If possible, try to use the contextmanager here like:: |
| 382 | |
| 383 | with Lock(...) as lock: |
| 384 | ... |
| 385 | |
| 386 | This makes sure the lock is released again if the block is left, no |
| 387 | matter how (e.g. if an exception occurred). |
| 388 | """ |
| 389 | |
| 390 | def __init__(self, path, exclusive=False, sleep=None, timeout=None, id=None): |
| 391 | self.path = path |
| 392 | self.is_exclusive = exclusive |
| 393 | self.sleep = sleep |
| 394 | self.timeout = timeout |
| 395 | self.id = id or platform.get_process_id() |
| 396 | # globally keeping track of shared and exclusive lockers: |
| 397 | self._roster = LockRoster(Path(path + ".roster"), id=id) |
| 398 | # an exclusive lock, used for: |
| 399 | # - holding while doing roster queries / updates |
| 400 | # - holding while the Lock itself is exclusive |
| 401 | self._lock = ExclusiveLock(str(Path(path + ".exclusive")), id=id, timeout=timeout) |
| 402 | |
| 403 | def __enter__(self): |
| 404 | return self.acquire() |
| 405 | |
| 406 | def __exit__(self, *exc): |
| 407 | self.release() |
| 408 | |
| 409 | def __repr__(self): |
| 410 | return f"<{self.__class__.__name__}: {self.id!r}>" |
| 411 | |
| 412 | def acquire(self, exclusive=None, remove=None, sleep=None): |
| 413 | if exclusive is None: |
| 414 | exclusive = self.is_exclusive |
| 415 | sleep = sleep or self.sleep or 0.2 |
| 416 | if exclusive: |
| 417 | self._wait_for_readers_finishing(remove, sleep) |
| 418 | self._roster.modify(EXCLUSIVE, ADD) |
| 419 | else: |
| 420 | with self._lock: |
| 421 | if remove is not None: |
| 422 | self._roster.modify(remove, REMOVE) |
| 423 | self._roster.modify(SHARED, ADD) |
| 424 | self.is_exclusive = exclusive |
| 425 | return self |
| 426 | |
| 427 | def _wait_for_readers_finishing(self, remove, sleep): |
| 428 | timer = TimeoutTimer(self.timeout, sleep).start() |
| 429 | while True: |
| 430 | self._lock.acquire() |
| 431 | try: |
no outgoing calls