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

Class SoftFileLock

src/filelock/_soft.py:21–201  ·  view source on GitHub ↗

Portable file lock based on file existence. 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 treats its e

Source from the content-addressed store, hash-verified

19
20
21class SoftFileLock(BaseFileLock):
22 """
23 Portable file lock based on file existence.
24
25 Unlike :class:`UnixFileLock <filelock.UnixFileLock>` and :class:`WindowsFileLock <filelock.WindowsFileLock>`, this
26 lock does not use OS-level locking primitives. Instead, it creates the lock file with ``O_CREAT | O_EXCL`` and
27 treats its existence as the lock indicator. This makes it work on any filesystem but leaves stale lock files behind
28 if the process crashes without releasing the lock.
29
30 To mitigate stale locks, the lock file contains the PID and hostname of the holding process. On contention, if the
31 holder is on the same host and its PID no longer exists, the stale lock is broken automatically.
32
33 """
34
35 def _acquire(self) -> None:
36 raise_on_not_writable_file(self.lock_file)
37 ensure_directory_exists(self.lock_file)
38 flags = (
39 os.O_WRONLY # open for writing only
40 | os.O_CREAT
41 | os.O_EXCL # together with above raise EEXIST if the file specified by filename exists
42 | os.O_TRUNC # truncate the file to zero byte
43 )
44 if (o_nofollow := getattr(os, "O_NOFOLLOW", None)) is not None:
45 flags |= o_nofollow
46 try:
47 file_handler = os.open(self.lock_file, flags, self._open_mode())
48 except OSError as exception:
49 if not (
50 exception.errno == EEXIST or (exception.errno == EACCES and sys.platform == "win32")
51 ): # pragma: win32 no cover
52 raise
53 self._try_break_stale_lock()
54 else:
55 self._write_lock_info(file_handler)
56 self._context.lock_file_fd = file_handler
57
58 def _try_break_stale_lock(self) -> None:
59 with suppress(OSError, ValueError):
60 content, mtime, ino = _read_lock_file(self.lock_file)
61 holder = _parse_lock_holder(content)
62
63 if holder is None:
64 # Unparsable: wrong line count, a non-integer PID or creation time, empty, oversized or not UTF-8.
65 # Self-heal only once the file is clearly not a half-written fresh lock (a peer between O_EXCL and
66 # _write_lock_info), so the brief create-then-write window is never mistaken for a stale lock.
67 if time.time() - mtime >= _MALFORMED_LOCK_AGE_THRESHOLD:
68 break_lock_file(self.lock_file, mtime, ino)
69 return
70
71 pid, hostname, creation_time = holder
72 if hostname != socket.gethostname():
73 return
74
75 if self._is_process_alive(pid):
76 if sys.platform != "win32" or creation_time is None: # pragma: win32 no cover
77 return # same process, or no creation time to disambiguate a recycled PID — don't evict
78 actual = self._get_process_creation_time(pid) # pragma: win32 cover

Callers 15

__init__Method · 0.90
_assert_self_healsFunction · 0.90
_assert_times_outFunction · 0.90
test_pidFunction · 0.90
test_pid_while_lockedFunction · 0.90
test_is_lock_held_by_usFunction · 0.90
test_break_lockFunction · 0.90

Calls

no outgoing calls

Used in the wild real call sites across dependent graphs

searching dependent graphs…