Skip to content

allow service workers to created nested dedicated workers #1529

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wanderview opened this issue Aug 13, 2020 · 18 comments
Open

allow service workers to created nested dedicated workers #1529

wanderview opened this issue Aug 13, 2020 · 18 comments

Comments

@wanderview
Copy link
Member

I think we've discussed this before, but I cannot find the issue. It would be nice to allow service workers to created nested dedicated workers via new Worker(). This would allow the service worker to perform CPU intensive operations, like custom decoding, without potentially blocking the thread being used to process further FetchEvents.

chromeos/static-site-scaffold-modules#40 (comment) is an example of where this would be useful.

@jakearchibald
Copy link
Contributor

Yeah, I think workers are currently linked to a root document, so that needs to be changed to "document or service worker global", or something common between the two.

@wanderview
Copy link
Member Author

I think dedicated workers can be nested under shared workers right now?

@mfalken
Copy link
Member

mfalken commented Aug 27, 2020

Would the idea be that the dedicated worker lives as long as the service worker is running?

@wanderview
Copy link
Member Author

Correct. Its lifetime would be bound by the lifetime of its parent, just like when creating a dedicated worker in a window or iframe.

@tophf
Copy link

tophf commented Jun 10, 2021

FWIW, It would be useful for the new ManifestV3 browser extensions (currently implemented only in Chrome), which are built on service workers now so they've completely lost the ability to create workers in the background script.

Looks like whatwg/html#411

@cohenerickson
Copy link

I would love to see this become implemented into the service worker spec, I think it would be beneficial for highly computational fetch requests.

@ian97531
Copy link

ian97531 commented Oct 6, 2022

I created a similar issue over in WHATWG: whatwg/html#8362

@js-choi
Copy link

js-choi commented Feb 6, 2025

A while back in whatwg/html#8362, I made several comments describing real-world use cases for service workers spawning dedicated workers. I’m concisely summarizing them here too, since I figure this space might be more likely to increase implementer interest.

An MDN webpage on offline progressive web apps says, “If you have heavy calculations to do, you can offload them from the main thread and do them in the [service] worker, and receive results as soon as they are available.” In actuality, whenever a service worker needs to do CPU-expensive work, it must resort to complex coordination with at least one dedicated worker belonging to its origin’s browser tab(s), e.g., using Web Locks and some leader-election scheme. (This can get especially complicated when the leader tab gets closed, destroying its dedicated worker, at which the newly elected leader tab must restore that dedicated worker’s state.) It would be considerably simpler for web developers if service workers could “simply” spawn dedicated workers, to which the service workers may offload CPU-intensive tasks.

This complexity affects the following use cases:

  • Fetching offline data from SQLite, OPFS sync access, or local storage: Cache, IndexedDB, and cookies are currently the only persistent offline data storage available to service workers. A multi-page progressive web app that wishes to be available offline is unable to load data without the aforementioned complex coordination with dedicated document workers; otherwise, the service worker must use IndexedDB or the like.

    • OPFS sync access with FileSystemSyncAccessHandle is essential for WASM SQLite but is unavailable to service workers without dedicated workers. Therefore, it is currently impossible for an offline website’s service worker to fetch data from SQLite data in OPFS, without coordinating with a worker (or the main thread) belonging to an elected-leader browser tab(s). See Add SharedService alternative demo using service worker rhashimoto/wa-sqlite#95, Using a shared Worker (instead of SharedWorker) rhashimoto/wa-sqlite#81, Allow using sqlite's OPFS_SAH backend DallasHoff/sqlocal#39.
    • If service workers could use OPFS, webpages that are loaded from URLs typed into the address bar can be dynamically generated offline by the service workers. For example, a cloud document editor, wiki, CMS-based blog, or Pokédex web app that wished to make itself available offline could dump all of its documents’, wiki’s, or CMS’s data into a SQLite database, then use its service worker to dynamically generate the same web pages offline.
    • Furthermore, browser tabs would be able to use the same HTTP API both to read/write data in cloud storage and to read/write data to SQLite files or other documents in OPFS. When the service worker acts as a “transparent” mediator syncing cloud storage and with local OPFS caching, document scripts do not have to be concerned with whether documents are stored offline only in OPFS, online only in the cloud, or both.
  • Transparent decoding/decompression of unsupported file formats: Service workers can intercept data fetches and transparently encode/decode data into new formats when the formats are unsupported by current browsers.

  • Expensive RegExp processing for navigation routing: A core use case of service workers is offline URL routing, but this routing may be expensive. As @wanderview mentioned in this issue’s original post, a real-world case of this is chromeos.dev’s >1.0-second synchronous microtask delay while processing complex regular expressions; see Performance issues with swiCleanup chromeos/static-site-scaffold-modules#40 (comment).

@nickchomey
Copy link

Excellent comment. One more reason for service workers needing to be able to spawn workers is that service workers cannot do dynamic imports. If they could, it would be a partial reason for not needing to spawn a worker. But it would still be best if they could.

@yoshisatoyanagisawa
Copy link
Collaborator

