remove custom filelock

This commit is contained in:
2026-02-14 00:47:26 +02:00
parent 123118d3c9
commit 7fedfce4f5
7 changed files with 12 additions and 82 deletions

View File

@@ -10,7 +10,7 @@ echo -e '[arcanisrepo]\nServer = https://repo.arcanis.me/$arch\nSigLevel = Never
# refresh the image # refresh the image
pacman -Syyu --noconfirm pacman -Syyu --noconfirm
# main dependencies # 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 # make dependencies
pacman -S --noconfirm --asdeps base-devel python-build python-flit python-installer python-tox python-wheel pacman -S --noconfirm --asdeps base-devel python-build python-flit python-installer python-tox python-wheel
# optional dependencies # optional dependencies

View File

@@ -25,6 +25,7 @@ RUN pacman -S --noconfirm --asdeps \
git \ git \
pyalpm \ pyalpm \
python-bcrypt \ python-bcrypt \
python-filelock \
python-inflection \ python-inflection \
python-pyelftools \ python-pyelftools \
python-requests \ python-requests \

View File

@@ -8,7 +8,7 @@ pkgdesc="ArcH linux ReposItory MANager"
arch=('any') arch=('any')
url="https://ahriman.readthedocs.io/" url="https://ahriman.readthedocs.io/"
license=('GPL-3.0-or-later') 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') makedepends=('python-build' 'python-flit' 'python-installer' 'python-wheel')
source=("https://github.com/arcan1s/ahriman/releases/download/$pkgver/$pkgbase-$pkgver.tar.gz" source=("https://github.com/arcan1s/ahriman/releases/download/$pkgver/$pkgbase-$pkgver.tar.gz"
"$pkgbase.sysusers" "$pkgbase.sysusers"

View File

@@ -18,6 +18,7 @@ authors = [
dependencies = [ dependencies = [
"bcrypt", "bcrypt",
"filelock",
"inflection", "inflection",
"pyelftools", "pyelftools",
"requests", "requests",

View File

@@ -20,6 +20,7 @@
import shutil import shutil
from collections.abc import Iterable from collections.abc import Iterable
from filelock import FileLock
from pathlib import Path from pathlib import Path
from tempfile import TemporaryDirectory 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.build_tools.task import Task
from ahriman.core.repository.cleaner import Cleaner from ahriman.core.repository.cleaner import Cleaner
from ahriman.core.repository.package_info import PackageInfo 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.changes import Changes
from ahriman.models.event import EventType from ahriman.models.event import EventType
from ahriman.models.package import Package 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) self.logger.info("using prebuilt packages for %s-%s", loaded_package.base, loaded_package.version)
built = [] built = []
for artifact in prebuilt: for artifact in prebuilt:
with filelock(artifact): with FileLock(artifact.with_name(f".{artifact.name}.lock")):
shutil.copy(artifact, path) shutil.copy(artifact, path)
built.append(path / artifact.name) built.append(path / artifact.name)
else: else:

View File

@@ -18,9 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
# pylint: disable=too-many-lines # pylint: disable=too-many-lines
import contextlib
import datetime import datetime
import fcntl
import io import io
import itertools import itertools
import logging import logging
@@ -33,6 +31,7 @@ import subprocess
from collections.abc import Callable, Iterable, Iterator, Mapping from collections.abc import Callable, Iterable, Iterator, Mapping
from dataclasses import asdict from dataclasses import asdict
from enum import Enum from enum import Enum
from filelock import FileLock
from pathlib import Path from pathlib import Path
from pwd import getpwuid from pwd import getpwuid
from typing import Any, IO, TypeVar from typing import Any, IO, TypeVar
@@ -48,7 +47,6 @@ __all__ = [
"dataclass_view", "dataclass_view",
"enum_values", "enum_values",
"extract_user", "extract_user",
"filelock",
"filter_json", "filter_json",
"full_version", "full_version",
"list_flatmap", "list_flatmap",
@@ -89,7 +87,7 @@ def atomic_move(src: Path, dst: Path) -> None:
>>> atomic_move(src, dst) >>> atomic_move(src, dst)
""" """
with filelock(dst): with FileLock(dst.with_name(f".{dst.name}.lock")):
shutil.move(src, dst) 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") 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]: 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 filter json object by fields used for json-to-object conversion

View File

@@ -1,5 +1,4 @@
import datetime import datetime
import fcntl
import logging import logging
import os import os
import pytest import pytest
@@ -21,11 +20,11 @@ def test_atomic_move(mocker: MockerFixture) -> None:
""" """
must move file with locking 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") move_mock = mocker.patch("shutil.move")
atomic_move(Path("source"), Path("destination")) 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")) move_mock.assert_called_once_with(Path("source"), Path("destination"))
@@ -248,53 +247,6 @@ def test_extract_user() -> None:
assert extract_user() == "doas" 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: def test_filter_json(package_ahriman: Package) -> None:
""" """
must filter fields by known list must filter fields by known list