From 7fedfce4f54cb4bb4e0cb516e6785ffd01caf1e2 Mon Sep 17 00:00:00 2001 From: Evgenii Alekseev Date: Sat, 14 Feb 2026 00:47:26 +0200 Subject: [PATCH] remove custom filelock --- .github/workflows/setup.sh | 2 +- docker/Dockerfile | 3 +- package/archlinux/PKGBUILD | 2 +- pyproject.toml | 1 + src/ahriman/core/repository/executor.py | 5 ++- src/ahriman/core/utils.py | 29 +------------- tests/ahriman/core/test_utils.py | 52 +------------------------ 7 files changed, 12 insertions(+), 82 deletions(-) diff --git a/.github/workflows/setup.sh b/.github/workflows/setup.sh index 2a52bfc2..6731e824 100755 --- a/.github/workflows/setup.sh +++ b/.github/workflows/setup.sh @@ -10,7 +10,7 @@ echo -e '[arcanisrepo]\nServer = https://repo.arcanis.me/$arch\nSigLevel = Never # refresh the image pacman -Syyu --noconfirm # main dependencies -pacman -S --noconfirm devtools git pyalpm python-bcrypt python-inflection python-pyelftools python-requests python-systemd sudo +pacman -S --noconfirm devtools git pyalpm python-bcrypt python-filelock python-inflection python-pyelftools python-requests python-systemd sudo # make dependencies pacman -S --noconfirm --asdeps base-devel python-build python-flit python-installer python-tox python-wheel # optional dependencies diff --git a/docker/Dockerfile b/docker/Dockerfile index b7606a6f..fa5eed5c 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -24,7 +24,8 @@ RUN pacman -S --noconfirm --asdeps \ devtools \ git \ pyalpm \ - python-bcrypt \ + python-bcrypt \ + python-filelock \ python-inflection \ python-pyelftools \ python-requests \ diff --git a/package/archlinux/PKGBUILD b/package/archlinux/PKGBUILD index 722f5c16..ad44670d 100644 --- a/package/archlinux/PKGBUILD +++ b/package/archlinux/PKGBUILD @@ -8,7 +8,7 @@ pkgdesc="ArcH linux ReposItory MANager" arch=('any') url="https://ahriman.readthedocs.io/" license=('GPL-3.0-or-later') -depends=('devtools>=1:1.0.0' 'git' 'pyalpm' 'python-bcrypt' 'python-inflection' 'python-pyelftools' 'python-requests') +depends=('devtools>=1:1.0.0' 'git' 'pyalpm' 'python-bcrypt' 'python-filelock' 'python-inflection' 'python-pyelftools' 'python-requests') makedepends=('python-build' 'python-flit' 'python-installer' 'python-wheel') source=("https://github.com/arcan1s/ahriman/releases/download/$pkgver/$pkgbase-$pkgver.tar.gz" "$pkgbase.sysusers" diff --git a/pyproject.toml b/pyproject.toml index ae839788..dc82e6ca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ authors = [ dependencies = [ "bcrypt", + "filelock", "inflection", "pyelftools", "requests", diff --git a/src/ahriman/core/repository/executor.py b/src/ahriman/core/repository/executor.py index 5265b354..20be5efc 100644 --- a/src/ahriman/core/repository/executor.py +++ b/src/ahriman/core/repository/executor.py @@ -20,6 +20,7 @@ import shutil from collections.abc import Iterable +from filelock import FileLock from pathlib import Path from tempfile import TemporaryDirectory @@ -27,7 +28,7 @@ from ahriman.core.build_tools.package_archive import PackageArchive from ahriman.core.build_tools.task import Task from ahriman.core.repository.cleaner import Cleaner from ahriman.core.repository.package_info import PackageInfo -from ahriman.core.utils import atomic_move, filelock, list_flatmap, package_like, safe_filename, symlink_relative +from ahriman.core.utils import atomic_move, list_flatmap, package_like, safe_filename, symlink_relative from ahriman.models.changes import Changes from ahriman.models.event import EventType from ahriman.models.package import Package @@ -111,7 +112,7 @@ class Executor(PackageInfo, Cleaner): self.logger.info("using prebuilt packages for %s-%s", loaded_package.base, loaded_package.version) built = [] for artifact in prebuilt: - with filelock(artifact): + with FileLock(artifact.with_name(f".{artifact.name}.lock")): shutil.copy(artifact, path) built.append(path / artifact.name) else: diff --git a/src/ahriman/core/utils.py b/src/ahriman/core/utils.py index 6eb5d150..d27fd2da 100644 --- a/src/ahriman/core/utils.py +++ b/src/ahriman/core/utils.py @@ -18,9 +18,7 @@ # along with this program. If not, see . # # pylint: disable=too-many-lines -import contextlib import datetime -import fcntl import io import itertools import logging @@ -33,6 +31,7 @@ import subprocess from collections.abc import Callable, Iterable, Iterator, Mapping from dataclasses import asdict from enum import Enum +from filelock import FileLock from pathlib import Path from pwd import getpwuid from typing import Any, IO, TypeVar @@ -48,7 +47,6 @@ __all__ = [ "dataclass_view", "enum_values", "extract_user", - "filelock", "filter_json", "full_version", "list_flatmap", @@ -89,7 +87,7 @@ def atomic_move(src: Path, dst: Path) -> None: >>> atomic_move(src, dst) """ - with filelock(dst): + with FileLock(dst.with_name(f".{dst.name}.lock")): shutil.move(src, dst) @@ -264,29 +262,6 @@ def extract_user() -> str | None: return os.getenv("SUDO_USER") or os.getenv("DOAS_USER") or os.getenv("USER") -@contextlib.contextmanager -def filelock(path: Path) -> Iterator[None]: - """ - lock on file passed as argument - - Args: - path(Path): path object on which lock must be performed - """ - lock_path = path.with_name(f".{path.name}") - try: - with lock_path.open("ab") as lock_file: - fd = lock_file.fileno() - try: - fcntl.flock(fd, fcntl.LOCK_EX) # lock file and wait lock is until available - yield - finally: - fcntl.flock(fd, fcntl.LOCK_UN) # unlock file first - finally: - # remove lock file at the end - # there might be a race condition here, but we don't care about this case - lock_path.unlink(missing_ok=True) - - def filter_json(source: dict[str, Any], known_fields: Iterable[str]) -> dict[str, Any]: """ filter json object by fields used for json-to-object conversion diff --git a/tests/ahriman/core/test_utils.py b/tests/ahriman/core/test_utils.py index 2097625c..d2649f90 100644 --- a/tests/ahriman/core/test_utils.py +++ b/tests/ahriman/core/test_utils.py @@ -1,5 +1,4 @@ import datetime -import fcntl import logging import os import pytest @@ -21,11 +20,11 @@ def test_atomic_move(mocker: MockerFixture) -> None: """ must move file with locking """ - filelock_mock = mocker.patch("ahriman.core.utils.filelock") + filelock_mock = mocker.patch("ahriman.core.utils.FileLock") move_mock = mocker.patch("shutil.move") atomic_move(Path("source"), Path("destination")) - filelock_mock.assert_called_once_with(Path("destination")) + filelock_mock.assert_called_once_with(Path(".destination.lock")) move_mock.assert_called_once_with(Path("source"), Path("destination")) @@ -248,53 +247,6 @@ def test_extract_user() -> None: assert extract_user() == "doas" -def test_filelock(mocker: MockerFixture) -> None: - """ - must perform file locking - """ - lock_mock = mocker.patch("fcntl.flock") - open_mock = mocker.patch("pathlib.Path.open", autospec=True) - unlink_mock = mocker.patch("pathlib.Path.unlink") - - with filelock(Path("local")): - pass - open_mock.assert_called_once_with(Path(".local"), "ab") - lock_mock.assert_has_calls([ - MockCall(pytest.helpers.anyvar(int), fcntl.LOCK_EX), - MockCall(pytest.helpers.anyvar(int), fcntl.LOCK_UN), - ]) - unlink_mock.assert_called_once_with(missing_ok=True) - - -def test_filelock_remove_lock(mocker: MockerFixture) -> None: - """ - must remove lock file in case of exception - """ - mocker.patch("pathlib.Path.open", side_effect=Exception) - unlink_mock = mocker.patch("pathlib.Path.unlink") - - with pytest.raises(Exception): - with filelock(Path("local")): - pass - unlink_mock.assert_called_once_with(missing_ok=True) - - -def test_filelock_unlock(mocker: MockerFixture) -> None: - """ - must unlock file in case of exception - """ - mocker.patch("pathlib.Path.open") - lock_mock = mocker.patch("fcntl.flock") - - with pytest.raises(Exception): - with filelock(Path("local")): - raise Exception - lock_mock.assert_has_calls([ - MockCall(pytest.helpers.anyvar(int), fcntl.LOCK_EX), - MockCall(pytest.helpers.anyvar(int), fcntl.LOCK_UN), - ]) - - def test_filter_json(package_ahriman: Package) -> None: """ must filter fields by known list