I am concerned about the complexity of detecting script changes in service workers when dynamic imports and dedicated workers are involved. Currently, service workers have a mechanism to automatically detect and reload when their scripts are updated. However, I believe that the introduction of dynamic imports and dedicated workers may pose a challenge to this mechanism. How can service workers reliably detect changes in scripts when they use dynamic imports or include dedicated workers?

@nickchomey
Copy link

nickchomey commented Feb 7, 2025

I'm not quite following what your concern is. The main thread and web workers themselves can spawn dedicated workers. Could you please elaborate on what unique challenges service workers present for detecting script changes, and how this relates to them being able to speak dedicated workers?

(as for dynamic imports, again, I only brought it up because Id like to be able to spawn a worker from the sw, to do dynamic imports there. Allowing for dynamic imports in the sw is a separate issue #1585)

@asutherland
Copy link

I think the most practical approach is to require dedicated workers spawned by ServiceWorkers to be controlled by the ServiceWorker that spawned them, including using the routes for that registration. The ServiceWorker would be responsible for caching the worker scripts, ideally as part of the install phase, but it doesn't seem worth it to try and add enforcement mechanisms for that like we have for ServiceWorker scripts or for the update check mechanism to change.

The spec would also need to be clear that the controlled worker clients should not extend the underlying lifetime of the ServiceWorker similar to how ServiceWorker.postMessage from a ServiceWorker to another ServiceWorker should not extend the lifetime of the recipient ServiceWorker beyond the lifetime of the sender.

@nickchomey
Copy link

nickchomey commented Feb 7, 2025

You make a good point about the lifetime - a Dedicated Worker spawned by a Service Worker probably should be tied to the SW's lifetime, which I understand to be somewhat unpredictable/ephemeral...

At least as far as my use case goes for this, I think that if Android Chromium could ever get around to adding support for Shared Workers, then many issues like this would be largely irrelevant. I, and many others, are doing a lot of convoluted dances between different workers/threads, with leader election and various ways to message between them, all because Shared Workers are not a viable option when missing on 50% of web visitors.

If we had shared workers, instead of a SW needing to spawn a dedicated (or shared) worker, it could just pass messages to one that was already created by one of the main threads (which might not even be active anymore.

Shared Workers really are the holy grail of all of this - the rest is largely just terrible "polyfill" kludges. (Dedicated Workers could still be effectively used though, tied to its main thread).

@tophf
Copy link

tophf commented Feb 7, 2025

What about letting service worker connect to an already running SharedWorker's port if it's trivial to implement? It'll be kinda weird though that new SharedWorker() may fail when the worker isn't running, so maybe a new API should be added e.g. to find the running worker via self.clients or a new one like self.clientWorkers...

@asutherland
Copy link

The clients API already exposes and identifies shared worker clients.

@nickchomey
Copy link

nickchomey commented Feb 8, 2025

Again, the problem with Shared Workers is that 50% of devices don't support it. Perhaps not an issue for some applications, but it's a non-starter for most. https://caniuse.com/mdn-api_sharedworker

Hence we need various convoluted dances with Dedicated + service workers, leader election with web locks, broadcast channel to communicate etc to create our own "shared worker polyfill". Hence issues like this, being able to spawn workers from service workers etc...

@tophf
Copy link

tophf commented Feb 8, 2025

The clients API already exposes and identifies shared worker clients.

Yes, but AFAICT it's unusable and client.postMessage doesn't reach SharedWorker because it only has a connect event and not message.

@js-choi
Copy link

js-choi commented Feb 8, 2025

I’d like to emphasize that service-workers-spawning-workers are important, even with shared workers.

This issue covers many use cases that shared workers do not address, particularly with those described in #1529 (comment) – i.e., fetch handling that requires relatively long-running sync calls. Shared workers wouldn’t help with fetches using data from SQLite or other OPFS sync access; they wouldn’t help with transparent decoding/decompression of unsupported file formats or with async expensive RegExp processing for URL routing either.

If Chrome Android supported shared workers, that would help many other use cases that also currently require complex tab leader election and locking. But shared workers don’t address many important use cases in this thread. Even with broad shared-workers support—if we want to enable important fetch-related use cases, then we still need dedicated workers spawned by dedicated workers.


@asutherland: I largely agree with the ideas in #1529 (comment). I’m a little confused by what you mean by “enforcement mechanism”—do you mean how the last update check time can get updated, e.g., by importScripts(urls)? (Related comment by @jeffposnick from the dynamic-import() issue: #1585 (comment).)

I can see it being a developer footgun if a change in a JS resource didn’t trigger an update in a service worker that used it as a dedicated worker…but it also probably would be a footgun if JS modules didn’t use the same module cache as the service worker’s statically imported modules.

I definitely think that new Worker’s SW-update-triggering behavior (or lack thereof) should be consistent whether or not it uses type: "module". And I agree that it would be simpler and more consistent for module dedicated workers to just use the same module cache system as statically imported modules.

And that would mean that changes to dedicated-worker resources wouldn’t contribute to the service worker’s update check, whether or not the dedicated worker is a module. Developers would just have to live changing a comment in the service worker or something similar, each time they also change one of its dedicated workers (or its dynamic imports, for that matter).

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

No branches or pull requests

10 participants