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)

Most upvoted 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 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.