- From: Michael Toomim <toomim@gmail.com>
- Date: Fri, 6 Mar 2026 18:17:32 -1000
- To: Patrick Meenan <patmeenan@gmail.com>
- Cc: HTTP Working Group <ietf-http-wg@w3.org>, Braid <braid-http@googlegroups.com>
- Message-ID: <25a4f3d9-6eec-4436-ae57-9d1b4a1c83cb@gmail.com>
Thanks, Patrick! This is very helpful. We just updated the experiment
and have learned more about what's happening. You've helped me
understand how the caching is *supposed* to work, and I'm starting to
see the shape of the browser's behaviors.
Yes, the requests have the same URL. The server differentiates the
second request because it adds a `Subscribe: true` header asking its
response to be streamed. The stream response headers are then sent
immediately, along with an initial chunk of stream data.
The 20-second delay isn't for that second request, though. It's for
opening a new tab to the same URL (when devtools are open in the first
tab), and the delay depends specifically on the headers specified on the
2nd request. If that 2nd (streaming) request does *not* say
Cache-Control: no-store, then the loading of a new tab to that URL spins
for 20 seconds. So perhaps it's waiting for the streaming response to
... finish?
I recorded a netlog, and put it here:
https://braid.org/files/bugs/chrome-20second-netlog.json
We added two new conditions to the experiment: a "Vary: Subscribe"
response header (to teach the cache how to distinguish the two requests)
and the client-side fetch(url, {cache: 'no-store'}) parameter. Either of
these are enough to fix the 20-second delay, presumably by making it
clear to Chrome that it can't try to re-use the streaming response
(which hasn't finished) for the new tab it's opening.
I think we probably have enough to write these up as bug reports for
Chrome and FF, now!
Thanks, and cheers!
Michael
On 3/5/26 5:10 PM, Patrick Meenan wrote:
> Most of what you are describing is outside the scope of HTTP-proper
> (except for the actual caching of the responses) and is in the
> browser's fetch spec implementation before it hits HTTP.
>
> When you leave the response open as a stream, do you send an initial
> response with headers and just never finish it or do you leave the
> request hanging until there is data to send?
>
> How are the response headers varied? Specifically, what are the
> headers used for the HTML response? The reason I ask is that the
> client has to make its decision to fetch or not based 100% on any
> headers on the cached entry and has no idea what the second set of
> headers will be.
>
> Do the requests have the same URLs? How does the server differentiate
> between "first" and "second"? How does it handle any prefetch requests
> the browser might be sending?
>
> A Chrome netlog will help show what Chrome is doing (I'm happy to look
> if you collect one):
> https://www.chromium.org/for-testers/providing-network-details/
>
> The 20-second delay could well be the request de-duplication timeout
> where Chrome will hold additional requests for identical URLs to make
> sure they aren't for the same static resource (instead of wasting
> bandwidth downloading the same thing twice). It should be able to make
> that determination as soon as the headers are complete though and not
> have to wait for the response to end.
>
> You have some level of control over this on the javascript side if you
> know what you are fetching shouldn't go through the cache:
> https://developer.mozilla.org/en-US/docs/Web/API/RequestInit#cache
> i.e. fetch('/foo', cache: 'no-store') will skip checking or writing to
> the cache at request time.
>
>
>
> On Thu, Mar 5, 2026 at 6:23 PM Michael Toomim <toomim@gmail.com> wrote:
>
> Hello HTTPers!
>
> We've been experimenting with using fetch() to open a streaming
> HTTP response from the same URL that a page loads from.
> Specifically, the page loads at /foo, then its JavaScript does
> a fetch() back to /foo with the response left open as a
> stream. We've discovered some strange browser quirks that we'd
> appreciate the WGs input on.
>
> We wrote up a detailed test case with reproduction steps, results
> matrices, and videos here:
>
> https://braid.org/meeting-108/cache-quirks
>
>
> The test server varies Cache-Control headers (no-cache, no-store,
> none) across the initial page load and the streaming fetch,
> creating 5 experimental conditions. We tested on Chrome 145,
> Safari 26.3.1, and Firefox 148.0. Here's a summary of what we found:
>
> *Quirk 1: Cached page content returned instead of stream
> data.* When a tab is closed and restored (e.g. Shift-Cmd-T),
> the second fetch() returns the cached HTML of the page itself
> rather than the streamed response from the server. This
> affects Chrome, Safari, and Firefox on most endpoints.
> Setting Cache-Control: no-store on the initial page load fixes it
> in Chrome and Safari, but not Firefox.
>
> *Quirk 2: 20-second delay before anything renders.* In Chrome,
> opening a second tab to the same URL causes the page itself to
> hang for precisely 20 seconds before loading. (Not just the inner
> fetch. Nothing renders at all.) Safari handles this fine.
> Setting no-store on the streaming fetch (or both requests)
> resolves it for Chrome, but Firefox recognizes no stream data at
> all in this scenario.
>
> The exact 20-second duration suggests this may be Chrome's cache
> lock timing out — perhaps Chrome is blocking a second request
> to the same URL while waiting for the first (streaming) response
> to become cacheable, and gives up after 20 seconds. (In
> HTTP/2 setups, a similar 20-second delay can occur from the
> SETTINGS frame acknowledgment timeout, but our test case is
> plain HTTP/1.1, so cache locking seems the more likely culprit here.)
>
> *Quirk 3: Streaming fetch hangs indefinitely.* In Firefox 148.0,
> the streaming fetch() now hangs forever on all
> endpoints, regardless of Cache-Control settings. This appears to
> be a regression — previously only
> some no-store configurations triggered the hang, and it required
> opening a second tab. Now it occurs even on first load. Chrome and
> Safari are unaffected.
>
> We'd appreciate the WG's guidance on what the expected behavior
> should be in these cases. In particular:
>
> * Should the cache be allowed to serve the initial page's
> response body for a subsequent fetch() to the same URL when
> the requests carry different headers?
> * Is Chrome's 20-second blocking behavior a cache lock, and does
> the spec allow for this behavior? Should a streaming response
> that hasn't completed block other requests to the same URL?
> * Is there something in the spec that would explain or allow for
> Firefox's hanging behavior with streaming responses?
>
>
> Thanks for any insight!
>
> Michael
>
Received on Saturday, 7 March 2026 04:17:39 UTC