Code Monkey home page Code Monkey logo

Comments (13)

kettanaito avatar kettanaito commented on May 26, 2024

Hi, @jkinger. Thanks for reporting this.

Looks like Chrome has changed the way it deactivates idle service workers.

We have a ping/pong messaging in place to make sure the client constantly messages the worker so it never goes idle:

context.keepAliveInterval = window.setInterval(
() => context.workerChannel.send('KEEPALIVE_REQUEST'),
5000,
)

case 'KEEPALIVE_REQUEST': {
sendToClient(client, {
type: 'KEEPALIVE_RESPONSE',
})
break
}

Previously, the worker was considered idle if it hasn't handled any messages in the N period of time. Now, it looks like the criteria for that changed.

from msw.

kettanaito avatar kettanaito commented on May 26, 2024

Extension Service Worker termination policy change

I can see that Google has changed the Service Worker termination rules for Service Workers in extensions:

The service worker has not received an event for over thirty seconds and there are no outstanding long running tasks in progress. If a service worker received an event during that time, the idle timer was removed.

https://developer.chrome.com/blog/longer-esw-lifetimes/

This should have no effect on MSW as it's not an extension but a Service Worker registered by your app.

from msw.

kettanaito avatar kettanaito commented on May 26, 2024

Chrome FAQ on Service Worker termination

I've found another mention of this behavior that doesn't seem to be related to extension Service Workers but generally to Service Workers in Chrome:

The browser can terminate a running SW thread at almost any time. Chrome terminates a SW if the SW has been idle for 30 seconds. Chrome also detects long-running workers and terminates them. It does this if an event takes more than 5 minutes to settle, or if the worker is busy running synchronous JavaScript and does not respond to a ping within 30 seconds. When a SW is not running, Developer Tools and chrome://serviceworker-internals show its status as STOPPED.

https://chromium.googlesource.com/chromium/src/+/master/docs/security/service-worker-security-faq.md#do-service-workers-live-forever

If this is true, then the idle period has shortened from 5 minutes to 30 seconds.

It's worth mentioning this is a behavior specific only to the Manifest V3 API. Perhaps that's why it's not surfacing for everyone?

from msw.

kettanaito avatar kettanaito commented on May 26, 2024

I wonder why Chrome doesn't do this to MSW's Service Worker:

Service workers don't live indefinitely. While exact timings differ between browsers, service workers will be terminated if they've been idle for a few seconds, or if they've been busy for too long. If a service worker has been terminated and an event occurs that would start it up, it will restart.

https://web.dev/learn/pwa/service-workers#service_worker_lifespan

From what I understand, the worker goes idle, then the browser terminates it, and then your requests fail. Is that correct, @jkinger?

from msw.

kettanaito avatar kettanaito commented on May 26, 2024

A good thread on why Service Workers are killed after becoming idle: w3c/ServiceWorker#980

from msw.

kettanaito avatar kettanaito commented on May 26, 2024

