From b350801e8e3365e019667112761b8578b275b09d Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Wed, 2 Apr 2025 10:00:16 -0700 Subject: [PATCH 1/2] Enable removal of UNIX lock files This avoids leaving orphan locks files around after we are done when them. See https://github.com/emscripten-core/emscripten/pull/24035 Fixes: #31 --- src/filelock/_unix.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/filelock/_unix.py b/src/filelock/_unix.py index b2fd0f33..4b78fec4 100644 --- a/src/filelock/_unix.py +++ b/src/filelock/_unix.py @@ -52,14 +52,21 @@ def _acquire(self) -> None: msg = "FileSystem does not appear to support flock; use SoftFileLock instead" raise NotImplementedError(msg) from exception else: - self._context.lock_file_fd = fd + st = os.fstat(fd) + if st.st_nlink == 0: + # We raced with another process that deleted the lock file + # before we called fcntl.flock. This means that lock is not + # valid (since another process will just lock a different + # file) and we need to try again. + # See https://stackoverflow.com/a/51070775 + os.close(fd) + else: + self._context.lock_file_fd = fd def _release(self) -> None: - # Do not remove the lockfile: - # https://github.com/tox-dev/py-filelock/issues/31 - # https://stackoverflow.com/questions/17708885/flock-removing-locked-file-without-race-condition fd = cast("int", self._context.lock_file_fd) self._context.lock_file_fd = None + Path.unlink(self.lock_file) fcntl.flock(fd, fcntl.LOCK_UN) os.close(fd) From 54f3adfa41978ba99bcb1e11126cafb2cee56414 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Wed, 2 Apr 2025 10:55:55 -0700 Subject: [PATCH 2/2] add/update tests --- src/filelock/_unix.py | 4 +--- tests/test_filelock.py | 9 +++++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/filelock/_unix.py b/src/filelock/_unix.py index 4b78fec4..e5f93e69 100644 --- a/src/filelock/_unix.py +++ b/src/filelock/_unix.py @@ -38,9 +38,7 @@ class UnixFileLock(BaseFileLock): def _acquire(self) -> None: ensure_directory_exists(self.lock_file) - open_flags = os.O_RDWR | os.O_TRUNC - if not Path(self.lock_file).exists(): - open_flags |= os.O_CREAT + open_flags = os.O_RDWR | os.O_TRUNC | os.O_CREAT fd = os.open(self.lock_file, open_flags, self._context.mode) with suppress(PermissionError): # This locked is not owned by this UID os.fchmod(fd, self._context.mode) diff --git a/tests/test_filelock.py b/tests/test_filelock.py index b071396e..f5c87028 100644 --- a/tests/test_filelock.py +++ b/tests/test_filelock.py @@ -814,3 +814,12 @@ def __init__(self, file_path: str) -> None: lock_path = tmp_path / "a" lock = FilePathLock(str(lock_path)) assert lock.lock_file == str(lock_path) + ".lock" + + +@pytest.mark.parametrize("lock_type", [FileLock, SoftFileLock]) +def test_lock_is_removed(tmp_path: Path, lock_type: type[BaseFileLock]) -> None: + lock_path = tmp_path / "test.lock" + lock = lock_type(lock_path) + with lock: + assert Path.exists(lock_path) + assert not Path.exists(lock_path)