From 0b50bb6b8128a878991370d4a8ef81385c837867 Mon Sep 17 00:00:00 2001 From: Dillard Blom Date: Thu, 8 Oct 2026 01:21:15 +0200 Subject: [PATCH] Verify: retry storage exceptions only, let programming errors surface Catch \Exception (storage adapters throw their own exception types) instead of \Throwable, so an \Error such as a TypeError is not retried and reported as an unreadable blob. --- lib/Repository/Repository.php | 2 +- tests/Unit/Repository/VerifyTest.php | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/Repository/Repository.php b/lib/Repository/Repository.php index f45a4e2..9a383ae 100644 --- a/lib/Repository/Repository.php +++ b/lib/Repository/Repository.php @@ -840,7 +840,7 @@ final class Repository { for ($attempt = 1; $attempt <= self::VERIFY_READ_ATTEMPTS && $encrypted === null; $attempt++) { try { $encrypted = $this->backend->getRange('packs/' . substr($pack, 0, 2) . '/' . $pack, $offset, $length); - } catch (\Throwable $e) { + } catch (\Exception $e) { // storage adapters throw their own exceptions; \Error (a bug) is not retried $stats['readError'] = $e->getMessage(); } } diff --git a/tests/Unit/Repository/VerifyTest.php b/tests/Unit/Repository/VerifyTest.php index 88057e6..117321f 100644 --- a/tests/Unit/Repository/VerifyTest.php +++ b/tests/Unit/Repository/VerifyTest.php @@ -118,6 +118,15 @@ class VerifyTest extends TestCase { $this->assertNotSame([], $result['unreadable']); $this->assertSame('connection reset', $result['readError']); } + + public function testDeepVerifyDoesNotHideAProgrammingError(): void { + $repo = Repository::initWithKey($this->backend, $this->keys, $this->keys->wrap('pw')); + $s = $repo->backupDirectory($this->src); + $buggy = new FlakyRangeBackend($this->backend, failures: PHP_INT_MAX, error: new \TypeError('bug')); + + $this->expectException(\TypeError::class); + Repository::openWithKey($buggy, $this->keys, null, null)->verify($s['snapshot'], deep: true); + } } /** Delegates to a real backend, but getRange() throws for the first $failures calls. */ @@ -125,6 +134,7 @@ final class FlakyRangeBackend implements IBackend { public function __construct( private IBackend $inner, private int $failures, + private ?\Throwable $error = null, ) { } @@ -139,7 +149,7 @@ final class FlakyRangeBackend implements IBackend { public function getRange(string $path, int $offset, int $length): string { if ($this->failures > 0) { $this->failures--; - throw new BackendException('connection reset'); + throw $this->error ?? new BackendException('connection reset'); } return $this->inner->getRange($path, $offset, $length); }