fix(backups): robustness against stale locks: is_initted

backups-forget
Houkime 2023-08-09 13:58:53 +00:00
parent eca4b26a31
commit 26ab7b4d7b
2 changed files with 19 additions and 1 deletions

View File

@ -256,6 +256,7 @@ class ResticBackupper(AbstractBackupper):
if "created restic repository" not in output:
raise ValueError("cannot init a repo: " + output)
@unlocked_repo
def is_initted(self) -> bool:
command = self.restic_command(
"check",
@ -267,9 +268,10 @@ class ResticBackupper(AbstractBackupper):
shell=False,
stderr=subprocess.STDOUT,
) as handle:
# communication forces to complete and for returncode to get defined
output = handle.communicate()[0].decode("utf-8")
if handle.returncode != 0:
if "unable to create lock" in output:
raise ValueError("Stale lock detected: ", output)
return False
return True
@ -319,6 +321,7 @@ class ResticBackupper(AbstractBackupper):
except Exception as e:
raise ValueError("could not lock repository") from e
@unlocked_repo
def restored_size(self, snapshot_id: str) -> int:
"""
Size of a snapshot

View File

@ -776,6 +776,21 @@ def test_double_lock_unlock(backups, dummy_service):
def test_operations_while_locked(backups, dummy_service):
# Stale lock prevention test
# consider making it fully at the level of backupper?
# because this is where prevention lives?
# Backups singleton is here only so that we can run this against B2, S3 and whatever
# But maybe it is not necessary (if restic treats them uniformly enough)
Backups.provider().backupper.lock()
snap = Backups.back_up(dummy_service)
assert snap is not None
Backups.provider().backupper.lock()
# using lowlevel to make sure no caching interferes
assert Backups.provider().backupper.is_initted() is True
# check that no locks were left
Backups.provider().backupper.lock()
Backups.provider().backupper.unlock()