Skip to content

Comments

feat: Support async cache stores in revalidation#4826

Merged
metcoder95 merged 3 commits intonodejs:mainfrom
marcopiraccini:fix/cache-async-store-revalidation
Feb 13, 2026
Merged

feat: Support async cache stores in revalidation#4826
metcoder95 merged 3 commits intonodejs:mainfrom
marcopiraccini:fix/cache-async-store-revalidation

Conversation

@marcopiraccini
Copy link
Contributor

@marcopiraccini marcopiraccini commented Feb 12, 2026

This relates to...

Cache revalidation failures when using async cache stores (e.g. Redis-backed stores accessed via ITC/remote proxies).

Rationale

When using an async cache store (one whose get() returns a Promise rather than a synchronous value), the cache interceptor has three bugs that prevent proper revalidation:

  1. Null vary values spread into revalidation headers: parseVaryHeader stores null for request headers that were absent. During revalidation, these null values are spread into the outgoing request headers via ...result.vary, which can cause upstream servers to fail.
  2. 304 handler assumes synchronous store.get(): The 304 Not Modified handler in CacheHandler calls this.#store.get() and immediately checks the result. For async stores, this returns a Promise (truthy), so the null check passes, but Promise.body is undefined, causing the handler to bail out without refreshing the cache entry.
  3. 304 handler assumes array body: The 304 handler calls cachedValue.body.values() which only works for array bodies. Async/remote cache stores may return body as a Readable stream, causing a runtime error.

The combined effect is that cache entries are never refreshed after becoming stale — responses are served with warning: 110 - "response is stale" indefinitely, and the stale-while-revalidate background revalidation silently fails to update the cache.

Changes

  • In lib/interceptor/cache.js, replaced headers = { ...headers, ...result.vary } with a loop that skips entries where the value is null, in both the stale-while-revalidate background revalidation path and the foreground CacheRevalidationHandler path.
  • In lib/handler/cache-handler.js, extracted the 304 handling logic into a handle304 callback. After calling this.#store.get(), the result is checked for a .then() method: if it's a Promise (async store), handle304 is deferred via .then(); otherwise it's called synchronously.
  • In lib/handler/cache-handler.js, added a Readable stream branch in the 304 handler. When cachedValue.body has a .values() method, the existing array iteration logic is used. When it has an .on() method (Readable stream), the body is piped through the write stream using data/end/error events, with an explicit writeStream.end() on the stream's end event (since onResponseEnd may have already fired before the async handle304 runs).

Features

  • Cache stores with async get() (returning a Promise) are now fully supported in the revalidation paths, including 304 Not Modified handling and stale-while-revalidate background revalidation.
  • Cache stores returning body as a Readable stream (instead of an array of Buffers) are now supported in the 304 handler.

Bug Fixes

N/A

Breaking Changes and Deprecations

N/A

Status

@marcopiraccini marcopiraccini changed the title Support async cache stores in revalidation paths Support async cache stores in revalidation Feb 12, 2026
Copy link
Contributor

@ShogunPanda ShogunPanda left a comment

Choose a reason for hiding this comment

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

Nice find!

@codecov-commenter
Copy link

codecov-commenter commented Feb 12, 2026

Codecov Report

❌ Patch coverage is 87.35632% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.19%. Comparing base (2453caf) to head (e418892).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
lib/handler/cache-handler.js 85.71% 11 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4826      +/-   ##
==========================================
+ Coverage   93.17%   93.19%   +0.01%     
==========================================
  Files         109      109              
  Lines       34187    34222      +35     
==========================================
+ Hits        31855    31892      +37     
+ Misses       2332     2330       -2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Member

@metcoder95 metcoder95 left a comment

Choose a reason for hiding this comment

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

lgtm, just need some documentation

@marcopiraccini marcopiraccini marked this pull request as ready for review February 13, 2026 11:04
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

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

lgtm

@metcoder95 metcoder95 changed the title Support async cache stores in revalidation feat: Support async cache stores in revalidation Feb 13, 2026
@metcoder95 metcoder95 merged commit f3c5c61 into nodejs:main Feb 13, 2026
36 of 37 checks passed
@github-actions github-actions bot mentioned this pull request Feb 13, 2026
slagiewka pushed a commit to slagiewka/undici that referenced this pull request Feb 14, 2026
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.

5 participants