I wonder if instead of trying to keep the worker alive and playing a guess game with the browser and its internal policies, we could detect that the browser has unregistered the worker (I do believe the worker's state change event should be emitted in that case) and if that happens, try to register and activate the worker again?

from msw.

jkinger avatar jkinger commented on May 26, 2024

@kettanaito

Yes, all that you explained above sounds accurate as this just started happening within 6 months or so and only happens on Chromium-based browsers. Firefox doesn't have this issue.

Your last post about "..try to register and activate the worker again" is what I've been thinking about too. Could I re-instantiate the MSW service again somehow (like below) when the service worker is de-registered or starts throwing errors?

  const { worker } = await import('./mocks/browser')
  return worker.start()

Or would there be more to it? Or maybe something like a worker.restart()?

from msw.

kettanaito avatar kettanaito commented on May 26, 2024

I think it's not something you should be doing manually. I wonder if that unregistration that Chrome does trigger the state change event on the worker.

@jkinger, can you please try this out in your scenario?

navigator.serviceWorker.controller.addEventListener('statechange', (event) => {
  console.log(event.target.state)
})

What does this print when Chrome terminates the idle worker? Does it print anything at all?

I hope Chrome doesn't go around the worker's lifecycle and the termination that it initiates still causes the worker to go into something like "redundant". If we can detect that via a listener, we can call something akin to worker.start() when that state transition happens.

from msw.

jkinger avatar jkinger commented on May 26, 2024

@kettanaito

I've taken your recommendation and code to see if I can get notified when Chrome stops the service worker but unfortunately not getting any events for "'statechange'" when Chrome stops it. I know what you're getting at so trying to explore other listeners as well.

FWIW another observation I've noticed is Chrome doesn't seem to unregister the Service Worker but only stops it. When I return to the Browser / Tab and start interacting with the page again Chrome starts the Server Worker again and it's still under the same Registration ID it was first registered with.

Thanks for your help thus far.

from msw.

kettanaito avatar kettanaito commented on May 26, 2024

FWIW another observation I've noticed is Chrome doesn't seem to unregister the Service Worker but only stops it.

Yeah, this makes sense. Aligns with what Chrome is saying they do.

When I return to the Browser / Tab and start interacting with the page again Chrome starts the Server Worker again and it's still under the same Registration ID it was first registered with.

Does this "solve" the issue then? Can you describe to me the scenario when Chrome kills the worker and that causes issues?

from msw.

jkinger avatar jkinger commented on May 26, 2024

Does this "solve" the issue then?

Yeah, you would think, right? But it doesn't, I've been monitoring the Service Worker via chrome://serviceworker-internals/?devtools and I see when Chrome stops it after the set time of inactivity and then I see it being started again when I start interacting with the page but nothing is being intercepted as before.

There is a disconnect happening somewhere during the Stop / Restart of this Worker that's keeping MSW inoperable and that is what I'm trying to pin down. If Chrome re-starts it again then why doesn't MSW start responding to requests again? That's the part that doesn't make sense. It's like the Starting, Stopping then Starting again throws it off somehow.

The only way I'm able to get MSW to start responding again is by doing a Refresh.

from msw.

kettanaito avatar kettanaito commented on May 26, 2024

There is a disconnect happening somewhere during the Stop / Restart of this Worker that's keeping MSW inoperable and that is what I'm trying to pin down.

I can imagine if terminating the worker also removes any listeners attached to it, MSW will lose any connection with the worker. We rely on a MessageChannel to talk to the worker. If that gets shutdown and doesn't revive, MSW won't work.

The only way I'm able to get MSW to start responding again is by doing a Refresh.

This is a strong indication that's precisely what's happening.

from msw.

jkinger avatar jkinger commented on May 26, 2024

I finally identified the root cause: when Chrome stops and then restarts the service worker, it creates a new instance of mockServiceWorker.js. This action removes any previously registered Client IDs under activeClientIds. To complicate matters, when Chrome restarts the service worker, it provides no indication (that I'm aware of) that a new instance was created. Consequently, no MOCK_ACTIVATE message is posted to re-register a new active client ID.

Fortunately, all the listeners remain connected and active, so fetch will still fire. However, since there are no activeClientIds, the activeClientIds.size === 0 check will prevent it from proceeding.

I'm not yet sufficiently familiar with the MSW code to suggest a comprehensive solution, but I managed to devise a quick, albeit makeshift, fix. If implemented in the App before a fetch, it should temporarily resolve the issue. Here's what the fix looks like:

  if ('serviceWorker' in navigator && navigator.serviceWorker.controller) {
    navigator.serviceWorker.controller.postMessage('MOCK_ACTIVATE');

    // Small wait for changes to propagate
    await new Promise((resolve) => {
      setTimeout(resolve, 10);
    });
  }

 await fetch('endpoint', {...});

By calling MOCK_ACTIVATE manually it registers a new client ID in activeClientIds and allows the rest of the Service Worker's "fetch" to continue.

I've updated my Github repo (as well as the live site) with this fix, so anyone interested can see it in action.

It’s not the most elegant solution, but it should suffice until a more definitive fix is developed.

from msw.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.