Service worker stuck in busy state when SSE request hits the cache
See original GitHub issueLibrary 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.
Issue Analytics
- State:
- Created 3 years ago
- Comments:14 (6 by maintainers)
Top Results From Across the Web
How can I remove a buggy service worker, or implement a "kill ...
The problem is that this code has an error which prevented the function from making the request, so my page is left blank;...
Read more >Skipwaiting() Is Not Installing New Service Worker Which Is ...
Service worker stuck in busy state when SSE request hits the cache GoogleChrome/workbox#. skipWaiting method of the ServiceWorkerGlobalScope forces the ...
Read more >Service worker cache.addAll fetch pending, stuck on trying to ...
Service worker endsup in error (1)/Redundant after trying to install for long time and in console I see many fetch request with pending...
Read more >Buildbot Documentation
Edit master.cfg and look for the BUILDBOT SERVICES section. ... of cached worker state (e.g., for Source steps in copy mode) is not...
Read more >The Magic of Service Workers - InRhythm
From that point service worker will handle all coming and going requests. In the case that you lost connection, the page can still...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
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.Hey folks! Thanks for the discussion on this!
Yes that’s pretty much it
In my case, I wasn’t even aware that SSE was present on the app - that request was being made by a third party library. I think the sketchy part here is that once we get into that SEE w/ workbox caching state - it’s impossible to push a new service-worker (or app in the case of precaching) and you’re at the mercy of when the user exits their browser session.
If it’s possible to prevent that locked-in condition whether that be by ignoring
text/event-stream
requests or by callingwaitUntil
(which I would guess would cover all streaming protocols) I think that’s the safest option - it doesn’t look like SEE can ever work with workbox’s cache anyhow.If not, maybe documentation on filtering out text/event-stream be the second best option.
Side note - CRA for better or for worse only enables service workers in production builds. Obviously there’s more to web dev than just CRA but it’s worth noting that a lot of users could encounter this issue and not be aware until it hits production.