From 625c3f30468127f612723a4e75df6e194881a0e4 Mon Sep 17 00:00:00 2001 From: Josh Date: Sat, 25 Apr 2026 12:29:43 -0400 Subject: [PATCH 1/5] feat(StorageGlobal): add targeted cache invalidation helpers Signed-off-by: Josh --- lib/private/Files/Cache/StorageGlobal.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lib/private/Files/Cache/StorageGlobal.php b/lib/private/Files/Cache/StorageGlobal.php index 336f136509dfe..87116702d3a5d 100644 --- a/lib/private/Files/Cache/StorageGlobal.php +++ b/lib/private/Files/Cache/StorageGlobal.php @@ -118,7 +118,26 @@ public function getStorageInfoByNumericId(int $numericId): ?array { return $this->numericIdCache[$numericId] ?? null; } + public function clearStorageInfo(string $storageId): void { + $row = $this->cache[$storageId] ?? null; + unset($this->cache[$storageId]); + + if ($row !== null) { + unset($this->numericIdCache[$row['numeric_id']]); + } + } + + public function clearStorageInfoByNumericId(int $numericId): void { + $row = $this->numericIdCache[$numericId] ?? null; + unset($this->numericIdCache[$numericId]); + + if ($row !== null) { + unset($this->cache[$row['id']]); + } + } + public function clearCache(): void { $this->cache = []; + $this->numericIdCache = []; } } From c689aaa478171215d5b83a8515583391c53d2c8b Mon Sep 17 00:00:00 2001 From: Josh Date: Sat, 25 Apr 2026 12:32:11 -0400 Subject: [PATCH 2/5] fix(Storage): invalidate cache immediately after availability updates Signed-off-by: Josh --- lib/private/Files/Cache/Storage.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/private/Files/Cache/Storage.php b/lib/private/Files/Cache/Storage.php index 5417095f44bee..645fd04c8ceec 100644 --- a/lib/private/Files/Cache/Storage.php +++ b/lib/private/Files/Cache/Storage.php @@ -153,6 +153,8 @@ public function setAvailability(bool $isAvailable, int $delay = 0): void { ->set('last_checked', $query->createNamedParameter(time() + $delay)) ->where($query->expr()->eq('id', $query->createNamedParameter($this->storageId))); $query->executeStatement(); + + self::getGlobalCache()->clearStorageInfo($this->storageId); } /** From 358f86753e13fabcf44464800db77660cc56f659 Mon Sep 17 00:00:00 2001 From: Josh Date: Sat, 25 Apr 2026 12:44:17 -0400 Subject: [PATCH 3/5] docs(Storage): update docblock for Cache/Storage class Signed-off-by: Josh --- lib/private/Files/Cache/Storage.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/private/Files/Cache/Storage.php b/lib/private/Files/Cache/Storage.php index 645fd04c8ceec..c268352508575 100644 --- a/lib/private/Files/Cache/Storage.php +++ b/lib/private/Files/Cache/Storage.php @@ -17,15 +17,14 @@ use Psr\Log\LoggerInterface; /** - * Handle the mapping between the string and numeric storage ids + * Represents the persisted storage-id mapping for a single storage. * - * Each storage has 2 different ids - * a string id which is generated by the storage backend and reflects the configuration of the storage (e.g. 'smb://user@host/share') - * and a numeric storage id which is referenced in the file cache + * A storage has: + * - a backend-provided string identifier that reflects its configuration (e.g. 'smb://user@host/share') + * - a numeric identifier referenced in the file cache * - * A mapping between the two storage ids is stored in the database and accessible through this class - * - * @package OC\Files\Cache + * This class resolves or creates that mapping, exposes the numeric id for the + * current storage, and updates persisted storage state such as availability. */ class Storage { private string $storageId; From b9c214f0bce073908fd3880f9ea63254fbaed070 Mon Sep 17 00:00:00 2001 From: Josh Date: Sat, 25 Apr 2026 12:44:28 -0400 Subject: [PATCH 4/5] docs(Storage): update docblock for Cache/StorageGlobal class Signed-off-by: Josh --- lib/private/Files/Cache/StorageGlobal.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/private/Files/Cache/StorageGlobal.php b/lib/private/Files/Cache/StorageGlobal.php index 87116702d3a5d..b7d128d00a0a0 100644 --- a/lib/private/Files/Cache/StorageGlobal.php +++ b/lib/private/Files/Cache/StorageGlobal.php @@ -12,15 +12,14 @@ use OCP\IDBConnection; /** - * Handle the mapping between the string and numeric storage ids + * Provides a process-local cache of persisted storage-id mappings. * - * Each storage has 2 different ids - * a string id which is generated by the storage backend and reflects the configuration of the storage (e.g. 'smb://user@host/share') - * and a numeric storage id which is referenced in the file cache + * Caches lookups in both directions: + * - string storage id to storage record + * - numeric storage id to storage record * - * A mapping between the two storage ids is stored in the database and accessible through this class - * - * @package OC\Files\Cache + * This class reduces repeated database lookups for storage mapping metadata + * and provides invalidation helpers for callers that update those records. */ class StorageGlobal { /** @var array */ From 6706eeea7c39bbf483a7648d9828442b30c34932 Mon Sep 17 00:00:00 2001 From: Josh Date: Sat, 25 Apr 2026 13:12:46 -0400 Subject: [PATCH 5/5] test(Storage): add coverage of storage availability cache Signed-off-by: Josh --- tests/lib/Files/Cache/StorageTest.php | 96 +++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 tests/lib/Files/Cache/StorageTest.php diff --git a/tests/lib/Files/Cache/StorageTest.php b/tests/lib/Files/Cache/StorageTest.php new file mode 100644 index 0000000000000..75caecba31157 --- /dev/null +++ b/tests/lib/Files/Cache/StorageTest.php @@ -0,0 +1,96 @@ +connection = Server::get(IDBConnection::class); + Storage::getGlobalCache()->clearCache(); + } + + protected function tearDown(): void { + Storage::getGlobalCache()->clearCache(); + + if ($this->createdStorageId !== null) { + $query = $this->connection->getQueryBuilder(); + $query->delete('storages') + ->where( + $query->expr()->eq('id', $query->createNamedParameter($this->createdStorageId)) + ) + ->executeStatement(); + + $this->createdStorageId = null; + } + + parent::tearDown(); + } + + public function testSetAvailabilityInvalidatesGlobalCacheForStringAndNumericLookups(): void { + $storageId = 'test::availability-cache-' . uniqid('', true); + $this->createdStorageId = $storageId; + + $storage = new Storage($storageId, true, $this->connection); + $numericId = $storage->getNumericId(); + + $globalCache = Storage::getGlobalCache(); + + // Prime both process-local caches with the initial persisted state. + $initialById = $globalCache->getStorageInfo($storageId); + $initialByNumericId = $globalCache->getStorageInfoByNumericId($numericId); + + $this->assertIsArray($initialById); + $this->assertIsArray($initialByNumericId); + + $this->assertSame($storageId, $initialById['id']); + $this->assertSame($numericId, $initialById['numeric_id']); + $this->assertTrue($initialById['available']); + $this->assertSame(0, $initialById['last_checked']); + + $this->assertSame($storageId, $initialByNumericId['id']); + $this->assertSame($numericId, $initialByNumericId['numeric_id']); + $this->assertTrue($initialByNumericId['available']); + $this->assertSame(0, $initialByNumericId['last_checked']); + + $before = time(); + $delay = 600; + + $storage->setAvailability(false, $delay); + + // Both lookup directions must now observe fresh persisted state rather than + // stale data from the process-local cache. + $updatedById = $globalCache->getStorageInfo($storageId); + $updatedByNumericId = $globalCache->getStorageInfoByNumericId($numericId); + + $this->assertIsArray($updatedById); + $this->assertIsArray($updatedByNumericId); + + $this->assertSame($storageId, $updatedById['id']); + $this->assertSame($numericId, $updatedById['numeric_id']); + $this->assertFalse($updatedById['available']); + $this->assertGreaterThanOrEqual($before + $delay, $updatedById['last_checked']); + $this->assertLessThanOrEqual($before + $delay + 1, $updatedById['last_checked']); + + $this->assertSame($storageId, $updatedByNumericId['id']); + $this->assertSame($numericId, $updatedByNumericId['numeric_id']); + $this->assertFalse($updatedByNumericId['available']); + $this->assertSame($updatedById['last_checked'], $updatedByNumericId['last_checked']); + } +}