mirror of
https://github.com/borgbackup/borg.git
synced 2026-06-11 01:41:57 -04:00
remote: fix StoreObjectNotFound exception lost over RPC, fixes #9380
This commit is contained in:
parent
66051cc069
commit
4654b903c9
4 changed files with 53 additions and 11 deletions
|
|
@ -712,8 +712,7 @@ def delete_chunkindex_cache(repository):
|
|||
cache_name = f"cache/chunks.{hash}"
|
||||
try:
|
||||
repository.store_delete(cache_name)
|
||||
except (Repository.ObjectNotFound, StoreObjectNotFound):
|
||||
# TODO: ^ seem like RemoteRepository raises Repository.ONF instead of StoreONF
|
||||
except StoreObjectNotFound:
|
||||
pass
|
||||
logger.debug(f"cached chunk indexes deleted: {hashes}")
|
||||
|
||||
|
|
@ -769,8 +768,7 @@ def write_chunkindex_to_repo_cache(
|
|||
cache_name = f"cache/chunks.{hash}"
|
||||
try:
|
||||
repository.store_delete(cache_name)
|
||||
except (Repository.ObjectNotFound, StoreObjectNotFound):
|
||||
# TODO: ^ seem like RemoteRepository raises Repository.ONF instead of StoreONF
|
||||
except StoreObjectNotFound:
|
||||
pass
|
||||
if delete_these:
|
||||
logger.debug(f"cached chunk indexes deleted: {delete_these}")
|
||||
|
|
@ -782,8 +780,7 @@ def read_chunkindex_from_repo_cache(repository, hash):
|
|||
logger.debug(f"trying to load {cache_name} from the repo...")
|
||||
try:
|
||||
chunks_data = repository.store_load(cache_name)
|
||||
except (Repository.ObjectNotFound, StoreObjectNotFound):
|
||||
# TODO: ^ seem like RemoteRepository raises Repository.ONF instead of StoreONF
|
||||
except StoreObjectNotFound:
|
||||
logger.debug(f"{cache_name} not found in the repository.")
|
||||
else:
|
||||
if xxh64(chunks_data, seed=CHUNKINDEX_HASH_SEED) == hex_to_bin(hash):
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ from .logger import create_logger, borg_serve_log_queue
|
|||
from .manifest import NoManifestError
|
||||
from .helpers import msgpack
|
||||
from .legacyrepository import LegacyRepository
|
||||
from .repository import Repository
|
||||
from .repository import Repository, StoreObjectNotFound
|
||||
from .version import parse_version, format_version
|
||||
from .checksums import xxh64
|
||||
from .helpers.datastruct import EfficientCollectionQueue
|
||||
|
|
@ -269,11 +269,17 @@ class RepositoryServer: # pragma: no cover
|
|||
logging.debug("\n".join(ex_full))
|
||||
|
||||
sys_info = sysinfo()
|
||||
# StoreObjectNotFound and Repository.ObjectNotFound both have
|
||||
# __name__ == "ObjectNotFound", so we need to distinguish them
|
||||
# explicitly for correct client-side reconstruction.
|
||||
exc_cls_name = (
|
||||
"StoreObjectNotFound" if isinstance(e, StoreObjectNotFound) else e.__class__.__name__
|
||||
)
|
||||
try:
|
||||
msg = msgpack.packb(
|
||||
{
|
||||
MSGID: msgid,
|
||||
"exception_class": e.__class__.__name__,
|
||||
"exception_class": exc_cls_name,
|
||||
"exception_args": e.args,
|
||||
"exception_full": ex_full,
|
||||
"exception_short": ex_short,
|
||||
|
|
@ -285,7 +291,7 @@ class RepositoryServer: # pragma: no cover
|
|||
msg = msgpack.packb(
|
||||
{
|
||||
MSGID: msgid,
|
||||
"exception_class": e.__class__.__name__,
|
||||
"exception_class": exc_cls_name,
|
||||
"exception_args": [
|
||||
x if isinstance(x, (str, bytes, int)) else None for x in e.args
|
||||
],
|
||||
|
|
@ -403,6 +409,8 @@ class RepositoryServer: # pragma: no cover
|
|||
raise PathNotAllowed("foo")
|
||||
elif kind == "ObjectNotFound":
|
||||
raise self.RepoCls.ObjectNotFound(s1, s2)
|
||||
elif kind == "StoreObjectNotFound":
|
||||
raise StoreObjectNotFound(s1)
|
||||
elif kind == "InvalidRPCMethod":
|
||||
raise InvalidRPCMethod(s1)
|
||||
elif kind == "divide":
|
||||
|
|
@ -784,6 +792,8 @@ class RemoteRepository:
|
|||
raise Repository.ParentPathDoesNotExist(args[0])
|
||||
elif error == "ObjectNotFound":
|
||||
raise Repository.ObjectNotFound(args[0], self.location.processed)
|
||||
elif error == "StoreObjectNotFound":
|
||||
raise StoreObjectNotFound(args[0])
|
||||
elif error == "InvalidRPCMethod":
|
||||
raise InvalidRPCMethod(args[0])
|
||||
elif error == "LockTimeout":
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import pytest
|
|||
from .hashindex_test import H
|
||||
from .crypto.key_test import TestKey
|
||||
from ..archive import Statistics
|
||||
from ..cache import AdHocWithFilesCache
|
||||
from ..cache import AdHocWithFilesCache, delete_chunkindex_cache, read_chunkindex_from_repo_cache
|
||||
from ..crypto.key import AESOCBRepoKey
|
||||
from ..manifest import Manifest
|
||||
from ..repository import Repository
|
||||
|
|
@ -53,3 +53,32 @@ class TestAdHocWithFilesCache:
|
|||
assert cache.file_known_and_unchanged(b"foo", bytes(32), st) == (False, None)
|
||||
assert cache.cache_mode == "d"
|
||||
assert cache.files == {}
|
||||
|
||||
|
||||
def test_delete_chunkindex_cache_missing(tmp_path):
|
||||
"""delete_chunkindex_cache handles StoreObjectNotFound when cache entries do not exist."""
|
||||
from borgstore.store import ObjectNotFound as StoreObjectNotFound
|
||||
|
||||
repository_location = os.fspath(tmp_path / "repository")
|
||||
with Repository(repository_location, exclusive=True, create=True) as repository:
|
||||
# Create a cache entry so list_chunkindex_hashes finds it.
|
||||
repository.store_store(f"cache/chunks.{'a' * 64}", b"data")
|
||||
# Patch store_delete to raise StoreObjectNotFound (simulates a race or already-deleted entry).
|
||||
original_store_delete = repository.store_delete
|
||||
|
||||
def failing_store_delete(name):
|
||||
raise StoreObjectNotFound(name)
|
||||
|
||||
repository.store_delete = failing_store_delete
|
||||
# Should not raise — the except StoreObjectNotFound catches it.
|
||||
delete_chunkindex_cache(repository)
|
||||
repository.store_delete = original_store_delete
|
||||
|
||||
|
||||
def test_read_chunkindex_from_repo_cache_missing(tmp_path):
|
||||
"""read_chunkindex_from_repo_cache handles StoreObjectNotFound when cache does not exist."""
|
||||
repository_location = os.fspath(tmp_path / "repository")
|
||||
with Repository(repository_location, exclusive=True, create=True) as repository:
|
||||
# Try to load a non-existent cache entry — should return None, not raise.
|
||||
result = read_chunkindex_from_repo_cache(repository, "f" * 64)
|
||||
assert result is None
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from ..helpers import Location
|
|||
from ..helpers import IntegrityError
|
||||
from ..platformflags import is_win32
|
||||
from ..remote import RemoteRepository, InvalidRPCMethod, PathNotAllowed
|
||||
from ..repository import Repository, MAX_DATA_SIZE
|
||||
from ..repository import Repository, StoreObjectNotFound, MAX_DATA_SIZE
|
||||
from ..repoobj import RepoObj
|
||||
from .hashindex_test import H
|
||||
|
||||
|
|
@ -214,6 +214,12 @@ def test_remote_rpc_exception_transport(remote_repository):
|
|||
assert e.args[0] == s1
|
||||
assert e.args[1] == remote_repository.location.processed
|
||||
|
||||
try:
|
||||
remote_repository.call("inject_exception", {"kind": "StoreObjectNotFound"})
|
||||
except StoreObjectNotFound as e:
|
||||
assert len(e.args) == 1
|
||||
assert e.args[0] == s1
|
||||
|
||||
try:
|
||||
remote_repository.call("inject_exception", {"kind": "InvalidRPCMethod"})
|
||||
except InvalidRPCMethod as e:
|
||||
|
|
|
|||
Loading…
Reference in a new issue