GoogleChromeLabs / sw-appcache-behavior

A service worker implementation of the behavior defined in a page's AppCache manifest.
Apache License 2.0
54 stars 15 forks source link

Potential race condition with ApplicationCache #24

Open kirill578 opened 3 years ago

kirill578 commented 3 years ago

Hi, we have been using the library successfuly for a couple of months now. However, we recently noticed that around 0.5% of our users are stuck on an old version. Two members of our dev team were able to reproduce the issue locally but unfortunately we do not have a reliable way to do so.

When the issue occured I noticed that the website has both legacy application cache as well as webservice cache. The issue could only be resolved by checking by pass network on the service worker and refreshing the page twice.

This issue happaned on the Android version of Chrome 86 which still has ApplicationCache available.

I noticed that @jeffposnick mentioned

In Chrome, once there's a service worker registered and a page is loaded in scope of that service worker, AppCache is disabled. (I.e. the service worker always "wins", even if there's still a manifest attribute on the <html>.)

The example in the readme file has an wait statement before the service worker is registered

init({
    cachePopulatedCallback: myCachePopulatedCallback,
  }).then(() => navigator.serviceWorker.register('sw.js'));

Could that be the root cause of the race condition? in our usecase we do an additional await for await caches.keys() before initializing the library.

I was thinking we could add navigator.serviceWorker.register('no-op-sw.js') at the top of the HTML file just to disable ApplicationCache. Do you have any suggestions?

jeffposnick commented 3 years ago

Just to correct my previous statement to be more accurate: disabling AppCache happens as part of the service worker activation algorithm, described as step 8 in https://w3c.github.io/ServiceWorker/#activation-algorithm. So it's once a registered service worker activates that AppCache is effectively disabled, not immediately following registration.

I would probably not recommend this, but if you did want to go down the path of trying to register a no-op service worker as early as possible, the best way to do it would be:

// Avoid re-registering the no-op service worker if there's
// already a service worker in control of the page.
if (!navigator.serviceWorker.controller) {
  navigator.serviceWorker.register('no-op-sw.js');
}

If there's something about the way your AppCache manifest is set up that would prevent any of the caching operations that need to be performed in init() from succeeding, that would in turn prevent the service worker registration from happening. I'd probably start by hanging a .catch() off of the init() promise chain and if you have some sort of centralizing logging or error reporting, use that to report any exceptions you catch. That might point to the underlying issue.

kirill578 commented 3 years ago

I don't think init() will throws an exception. There is an internal try-catch. I checked our logs and I could not find any instances of the error messaged logged in the catch clause.

I am certain that the service worker is conflicting with app cache as I can see the app cache logs in the console while the service worker is registered. Given that it's going to be removed anyway we'll try to disable the app cache on the webview on our android app. Hopefully that will resolve this strange behavior.

in case anyone reads it in the future, there are two erros that I have seen what that happans. There is a 500 error to fetch the appcache.manifest file. And we keep getting a cache updated event.