question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Service worker stuck in busy state when SSE request hits the cache

See original GitHub issue

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.

Issue Analytics

  • State:open
  • Created 3 years ago
  • Comments:14 (6 by maintainers)

github_iconTop GitHub Comments

1reaction
jeffposnickcommented, Dec 8, 2020

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 calls event.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 the installed state indefinitely, and things appear to be “stuck.”

  • But, if the active service worker had called event.waitUntil(responsePromise) inside its fetch handler, immediately prior to calling event.respondWith(responsePromise), then calling self.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 if event.waitUntil(r) is called.”, so I find it unexpected that explicitly calling event.waitUntil() changes the behavior.

0reactions
andyrichardsoncommented, Dec 8, 2020

Hey folks! Thanks for the discussion on this!

You have a service worker, using Workbox, whose fetch handler calls event.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.)

Yes that’s pretty much it

registerRoute(
  ({ url }) => /^somefeatureflagservice/.test(url.host),
  new StaleWhileRevalidate({
    cacheName: `flags`,
  }),
);

what if we added additional logging that’s enabled in the workbox-routing development builds which checks for fetchEvent.request.headers.get(‘accept’) === ‘text/event-stream’ and warns when workbox-router attempts to respond to such an event?

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 calling waitUntil (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.

Read more comments on GitHub >

github_iconTop 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 >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found