workbox: Service worker stuck in busy state when SSE request hits the cache
Library Affected: workbox-routing
Browser & Platform: all browsers
Issue or Feature Request Description: Bug report:
Consider the following service worker
import { clientsClaim, skipWaiting } from 'workbox-core';
import { registerRoute } from **'workbox-routing';
import { StaleWhileRevalidate } from 'workbox-strategies';
skipWaiting();
clientsClaim();
registerRoute(
({ request, url }) => request.url.origin === self.location.origin && /(?<!service-worker)\.js$/.test(url.pathname),
new StaleWhileRevalidate({
cacheName: 'app',
}),
);
When a new service worker exists and installation of the newer worker begins, it would be expected that the use of skipWaiting
would activate the new service worker.
This isn’t the case however because the registerRoute function instantiates a fetch handler that never calls waitUntil.
The result of this is that the updated service worker will never activate (stuck in installed
state) until the user’s browser session is closed.
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Comments: 15 (7 by maintainers)
Ah, okay, glad that there’s an explanation!
Service workers don’t intercept WebSocket requests, but they can be used with the Streams API, and they also explicitly do intercept SSE, as per this thread that @wanderview participated in back in the day. That thread discussed potential edge cases and had an overall consensus that using service workers with SSE was likely to be uncommon.
I’m not ruling out making a change to Workbox if needed, but I’m wondering if there’s an underlying issue that would better be addressed by the browser(s). You mentioned both Chrome and Firefox exhibited that behavior—how about Safari?
I’d like to summarize the issue and then pull in some folks for their opinion. Please correct me if I’m wrong about any of this:
You have a service worker, using Workbox, whose
fetch
handler callsevent.respondWith(responsePromise)
to generate a response for a SSE. (I’m not clear on whether this was a mistake since you didn’t realize it was a SSE, or whether you actually meant to do this.)Doing so keeps that service worker alive for an extended period of time. (Presumably until the server closes the connection, though maybe there’s a hard time limit imposed by the browser?)
If, during the time that the active service worker is being kept alive, a newly installed service worker calls
self.skipWaiting()
, this has no effect. The new service worker remains in theinstalled
state indefinitely, and things appear to be “stuck.”But, if the active service worker had called
event.waitUntil(responsePromise)
inside itsfetch
handler, immediately prior to callingevent.respondWith(responsePromise)
, then callingself.skipWaiting()
in the newly installed service worker behaves as intended, and things are not “stuck.” (This is what https://github.com/GoogleChrome/workbox/pull/2693 added.)The service worker spec states that “
event.respondWith(r)
extends the lifetime of the event by default as ifevent.waitUntil(r)
is called.”, so I find it unexpected that explicitly callingevent.waitUntil()
changes the behavior.