From 5b4781e0e519a7d35d142550cb5c179b040cd437 Mon Sep 17 00:00:00 2001 From: Monica Chintala Date: Thu, 16 Jul 2026 13:48:22 -0700 Subject: [PATCH] Editorial: serialize CacheStorage access on the name to cache map's parallel queue Introduce a dedicated parallel queue on the name to cache map and route CacheStorage.match / has / open / delete / keys through it, so that reads and writes on the shared map (across CacheStorage objects in different documents/workers) are serialized. Also queue-a-task for all promise resolutions to avoid touching JS objects from the parallel queue. Refs: #1740, #1831 --- index.bs | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/index.bs b/index.bs index 7c11b834..d1e76319 100644 --- a/index.bs +++ b/index.bs @@ -1966,6 +1966,8 @@ spec: storage; urlPrefix: https://storage.spec.whatwg.org/ A name to cache map is an ordered map whose [=map/entry=] consists of a [=map/key=] (a string that represents the name of a [=request response list=]) and a [=map/value=] (a [=request response list=]). + A [=name to cache map=] has an associated name to cache map parallel queue (a [=parallel queue=]) used to serialize reads and writes on the [=name to cache map=]. + The relevant name to cache map for a {{CacheStorage}} object is the [=name to cache map=] associated with the result of running [=obtain a local storage bottle map=] with the object's [=relevant settings object=] and "caches". @@ -2281,12 +2283,14 @@ spec: storage; urlPrefix: https://storage.spec.whatwg.org/ The match(|request|, |options|) method steps are: 1. If |options|["{{MultiCacheQueryOptions/cacheName}}"] [=map/exists=], then: - 1. Return [=a new promise=] |promise| and run the following substeps [=in parallel=]: + 1. Let |promise| be [=a new promise=]. + 1. [=queue/Enqueue=] the following steps to the [=relevant name to cache map=]'s [=name to cache map parallel queue=]: 1. [=map/For each=] |cacheName| → |cache| of the [=relevant name to cache map=]: 1. If |options|["{{MultiCacheQueryOptions/cacheName}}"] matches |cacheName|, then: - 1. Resolve |promise| with the result of running the algorithm specified in {{Cache/match(request, options)}} method of {{Cache}} interface with |request| and |options| (providing |cache| as thisArgument to the `\[[Call]]` internal method of {{Cache/match(request, options)}}.) - 1. Abort these steps. - 1. Resolve |promise| with undefined. + 1. [=Queue a task=] on |promise|'s [=relevant settings object=]'s [=responsible event loop=], using the [=DOM manipulation task source=], to run the following steps: + 1. Resolve |promise| with the result of running the algorithm specified in {{Cache/match(request, options)}} method of {{Cache}} interface with |request| and |options| (providing |cache| as thisArgument to the `\[[Call]]` internal method of {{Cache/match(request, options)}}). + 1. Abort these steps. + 1. [=Queue a task=] on |promise|'s [=relevant settings object=]'s [=responsible event loop=], using the [=DOM manipulation task source=], to resolve |promise| with undefined. 1. Else: 1. Let |promise| be [=a promise resolved with=] undefined. 1. [=map/For each=] cacheName → |cache| of the [=relevant name to cache map=]: @@ -2302,10 +2306,12 @@ spec: storage; urlPrefix: https://storage.spec.whatwg.org/ The has(|cacheName|) method steps are: 1. Let |promise| be [=a new promise=]. - 1. Run the following substeps [=in parallel=]: + 1. [=queue/Enqueue=] the following steps to the [=relevant name to cache map=]'s [=name to cache map parallel queue=]: 1. [=map/For each=] |key| → value of the [=relevant name to cache map=]: - 1. If |cacheName| matches |key|, resolve |promise| with true and abort these steps. - 1. Resolve |promise| with false. + 1. [=Queue a task=] on |promise|'s [=relevant settings object=]'s [=responsible event loop=], using the [=DOM manipulation task source=], to run the following steps: + 1. If |cacheName| matches |key|, resolve |promise| with true. + 1. Abort these steps. + 1. [=Queue a task=] on |promise|'s [=relevant settings object=]'s [=responsible event loop=], using the [=DOM manipulation task source=], to resolve |promise| with false. 1. Return |promise|. @@ -2315,14 +2321,18 @@ spec: storage; urlPrefix: https://storage.spec.whatwg.org/ The open(|cacheName|) method steps are: 1. Let |promise| be [=a new promise=]. - 1. Run the following substeps [=in parallel=]: + 1. [=queue/Enqueue=] the following steps to the [=relevant name to cache map=]'s [=name to cache map parallel queue=]: 1. [=map/For each=] |key| → |value| of the [=relevant name to cache map=]: 1. If |cacheName| matches |key|, then: - 1. Resolve |promise| with a new {{Cache}} object that represents |value|. - 1. Abort these steps. + 1. [=Queue a task=] on |promise|'s [=relevant settings object=]'s [=responsible event loop=], using the [=DOM manipulation task source=], to run the following steps: + 1. Resolve |promise| with a new {{Cache}} object that represents |value|. + 1. Abort these steps. 1. Let |cache| be a new [=request response list=]. - 1. [=map/Set=] the [=relevant name to cache map=][|cacheName|] to |cache|. If this cache write operation failed due to exceeding the granted quota limit, reject |promise| with a {{QuotaExceededError}} and abort these steps. - 1. Resolve |promise| with a new {{Cache}} object that represents |cache|. + 1. [=map/Set=] the [=relevant name to cache map=][|cacheName|] to |cache|. If this cache write operation failed due to exceeding the granted quota limit, then: + 1. [=Queue a task=] on |promise|'s [=relevant settings object=]'s [=responsible event loop=], using the [=DOM manipulation task source=], to run the following steps: + 1. Reject |promise| with a "{{QuotaExceededError}}" {{DOMException}}. + 1. Abort these steps. + 1. [=Queue a task=] on |promise|'s [=relevant settings object=]'s [=responsible event loop=], using the [=DOM manipulation task source=], to resolve |promise| with a new {{Cache}} object that represents |cache|. 1. Return |promise|. @@ -2336,9 +2346,9 @@ spec: storage; urlPrefix: https://storage.spec.whatwg.org/ 1. If |cacheExists| is false, then: 1. Return false. 1. Let |cacheJobPromise| be [=a new promise=]. - 1. Run the following substeps [=in parallel=]: + 1. [=queue/Enqueue=] the following steps to the [=relevant name to cache map=]'s [=name to cache map parallel queue=]: 1. [=map/Remove=] the [=relevant name to cache map=][|cacheName|]. - 1. Resolve |cacheJobPromise| with true. + 1. [=Queue a task=] on |cacheJobPromise|'s [=relevant settings object=]'s [=responsible event loop=], using the [=DOM manipulation task source=], to resolve |cacheJobPromise| with true. Note: After this step, the existing DOM objects (i.e. the currently referenced Cache, Request, and Response objects) should remain functional. @@ -2351,12 +2361,12 @@ spec: storage; urlPrefix: https://storage.spec.whatwg.org/ The keys() method steps are: 1. Let |promise| be [=a new promise=]. - 1. Run the following substeps [=in parallel=]: + 1. [=queue/Enqueue=] the following steps to the [=relevant name to cache map=]'s [=name to cache map parallel queue=]: 1. Let |cacheKeys| be the result of [=map/get the keys|getting the keys=] of the [=relevant name to cache map=]. Note: The [=list/items=] in the result [=ordered set=] are in the order that their corresponding entry was added to the [=name to cache map=]. - 1. Resolve |promise| with |cacheKeys|. + 1. [=Queue a task=] on |promise|'s [=relevant settings object=]'s [=responsible event loop=], using the [=DOM manipulation task source=], to resolve |promise| with |cacheKeys|. 1. Return |promise|.