Async wrapper around :class:`ReadWriteLock` for use in ``asyncio`` applications. This wrapper dispatches every blocking SQLite operation to a thread pool via ``loop.run_in_executor()`` because Python's :mod:`sqlite3` module has no async API. It delegates reentrancy, upgrade/downgrade r
| 38 | |
| 39 | |
| 40 | class AsyncReadWriteLock: |
| 41 | """ |
| 42 | Async wrapper around :class:`ReadWriteLock` for use in ``asyncio`` applications. |
| 43 | |
| 44 | This wrapper dispatches every blocking SQLite operation to a thread pool via ``loop.run_in_executor()`` because |
| 45 | Python's :mod:`sqlite3` module has no async API. It delegates reentrancy, upgrade/downgrade rules, and singleton |
| 46 | behavior to the underlying :class:`ReadWriteLock`. |
| 47 | |
| 48 | :param lock_file: path to the SQLite database file used as the lock |
| 49 | :param timeout: maximum wait time in seconds; ``-1`` means block indefinitely |
| 50 | :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately when the lock is unavailable |
| 51 | :param is_singleton: if ``True``, reuse existing :class:`ReadWriteLock` instances for the same resolved path |
| 52 | :param loop: event loop for ``run_in_executor``; ``None`` uses the running loop |
| 53 | :param executor: executor for ``run_in_executor``. When ``None`` this lock creates and owns a dedicated |
| 54 | single-thread executor so every operation runs on the same thread (SQLite affinity requires this) and shuts it |
| 55 | down in :meth:`close`. This lock uses a caller-supplied executor as-is and never shuts it down, so after passing |
| 56 | no executor call :meth:`close` to release the owned one. |
| 57 | |
| 58 | .. versionadded:: 3.21.0 |
| 59 | |
| 60 | """ |
| 61 | |
| 62 | def __init__( # ruff:ignore[too-many-arguments] # public constructor: one parameter per documented lock option |
| 63 | self, |
| 64 | lock_file: str | os.PathLike[str], |
| 65 | timeout: float = -1, |
| 66 | *, |
| 67 | blocking: bool = True, |
| 68 | is_singleton: bool = True, |
| 69 | loop: asyncio.AbstractEventLoop | None = None, |
| 70 | executor: futures.Executor | None = None, |
| 71 | ) -> None: |
| 72 | creator_pid = os.getpid() |
| 73 | self._creator_pid = creator_pid |
| 74 | self._fork_invalidated = False |
| 75 | self._closed = False |
| 76 | _register_fork_object(self) |
| 77 | with _fork_transition(): |
| 78 | self._lock = ReadWriteLock(lock_file, timeout, blocking=blocking, is_singleton=is_singleton) |
| 79 | self._loop = loop |
| 80 | self._owns_executor = executor is None |
| 81 | self._executor = executor or ThreadPoolExecutor(max_workers=1) |
| 82 | if os.getpid() != creator_pid: # pragma: forked child |
| 83 | msg = "AsyncReadWriteLock construction cannot continue after fork" |
| 84 | raise RuntimeError(msg) |
| 85 | |
| 86 | @property |
| 87 | def lock_file(self) -> str: |
| 88 | """The path to the lock file.""" |
| 89 | return self._lock.lock_file |
| 90 | |
| 91 | @property |
| 92 | def timeout(self) -> float: |
| 93 | """The default timeout.""" |
| 94 | return self._lock.timeout |
| 95 | |
| 96 | @property |
| 97 | def blocking(self) -> bool: |
no outgoing calls