Re: Can anyone explain these strange browser Cache Quirks?

It's unlikely that this is an issue with any of the browsers. The response
headers to both requests have no cache-control directives and both requests
have the same cache key (without Vary) so if the request can be cached
and/or reused is largely up to heuristics
<https://datatracker.ietf.org/doc/html/rfc7234#section-4.2.2> and what
browsers decide to do when two in-flight requests match the same cache
entry and how they partition the caches.

Document response headers:

t=422 [st=6]          HTTP_TRANSACTION_READ_RESPONSE_HEADERS
                      --> HTTP/1.1 200 OK
                          Date: Sat, 07 Mar 2026 04:11:47 GMT
                          Connection: keep-alive
                          Keep-Alive: timeout=5
                          Content-Length: 477

fetch() response headers:

t=  430 [st=    1]          HTTP_TRANSACTION_READ_RESPONSE_HEADERS
                            --> HTTP/1.1 200 OK
                                Date: Sat, 07 Mar 2026 04:11:47 GMT
                                Connection: keep-alive
                                Keep-Alive: timeout=5
                                Transfer-Encoding: chunked

For deterministic behavior, you need to be explicit about the cache
treatment of the responses. What you use largely depends on what you want
the behavior to be.

"Vary: Subscribe" will add the value of the Subscribe header to the cache
key but it needs to be on both responses because the browser will lookup
based on the URL and then check if any existing vary conditions match.

Adding cache: "no-store" to the fetch request bypasses the cache entirely
for that request (which is probably what you want to do to avoid the cache
lookup time anyway if you never want to cache the response) but it doesn't
change the cache behavior of the document request.

A "cache-control: no-store" response header prevents the document response
from being written to the cache (including for back/forward navigations and
other cases where the browser might use stale responses). max-age=0 might
be more appropriate or even a long cache lifetime if the document itself is
static.

On Fri, Mar 6, 2026 at 11:17 PM Michael Toomim <toomim@gmail.com> wrote:

> 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 13:48:42 UTC