From d4e68ff1fcb43bbbecf3b3a9dc37056ad36ce53b Mon Sep 17 00:00:00 2001 From: deajan Date: Sun, 11 May 2025 22:26:58 +0200 Subject: [PATCH] Generalize don't acquire locks for certain ops --- npbackup/core/runner.py | 44 +++++++++++++++++++++++++++-- npbackup/restic_wrapper/__init__.py | 3 -- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/npbackup/core/runner.py b/npbackup/core/runner.py index ac9431f..4afe6d6 100644 --- a/npbackup/core/runner.py +++ b/npbackup/core/runner.py @@ -7,7 +7,7 @@ __intname__ = "npbackup.gui.core.runner" __author__ = "Orsiris de Jong" __copyright__ = "Copyright (C) 2022-2025 NetInvent" __license__ = "GPL-3.0-only" -__build__ = "2025040901" +__build__ = "2025051101" from typing import Optional, Callable, Union, List, Tuple @@ -64,6 +64,7 @@ required_permissions = { "raw": ["full"], } +# Specific operations that should never be run concurrently locking_operations = [ "backup", "repair", @@ -73,6 +74,17 @@ locking_operations = [ "unlock", ] +# Specific operations that should not lock the repository (--no-lock) +non_locking_operations = [ + "snapshots", + "stats", + "list", + "ls", + "find", + # check should not be locking, but we definitly don't want to play with fire here + # "check" +] + def metric_analyser( repo_config: dict, @@ -329,7 +341,7 @@ class NPBackupRunner: def no_lock(self) -> bool: return self._no_lock - @no_lock.setter + @no_aquire_lock.setter def no_lock(self, value: bool): if not isinstance(value, bool): msg = f"Bogus no_lock parameter given: {value}" @@ -701,6 +713,30 @@ class NPBackupRunner: return wrapper + def no_aquire_lock(fn: Callable): + """ + Don't lock some operations + """ + + @wraps(fn) + def wrapper(self, *args, **kwargs): + # pylint: disable=E1101 (no-member) + if fn.__name__ == "group_runner": + operation = kwargs.get("operation") + else: + # pylint: disable=E1101 (no-member) + operation = fn.__name__ + + no_lock = self._no_lock + if operation in non_locking_operations: + self._no_lock = True + # pylint: disable=E1102 (not-callable) + result = fn(self, *args, **kwargs) + self._no_lock = no_lock + return result + + return wrapper + def catch_exceptions(fn: Callable): """ Catch any exception and log it so we don't loose exceptions in thread @@ -1102,6 +1138,7 @@ class NPBackupRunner: @metrics @exec_timer @check_concurrency + @no_aquire_lock @has_permission @is_ready @apply_config_to_restic_runner @@ -1118,6 +1155,7 @@ class NPBackupRunner: @metrics @exec_timer @check_concurrency + @no_aquire_lock @has_permission @is_ready @apply_config_to_restic_runner @@ -1134,6 +1172,7 @@ class NPBackupRunner: @metrics @exec_timer @check_concurrency + @no_aquire_lock @has_permission @is_ready @apply_config_to_restic_runner @@ -1151,6 +1190,7 @@ class NPBackupRunner: @metrics @exec_timer @check_concurrency + @no_aquire_lock @has_permission @is_ready @apply_config_to_restic_runner diff --git a/npbackup/restic_wrapper/__init__.py b/npbackup/restic_wrapper/__init__.py index fe77331..a3a1e52 100644 --- a/npbackup/restic_wrapper/__init__.py +++ b/npbackup/restic_wrapper/__init__.py @@ -939,12 +939,9 @@ class ResticRunner: cmd = "snapshots" if id: cmd += f" {id}" - no_lock = self.no_lock - self.no_lock = True result, output = self.executor( cmd, timeout=FAST_COMMANDS_TIMEOUT, errors_allowed=errors_allowed ) - self.no_lock = no_lock if result: msg = "Snapshots listed successfully" elif errors_allowed: