Skip to content

fix(cache): freshen the stored entry when a 304 extends its lifetime - #5700

Open
RaphaelFakhri wants to merge 1 commit into
nodejs:mainfrom
RaphaelFakhri:fix/rfc9111-304-updates-stored-entry
Open

fix(cache): freshen the stored entry when a 304 extends its lifetime#5700
RaphaelFakhri wants to merge 1 commit into
nodejs:mainfrom
RaphaelFakhri:fix/rfc9111-304-updates-stored-entry

Conversation

@RaphaelFakhri

Copy link
Copy Markdown

Closes #5505.

On the synchronous revalidation path CacheRevalidationHandler short-circuits on a 304 and never forwards it, so the RFC 9111 section 4.3.4 merge that already exists in CacheHandler's 304 branch only ever runs on the stale-while-revalidate background path. A 304 carrying a new Cache-Control was handled by deleting the entry instead, so an origin extending freshness with 304 + Cache-Control: max-age=60, which is what a CDN in front of an origin does, bought nothing: the next request was a full refetch and the one after that revalidated again.

The interceptor's revalidation callback can now return true to ask for the validation response to be forwarded to the wrapped CacheHandler after all. It does that only when the 304's Cache-Control actually grants a new lifetime (max-age or s-maxage, and no no-cache or no-store), serves the client the cached value itself as before, and gates everything the CacheHandler emits downstream so the client is served exactly once. A 304 whose Cache-Control does not grant a lifetime keeps the existing behaviour and still evicts the entry.

Tests: one regression test in test/interceptors/cache.js covering max-age carried by a 304, verified to fail without the lib change. The conformance skip list is untouched: the 304-etag-update-response-* cases need arbitrary stored headers to be merged from a 304 that carries no freshness information at all, which this change deliberately does not do.

Local runs: test/interceptors/cache.js 88 pass, npm run test:cache-interceptor 76 pass, npm run test:cache-tests identical to main (276 tests, 218 passed, 0 failed, 54 failed-optional, 4 setup-failed), npm run lint clean.

On the synchronous revalidation path CacheRevalidationHandler short-circuits
on a 304 and never forwards it to the CacheHandler, so the merge logic that
already exists there never runs. A 304 carrying a new Cache-Control was
handled by deleting the entry instead, which means an origin extending
freshness with `304 + Cache-Control: max-age=60`, the usual CDN behaviour,
got no benefit at all: the next request was a full refetch, and the one
after that revalidated again.

RFC 9111 section 4.3.4 asks the cache to update the stored response from the
validation response and recompute its freshness. When the 304 hands over a
new lifetime, forward it to the CacheHandler so the existing 304 branch
merges it into the stored entry, and serve the client the cached value from
the interceptor as before. Everything the CacheHandler emits downstream in
that case is dropped, since the client has already been served.

A 304 whose Cache-Control does not grant a lifetime (no-cache, no-store) is
left on the previous path and still evicts the entry.
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.29630% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.35%. Comparing base (f387057) to head (2f82efe).
⚠️ Report is 6 commits behind head on main.

Files with missing lines Patch % Lines
lib/interceptor/cache.js 95.16% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5700      +/-   ##
==========================================
- Coverage   93.47%   93.35%   -0.12%     
==========================================
  Files         110      110              
  Lines       38846    38919      +73     
==========================================
+ Hits        36310    36333      +23     
- Misses       2536     2586      +50     

☔ View full report in Codecov by Harness.
📢 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.

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.

cache: 304 revalidation response never updates the stored entry on the synchronous path (RFC 9111 §4.3.4) — entry stays stale forever

2 participants