Skip to content

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

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

fix(cache): preserve 304 response event order with async stores#5797
marcopiraccini wants to merge 2 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

When a 304 response is delivered synchronously while using an asynchronous cache store, the response can end before the store lookup completes. This causes downstream handlers to receive response events out of order and can prevent the cached body from being replayed.

Changes

  • Track pending 304 cache lookups and body replays.
  • Defer response data, end, and error events until the pending 304 operation completes.
  • Wait for cached array and Readable bodies to finish replaying before forwarding the response end.
  • Treat a rejected asynchronous cache lookup as a cache miss.
  • Add regression coverage using a synchronous dispatcher and an asynchronous store.
  • Test cache misses and cache hits with array and Readable cached bodies.

Features

N/A

Bug Fixes

  • Ensure a 304 response start is delivered before its end when the cache store lookup is asynchronous.
  • Ensure cached response bodies are replayed before the downstream response ends.

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.56%. Comparing base (3c3db8d) to head (bfeed00).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5797      +/-   ##
==========================================
+ Coverage   93.52%   93.56%   +0.03%     
==========================================
  Files         110      110              
  Lines       39414    39451      +37     
==========================================
+ Hits        36863    36911      +48     
+ Misses       2551     2540      -11     

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

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


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

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