MCPcopy Create free account
hub / github.com/tox-dev/filelock / SoftFileLock

Class SoftFileLock

src/filelock/_soft.py:23–203  ·  view source on GitHub ↗

Cooperative file lock based on a shared existence marker. Unlike :class:`UnixFileLock ` and :class:`WindowsFileLock `, this lock does not use OS-level locking primitives. Instead, it creates the lock file with ``O_CREAT | O_EXCL`` and

Source from the content-addressed store, hash-verified

21
22
23class SoftFileLock(BaseFileLock):
24 """
25 Cooperative file lock based on a shared existence marker.
26
27 Unlike :class:`UnixFileLock <filelock.UnixFileLock>` and :class:`WindowsFileLock <filelock.WindowsFileLock>`, this
28 lock does not use OS-level locking primitives. Instead, it creates the lock file with ``O_CREAT | O_EXCL`` and
29 treats its existence as the lock indicator. The filesystem must provide coherent exclusive creation and directory
30 updates to each participating process. A crash can leave the marker behind.
31
32 The marker contains the holder&#x27;s PID and hostname. A contender may remove it when it can no longer find a same-host
33 process with that PID. A configured :attr:`~filelock.BaseFileLock.lifetime` also permits removal based on marker
34 age, including while the holder remains alive. Age-based expiry can overlap protected operations and does not
35 provide strict mutual exclusion.
36
37 """
38
39 #: Existence locks reclaim by unlinking a pathname, so an age-based lease may break one; a native inode lock cannot.
40 _lifetime_supported: bool = True
41
42 #: Age-based expiry preserves historical behavior but does not provide strict mutual exclusion.
43 _lifetime_replacements: tuple[str, str] | None = ("StrictSoftFileLock", "SoftFileLease")
44
45 #: An existence lock unlinks its marker to release, so it cannot promise to keep the pathname.
46 _preserve_lock_file_supported: bool = False
47
48 #: An existence lock keeps protocol state in its marker, so it cannot lend the descriptor to an on_acquired hook.
49 _on_acquired_supported: bool = False
50
51 def _acquire(self) -> None:
52 raise_on_not_writable_file(self.lock_file)
53 ensure_directory_exists(self.lock_file)
54 # O_CREAT | O_EXCL makes the create fail with EEXIST when the file already exists, so a successful open
55 # means this process now holds the lock.
56 flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_TRUNC
57 if (o_nofollow := getattr(os, "O_NOFOLLOW", None)) is not None: # pragma: needs o-nofollow
58 flags |= o_nofollow
59 try:
60 fd = os.open(self.lock_file, flags, self._open_mode())
61 except OSError as exception:
62 if not (
63 exception.errno == EEXIST or (exception.errno == EACCES and sys.platform == "win32")
64 ): # pragma: win32 no cover
65 raise
66 self._try_break_stale_lock()
67 return
68 self._mark_descriptor_pending(fd)
69 self._publish_held_marker(fd)
70
71 def _publish_held_marker(self, fd: int) -> None:
72 # Publish held state only once the record is fully on disk. On any failure, including cancellation, close the
73 # descriptor and unlink the path only while it still names the file we opened, so a rollback never deletes a
74 # successor's marker that replaced ours at the same path after our lease expired.
75 identity: tuple[int, int] | None = None
76 try:
77 identity = _file_identity(os.fstat(fd))
78 self._write_lock_info(fd)
79 except BaseException:
80 self._mark_descriptor_released()

Calls

no outgoing calls