Skip to content

fix(cache): report a cache that refuses an invalidation as unavailable - #1047

Merged
abnegate merged 1 commit into
mainfrom
fix/cache-epoch-refusal-unavailable
Oct 23, 2026
Merged

abnegate merged 1 commit into
mainfrom
fix/cache-epoch-refusal-unavailable

Conversation

@abnegate

@abnegate abnegate commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

Problem

A write whose cache invalidation the cache refused failed with a plain RuntimeException:

RuntimeException: Failed to block document cache epoch '…:collection:database_140469_collection_11#epoch'
RuntimeException: Failed to activate document cache epoch '…:collection:database_99953_collection_44#epoch'

Appwrite Cloud translates Utopia\Cache\Exception\Unavailable into a retryable 503 (database_unavailable, or database_outcome_unknown once something committed). These are the same condition under another type, so they reached clients as a 500 general_unknown. They are still occurring on the release that already carries #1043's retries: CLOUD-3R8F, CLOUD-3R8D, CLOUD-3R94 and CLOUD-3RB1, most recently during a Dragonfly reload on 2026-10-22 22:30Z.

Fix

Throw Unavailable when the cache refuses one of these steps:

Cache Step
Document block the epoch, publish the epoch, release the owner, finish the invalidation
Query register the owner, block the epoch, release the owner, finish the invalidation, flush, activate

Unavailable extends RuntimeException, so existing handlers and the withRetries around each step are unchanged. Invalid … cache owner stays a RuntimeException, because it is a broken invariant, not an unavailable cache.

Unavailable ships in utopia-php/cache 5.2, so the requirement moves from ^4.0 || ^5.0 to ^5.2. Appwrite replaces utopia-php/cache with its own package, so this does not constrain it.

Tests

  • UnavailableCache now throws Unavailable for a purge it cannot confirm, like the real 5.2 circuit breaker.
  • CacheUnavailableTest::testAWriteTheCacheCannotInvalidateIsRolledBack asserts the failure is Unavailable. Without the fix, 4 of its 8 cases fail: RuntimeException is not an instance of Unavailable.
  • QueryTest::testInvalidationPropagatesACacheWriteFailure expects Unavailable. Fails without the fix.

Locally: the unit suite passes (13128 tests), and Pint and both PHPStan configs are clean. The one exception is SwooleAbsentTest::testEveryClassLoadsWithoutSwoole, which cannot load redis.so under -n on this host. CI's image has it.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Cache failures during invalidation and other cache operations now use a consistent cache-unavailable error, making these failures easier to identify and handle.
    • Existing failure conditions and messages remain unchanged.

When the cache refused the save that blocks or publishes a collection's
document epoch, or the purge that releases a writer or finishes its
invalidation, the write failed with a plain RuntimeException. Callers
could not tell that refusal from a bug: Appwrite Cloud translates
Utopia\Cache\Exception\Unavailable into a retryable 503, but these
reached clients as a 500 general_unknown (CLOUD-3R8F, CLOUD-3R8D,
CLOUD-3R94, CLOUD-3RB1), most recently while a Dragonfly node was
reloading its dataset.

Throw Unavailable for those refusals, and for the same steps of the
query cache. It extends RuntimeException, so existing handlers and the
retries around each step behave as before. An owner that belongs to
another writer stays a RuntimeException: that is a broken invariant,
not an unavailable cache.

Unavailable ships in utopia-php/cache 5.2, so require ^5.2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
📥 Commits

Reviewing files that changed from the base of the PR and between 86e31fb and b233aa4.

⛔ Files ignored due to path filters: 1
  • composer.lock is excluded by !**/*.lock
📒 Files selected for processing: 6
  • composer.json
  • src/Database/Cache/Query.php
  • src/Database/Trait/Documents.php
  • tests/unit/Cache/QueryTest.php
  • tests/unit/Cache/UnavailableCache.php
  • tests/unit/Documents/CacheUnavailableTest.php

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

📝 Walkthrough

Query-cache and document-cache failure paths now throw cache-specific unavailable exceptions instead of RuntimeException. The cache dependency constraint now requires version 5.2 or later within the 5.x series. Tests assert the updated exception types.

Changes: Cache exception handling

Layer / File(s) Summary
Cache failure exceptions
composer.json, src/Database/Cache/Query.php, src/Database/Trait/Documents.php, tests/unit/Cache/*, tests/unit/Documents/CacheUnavailableTest.php
Query-cache and document-cache failure paths now throw unavailable exceptions. Tests check the updated exception types. The cache dependency constraint is now ^5.2.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~8 minutes

Change: Bug fix

Merge Risk: ⚪ Minimal · up to b233a

The cache failure changes are dependency-consistent and preserve existing exception handling behavior.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 58.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 12 functions across 5 files. (1 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title names the main change: cache invalidation refusals are now reported as the Unavailable exception instead of a plain RuntimeException, for the document and query cache. The title is specific …
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title names the main change: cache invalidation refusals are now reported as the Unavailable exception instead of a plain RuntimeException, for the document and query cache. The title is specific and matches the objectives and the file summaries. The conventional-commit prefix 'fix(cache):' fits a bug-level change in the cache path.

Full details: Docstring Coverage

Explanation

Docstring coverage is 58.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 12 functions across 5 files. (1 skipped: 1 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@abnegate
abnegate merged commit bc00297 into main Oct 23, 2026
24 checks passed
@abnegate
abnegate deleted the fix/cache-epoch-refusal-unavailable branch October 23, 2026 00:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant