Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/filelock/_unix.py: 55%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from __future__ import annotations
3import os
4import sys
5import warnings
6from contextlib import suppress
7from errno import EAGAIN, ENOSYS, EWOULDBLOCK
8from pathlib import Path
9from typing import cast
11from ._api import BaseFileLock
12from ._util import ensure_directory_exists
14#: a flag to indicate if the fcntl API is available
15has_fcntl = False
16if sys.platform == "win32": # pragma: win32 cover
18 class UnixFileLock(BaseFileLock):
19 """Uses the :func:`fcntl.flock` to hard lock the lock file on unix systems."""
21 def _acquire(self) -> None:
22 raise NotImplementedError
24 def _release(self) -> None:
25 raise NotImplementedError
27else: # pragma: win32 no cover
28 try:
29 import fcntl
31 _ = (fcntl.flock, fcntl.LOCK_EX, fcntl.LOCK_NB, fcntl.LOCK_UN)
32 except (ImportError, AttributeError):
33 pass
34 else:
35 has_fcntl = True
37 class UnixFileLock(BaseFileLock):
38 """
39 Uses the :func:`fcntl.flock` to hard lock the lock file on unix systems.
41 Lock file cleanup: Unix and macOS delete the lock file reliably after release, even in
42 multi-threaded scenarios. Unlike Windows, Unix allows unlinking files that other processes
43 have open.
44 """
46 def _acquire(self) -> None: # noqa: C901, PLR0912
47 ensure_directory_exists(self.lock_file)
48 open_flags = os.O_RDWR | os.O_TRUNC
49 o_nofollow = getattr(os, "O_NOFOLLOW", None)
50 if o_nofollow is not None:
51 open_flags |= o_nofollow
52 open_flags |= os.O_CREAT
53 open_mode = self._open_mode()
54 try:
55 fd = os.open(self.lock_file, open_flags, open_mode)
56 except FileNotFoundError:
57 # On FUSE/NFS, os.open(O_CREAT) is not atomic: LOOKUP + CREATE can be split, allowing a concurrent
58 # unlink() to delete the file between them. For valid paths, treat ENOENT as transient contention.
59 # For invalid paths (e.g., empty string), re-raise to avoid infinite retry loops.
60 if self.lock_file and Path(self.lock_file).parent.exists():
61 return
62 raise
63 except PermissionError:
64 # Sticky-bit dirs (e.g. /tmp): O_CREAT fails if the file is owned by another user (#317).
65 # Fall back to opening the existing file without O_CREAT.
66 if not Path(self.lock_file).exists():
67 raise
68 try:
69 fd = os.open(self.lock_file, open_flags & ~os.O_CREAT, open_mode)
70 except FileNotFoundError:
71 return
72 if self.has_explicit_mode:
73 with suppress(PermissionError):
74 os.fchmod(fd, self._context.mode)
75 try:
76 fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
77 except OSError as exception:
78 os.close(fd)
79 if exception.errno == ENOSYS:
80 with suppress(OSError):
81 Path(self.lock_file).unlink()
82 self._fallback_to_soft_lock()
83 self._acquire()
84 return
85 if exception.errno not in {EAGAIN, EWOULDBLOCK}:
86 raise
87 else:
88 # The file may have been unlinked by a concurrent _release() between our open() and flock().
89 # A lock on an unlinked inode is useless — discard and let the retry loop start fresh.
90 if os.fstat(fd).st_nlink == 0:
91 os.close(fd)
92 else:
93 self._context.lock_file_fd = fd
95 def _fallback_to_soft_lock(self) -> None:
96 from ._soft import SoftFileLock # noqa: PLC0415
98 warnings.warn("flock not supported on this filesystem, falling back to SoftFileLock", stacklevel=2)
99 from .asyncio import AsyncSoftFileLock, BaseAsyncFileLock # noqa: PLC0415
101 self.__class__ = AsyncSoftFileLock if isinstance(self, BaseAsyncFileLock) else SoftFileLock
103 def _release(self) -> None:
104 fd = cast("int", self._context.lock_file_fd)
105 self._context.lock_file_fd = None
106 with suppress(OSError):
107 Path(self.lock_file).unlink()
108 fcntl.flock(fd, fcntl.LOCK_UN)
109 with suppress(OSError): # close can raise EIO on FUSE/Docker bind-mount filesystems after unlink
110 os.close(fd)
113__all__ = [
114 "UnixFileLock",
115 "has_fcntl",
116]