Skip to content

fix(cache): preserve 304 response event order with async stores - #5797

Open
marcopiraccini wants to merge 7 commits into
nodejs:mainfrom
marcopiraccini:fix/cache-handler-304-async-store
Open

marcopiraccini wants to merge 7 commits into
nodejs:mainfrom
marcopiraccini:fix/cache-handler-304-async-store

Conversation

@marcopiraccini

@marcopiraccini marcopiraccini commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

This relates to...

N/A

Rationale

A cache revalidation can receive a 304 before an asynchronous store has returned the cached response. The 304 itself has no body, but the cached body must be replayed before downstream receives the response end.

Changes

  • Pause a reusable 304 while the cache lookup and cached-body replay complete.
  • Use a private controller wrapper so downstream backpressure cannot release that internal pause early.
  • Resume the origin response only after replay completes, preserving start, cached data, then end ordering.
  • Handle failed cache lookups as misses and abort cleanly if asynchronous 304 handling throws.
  • Add coverage for synchronous and asynchronous stores, replay backpressure, write-stream failures, closed streams, and the asynchronous abort path.

Features

N/A

Bug Fixes

  • Preserve response event ordering when a 304 is revalidated through an asynchronous cache store.
  • Prevent cached-body replay from being cut short by an early 304 end.

Breaking Changes and Deprecations

N/A

Status

@marcopiraccini
marcopiraccini marked this pull request as draft September 10, 2026 08:41
@codecov-commenter

codecov-commenter commented Sep 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.57%. Comparing base (3c3db8d) to head (e3933b4).
⚠️ Report is 21 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5797      +/-   ##
==========================================
+ Coverage   93.52%   93.57%   +0.05%     
==========================================
  Files         110      110              
  Lines       39414    39853     +439     
==========================================
+ Hits        36863    37294     +431     
- Misses       2551     2559       +8     

☔ 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.

@marcopiraccini marcopiraccini changed the title fix(cache): deliver a 304's start before its end with an async store fix(cache): defer 304 response end for async stores Sep 10, 2026
@marcopiraccini marcopiraccini changed the title fix(cache): defer 304 response end for async stores fix(cache): preserve 304 response event order with async stores Sep 10, 2026
@marcopiraccini
marcopiraccini marked this pull request as ready for review September 10, 2026 09:26
Comment thread lib/handler/cache-handler.js Outdated
// when stream is full stop writing until we get a 'drain' event
if (full) {
break
return new Promise((resolve) => {

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.

I'd prefer if we use a callback instead of creating a new Promise; in that way, regardless of the nature, we minimize overhead as well keep things a bit consistent.

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.

I don't think it's the contract.

Comment thread lib/handler/cache-handler.js Outdated
// when stream is full stop writing until we get a 'drain' event
if (full) {
break
return new Promise((resolve) => {

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.

I don't think it's the contract.

Comment thread lib/handler/cache-handler.js Outdated

onResponseData (controller, chunk) {
if (this.#pending304) {
this.#pending304 = this.#pending304.then(() => this.onResponseData(controller, chunk))

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.

this create an infinite chain of pending promises

Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
@marcopiraccini

Copy link
Copy Markdown
Contributor Author

@mcollina PTAL

@mcollina mcollina 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.

LGTM, much much better

@mcollina

Copy link
Copy Markdown
Member

@metcoder95 PTAL

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.

4 participants