Skip to content

fix(cache): handle eviction promise rejections and use waitUntil - #16

Merged
pi0 merged 4 commits into
unjs:mainfrom
zsilbi:fix/evict-promises
Jun 10, 2026
Merged

fix(cache): handle eviction promise rejections and use waitUntil#16
pi0 merged 4 commits into
unjs:mainfrom
zsilbi:fix/evict-promises

Conversation

@zsilbi

@zsilbi zsilbi commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Based on the review in #15 (comment)

_evictFromStorage was calling storage.set(..., null) in a bare for loop with no promise handling, leaving rejections from async storage backends unhandled.

  • _evictFromStorage now returns Promise.all(...) instead of fire-and-forgetting in a loop
  • Both call sites (resolver rejection + invalid revalidation result) attach a .catch that routes errors through the existing _onError hook
  • When event.req.waitUntil is available (srvx / Cloudflare Workers pattern), the eviction promise is registered there so the runtime keeps the request context alive until the eviction completes

Summary by CodeRabbit

  • Bug Fixes
    • Cache eviction now runs asynchronously, avoids blocking requests, and reports eviction errors to the configured error handler; eviction is scheduled with the request lifecycle when available.
  • Tests
    • Added tests ensuring eviction/storage failures don't hide original errors and that eviction errors are reported once.
  • Documentation
    • Updated docs to recommend optional access for request lifecycle scheduling.

@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b138bc3a-4904-43da-816d-b44b37f7faa4

📥 Commits

Reviewing files that changed from the base of the PR and between 06da63f and 08fe716.

📒 Files selected for processing (3)
  • AGENTS.md
  • src/cache.ts
  • test/index.test.ts

📝 Walkthrough

Walkthrough

Eviction now runs concurrently across all storage bases via Promise.all. Eviction calls are caught and forwarded to _onError; eviction promises are scheduled with event?.req.waitUntil?.(...). Unsafe any-casts accessing waitUntil were removed and replaced with optional chaining.

Changes

Cache eviction & scheduling

Layer / File(s) Summary
Eviction implementation
src/cache.ts
_evictFromStorage now evicts across base tiers concurrently using Promise.all(...) of useStorage().set(..., null).
Eviction scheduling & waitUntil
src/cache.ts
Eviction calls are wrapped with .catch(...) to call _onError("[cache] Cache eviction error.", error) and eviction promises are scheduled via event?.req.waitUntil?.(...). Replaced any casts with optional chaining for waitUntil and expired-path scheduling.
Tests: eviction error propagation
test/index.test.ts
Added Vitest cases that assert async storage.set rejection and sync storage.set throws during eviction do not mask resolver errors and that onError receives a single eviction error.
Docs: waitUntil typing note
AGENTS.md
Document waitUntil as optional on event.req and recommend using event?.req.waitUntil?.(...) instead of any casts.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    rect rgba(200,230,255,0.5)
    Caller->>+CacheModule: trigger eviction / SWR revalidation paths
    end
    rect rgba(220,255,200,0.5)
    CacheModule->>+StorageTierA: set(key, null)
    CacheModule->>+StorageTierB: set(key, null)
    CacheModule->>+StorageTierN: set(key, null)
    end
    rect rgba(255,230,200,0.5)
    StorageTierA-->>-CacheModule: Promise resolves/rejects
    StorageTierB-->>-CacheModule: Promise resolves/rejects
    StorageTierN-->>-CacheModule: Promise resolves/rejects
    end
    CacheModule->>CacheModule: Promise.all(...) aggregates
    alt eviction error
        CacheModule->>Event/_onError: _onError("[cache] Cache eviction error.", err)
    end
    opt event.req.waitUntil available
        CacheModule->>Event: event.req.waitUntil(evictPromise)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nudge the cache, let promises glide,

Evictions run in parallel, side by side.
I catch the faults and whisper to _onError,
waitUntil holds the work, quiet and sure.
A tidy hop — async, safe, and spry.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically identifies the main changes: fixing promise rejection handling in cache eviction and implementing waitUntil usage, which directly match the PR's core objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

@zsilbi

This comment has been minimized.

@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/cache.ts`:
- Around line 140-142: Replace direct runtime-specific accesses to
event.req.waitUntil with the repo-standard pattern by reading the waitUntil
function once into a local const (e.g., const waitUntil = (event.req as
any).waitUntil) and then call waitUntil(evictPromise) at each site; update all
four call sites that currently call event.req.waitUntil(...) to use the local
waitUntil reference (so references like event.req.waitUntil(evictPromise) become
waitUntil(evictPromise)) to avoid repetition and ensure compatibility with
srvx/Cloudflare runtimes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0a871130-3b08-4c11-85e5-2143c421e63f

📥 Commits

Reviewing files that changed from the base of the PR and between a2a761d and 5b17fa0.

📒 Files selected for processing (1)
  • src/cache.ts

Comment thread src/cache.ts Outdated
zsilbi and others added 3 commits April 15, 2026 22:09
Make _evictFromStorage async so a synchronous throw from storage.set
becomes a rejection handled by the callers' catch, instead of masking
the original resolver error. Add tests for both async and sync eviction
errors and update the stale waitUntil note in AGENTS.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@pi0 pi0 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks ❤️

@pi0
pi0 merged commit 9ec6ade into unjs:main Jun 10, 2026
0 of 3 checks passed
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.

2 participants