remote: fix StoreObjectNotFound exception lost over RPC, fixes #9380

This commit is contained in:
Mrityunjay Raj 2026-02-20 20:52:02 +05:30
parent 66051cc069
commit 4654b903c9
4 changed files with 53 additions and 11 deletions

View file

@ -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):

View file

@ -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":

View file

@ -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

View file

@ -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: