-
Notifications
You must be signed in to change notification settings - Fork 312
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
Workers & SharedWorkers within ServiceWorkers #678
Comments
It would be nice if we could investigate a
|
@wanderview why
and lose
plus of course you'd have to have a new conceptual service worker and ServiceWorkerGlobalScope with install/activate/fetch events... Seems weird. |
I guess I don't understand the life cycle of a worker using the ServiceWorker as its parent. A ServiceWorker can be shut down at any time. Does that drop the ref to the worker? Doesn't that defeat the point of all this? I guess I'm not so confident we can make SharedWorker and Worker function properly with a ServiceWorker parent instead of a document. |
Yeah, I don't have any answers to that concern, just was confused what |
For the Mozilla email case, I am hoping this sort of relationship would work: // in service worker that handles background sync notifications
self.onsync = function(event) {
if (event.registration.id === "periodicSync") {
event.waitUntil(new Promise(resolve, reject) {
// This worker connects to the email servers and syncs data to IDB.
var emailWorker = new SharedWorker("email-worker.js");
// Listen for a message from the worker to know when it is done
// with its sync work.
emailWorker.port.onmessage = function(e) {
var data = e.data;
if (data.type === 'syncComplete') {
// Shared worker complete. Resolve the waitUntil
// promise to allow this service worker to be
// shut down. If no other documents or workers
// have a reference to email-worker.js, then the
// shared worker would be shut down. If there
// are other documents/workers with a reference
// to email-worker.js would keep the shared worker
// alive.
resolve();
}
};
// Start communicating with the worker, ask it to sync.
// Wait to be messaged back to resolve the waitUntil
// promise.
emailWorker.port.start();
emailWorker.port.postMessage({
type: 'sync'
});
});
}
}; Just a sketch, things like errors not handled here, and in the email case, we could have multiple accounts on different sync intervals, so probably be a bit more complicated than this sketch, but I was hoping this sort of thing would be possible. |
In terms of life-cycle for the Mozilla email case (context: which I also work on), if the ServiceWorker needs to be shut down for memory-pressure reasons (or other reasons), it does seem appropriate to reap the email SharedWorker if there's no active documents keeping the SharedWorker alive. I don't think that would defeat the point, which as I see it (and @jrburke explained on https://github.com/slightlyoff/ServiceWorker/issues/slightlyoff/BackgroundSync#70 quite well), the main benefit for the email app is the single execution context for the back-end that talks to the mail servers and potentially multiple UI pages. The ServiceWorker, like a UI page, is a consumer of the back-end but with the very simple goal in the requestsync case of saying "hey, go sync". |
I think current plans are to shutdown more aggressively than that. Like, after some small amount of time (few seconds) have passed without a SW triggering event. |
You mean like despite an active waitUntil? The SharedWorker can obviously spam the ServiceWorker with a heartbeat if needed, but I would hope that given the explicit triggering by the https://github.com/slightlyoff/BackgroundSync mechanism that the ServiceWorker could be blessed with a level of runtime goodwill. (Like let's assume that there's a countdown-to-termination timer that gets bumped with events. For BackgroundSync, it seems like it's reasonable to start with a pretty big value, or at least allow the API to make it an option.) |
Me neither. Shared worker/worker's inherent tying to their list of associated documents doesn't seem to easily fit to the service worker's model. Maybe we can think of the possibility where the SW's controlled clients are added to the shared worker's document list through the SW's invocation of the SharedWorker ctor. But I can hardly think of the case we set the parent of the shared worker to the service worker itself. The lifetime of that parent would be that of the registration of the service worker which won't allow the shared worker to be terminated even if the associated documents are unloaded. |
@jrburke I'm not saying the existing case should be replaced as such, but just curious to know for reasoning. Assuming we have |
@jungkees for the email app, it wants to use a shared worker to hold the data layer and convert network fetches to JS objects used by the data layer, and all UI windows talk to this shared worker to get data for its views. That shared worker should live as long as the UI windows live, and be possible to spin it up from the UI windows. That model would be used even without service workers in play. The user can manually trigger syncs in the UI via a button. Now with service workers we have an automated entry point for backgroundsync and we just want to hand off to that shared worker, which already has all the smarts to do the syncing. If you are suggesting that the shared worker, email-worker.js, could implement the service worker entry point for backgroundsync and that shared worker could be registered as a service worker, that seems fine as long as:
My impression is that the service worker is not long-lived enough to be able to also function in the same role as our shared worker, and it was actually desirable to make sure the service worker did not do too much to limit its scope and purpose. However if that is not true, then it would be great to walk through a more thorough code example that demonstrates how that would work. |
Thanks for elaborating on the context and answering to the question @jrburke! Basically, I agree to the point that the service worker's not designed for long-lived tasks and don't want to deviate from its event based reasoning. But no clear idea about what's a better approach either. So just pondering upon many cases. About your suggestion - supporting shared worker/worker in service workers, it seems it makes sense to use For the following case:
The SW may run until To clarify, we're counting the case where no tabs are alive when a sync event signals to do the background jobs, right?
From the document that registered the SW, you always has a reference to it: FYI. #651 discusses another use case that needs longer lifetime of SWs. |
Correct, the backgroundsync service worker case could trigger the email sync logic when there are no browser windows showing an document from the email app. It is good to know that the document has options via We likely should not get into that bootstrapping in this bug, just mentioning it to give more signal to this bug, where I believe just allowing service workers to participate in the shared worker reference refcounting should be enough for the email use case (with the knowledge that |
IMHO, we should consider ServiceWorker as a replacement for SharedWorker instead of creating some more complex approaches. Whatever the solution is , it should make developer's life easier whenever it can. Do you guys know how hard is it to build an offline web app? APIs like IndexedDB, Appcache (who knows ServiceWorker can replace it), ServiceWorker (Cache API, Push API, BackgroundSync...), SharedWorker, WebSocket, WebRTC... cause a huge amount of time to study. If things keep getting more and more complex like this, how can we web developers fight with the native one? |
@jrburke's example (#678 (comment)) is exactly how I'd see this working. Sure, without Of course, even with |
If you're using SharedWorker to maintain a single websocket connection for multiple tabs, ServiceWorker is not a good replacement. ServiceWorker should be allowed to close when it isn't handling events, and the websocket use-case requires persistence. |
But I can create 100 background sync service workers that all attach to the same SharedWorker to try to keep it alive so the SharedWorker can do bitcoin mining? This seems like an uphill battle to me. It just seems these "heuristics" are going to make a lot of compat issues between browsers and browser version s that will bite developers while still letting people game the system. Maybe the time has come to be more explicit in the spec about lifecycle of the ServiceWorker? Or perhaps we should put a CPU throttle on workers without a window. They are useful for I/O things, but if you try to use them for heavy CPU usage you will run slow. |
I am curious about the possible next steps for this issue, to allow service workers to talk to shared workers, and for a service worker to count as a reference count for keeping a shared worker alive. Is it getting an update to the Shared worker part in the whatwg document, then a corresponding service worker spec change? Although looks like event.waitUntil already exists in the service worker spec, maybe that is enough? Or is it more important to have an implementation try it out first? If it is about the whatwg document, in section 10.2.6.4: Step 7 mentions:
Seems like Then these steps adjusted to use those terms: Step 8.7.7:
And Step 11:
If a whatwg doc update is the next step, and if it helps, I can start a thread on the whatwg list about it. If an implementation is best, then I can file a bug for Gecko to track experimenting with it. |
Yep, I've asked for this at https://www.w3.org/Bugs/Public/show_bug.cgi?id=28504#c4
Yep, this is the right way to signal ongoing activity in a service worker. There are a couple of things we need to think about, will make a separate comment for those.
That'd be great! Would show that multiple vendors are interested in this. |
Requests from a service worker always go to the network, which could be a gotcha here. However, you could get a script from the cache & create a blob url. caches.match('/my-worker.js')
.then(workerJS => workerJS.blob())
.then(blob => URL.createObjectURL(blob))
.then(objectURL => {
new Worker(objectURL);
}); In the case of SharedWorkers, you'd have to use the name param Is this acceptable? Otherwise we have to:
|
From my brief inspection in Chrome and Firefox Nightly, it looks like I posted to the whatwg on the shared worker spec issue: |
Related, maybe even a better way of doing this stuff #756 |
It is unclear to me how #756 helps this situation: that ticket seems to be about allowing multiple service workers to be running. The issue here: we actually only want one thing doing some worker work in a shared worker, that is also shared with browser windows for the app to do the data layer work. |
It helps in the sense that you can do the heavy processing work inline during background-sync without worrying about blocking other service worker events. In theory those later service worker events could get scheduled on a different SW thread. |
That seems to imply loading the code that talks to the server and deals with data models in the service worker as well as the shared worker (or page-specific worker), and probably more coordination to make sure no one duplicates their efforts. If so, I would prefer to just talk to one central authority, like a Shared Worker, to handle that work. |
Closing in favour of whatwg/html#411 |
ServiceWorkers should be able to create dedicated workers to do processing work that would otherwise hold up the thread. With canvas-in-workers proposals starting to mature, this could include image pixel manipulation, but may also include complex diffing etc.
Multi-tab apps sometimes use a SharedWorker to ensure only one connection is held to the server, and that worker distributes the data to the appropriate clients. A ServiceWorker should be able to connect to an existing ServiceWorker instance or create a new one, to make use of this single connection. Mozilla have a use-case for their email app.
Workers and SharedWorkers are currently tied to documents, changing this to also allow ServiceWorkers shouldn't be too much of a problem spec-wise.
Since creating a Worker/SharedWorker forms a new client & will select its own registration, workers created within a SW may trigger
onfetch
within the same ServiceWorker.The text was updated successfully, but these errors were encountered: