Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Service worker locked in EXISTING_CLIENTS_ONLY state #31109

Closed
H--o-l opened this issue Jun 18, 2019 · 11 comments

Comments

@H--o-l
Copy link
Contributor

commented Jun 18, 2019

馃悶 bug report

Affected Package

The issue is caused by package @angular/pwa

Is this a regression?

Not a regression I think.

Description

If a network failure happens, the service worker will enter the
EXISTING_CLIENTS_ONLY state.

If a connected client reloads (F5), the service worker will stay in that state
forever and serve nothing to the client, nor to other clients or reloading clients.

In my opinion, it would be better if the service worker kept serving the old
cached version, while waiting for the network to recover and the update to occur successfully.
Otherwise, offline mode is broken.

馃敩 Minimal Reproduction

To enter EXISTING_CLIENTS_ONLY state:

  1. Create an Angular app:

     mkdir drop_me
     cd drop_me
     npm install @angular/cli
     node_modules/.bin/ng new my-dream-app
     cd my-dream-app
     node_modules/.bin/ng add @angular/pwa --project my-dream-app
     node_modules/.bin/ng build --prod
     npm install http-server
     node_modules/.bin/http-server -p 8080 -c-1 dist/my-dream-app
    
  2. Open http://localhost:8080 in Chrome and wait for the page

  3. Kill the server

  4. Change something in src/app/app.component.html

  5. Rebuild and serve:

     node_modules/.bin/ng build --prod
     node_modules/.bin/http-server -p 8080 -c-1 dist/my-dream-app
    
  6. Go back in Chrome, it slow 3g network in dev tool, hit F5, and wait for
    the service worker to start downloading index.html, main.js or any file of
    the update.

    When that happen, check chrome devtool Offline checkbox.

    The service worker is now in EXISTING_CLIENTS_ONLY.

From now, to create the issue, just hit F5.

The application will be broken offline, even after network reconnection,
tab close/re-open, etc.

For me, beside unregistering the service worker manually, only a new update of
the application can fix the state.

馃實 Your Environment

Angular Version:


Angular CLI: 8.0.3
Node: 11.5.0
OS: linux x64
Angular: 8.0.1
... animations, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router, service-worker

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.800.3
@angular-devkit/build-angular     0.800.3
@angular-devkit/build-optimizer   0.800.3
@angular-devkit/build-webpack     0.800.3
@angular-devkit/core              8.0.3
@angular-devkit/schematics        8.0.3
@angular/cli                      8.0.3
@angular/pwa                      0.800.3
@ngtools/webpack                  8.0.3
@schematics/angular               8.0.3
@schematics/update                0.800.3
rxjs                              6.4.0
typescript                        3.4.5
webpack                           4.30.0

Anything else relevant?

Only tested on Chrome.

@H--o-l

This comment has been minimized.

Copy link
Contributor Author

commented Jun 25, 2019

Finally I think even an update of the application, and thus a new ngsw.json can not fix the state of the service worker.
For me the servie worker try to make the n-1 broken version list of files match with the new ngsw.json which of course can not happen.
Only an update the ngsw-worker.js file itself re-compute the service worker driver state.

Do you think a solution can be found, at least so the service worker can go out of the broken state on an ngsw.json update?

@H--o-l

This comment has been minimized.

Copy link
Contributor Author

commented Jun 25, 2019

I think a code diff that could work to restore the service worker nominal state after a degraded state could be:

--- node_modules/@angular/service-worker/ngsw-worker.js
+++ node_modules/@angular/service-worker/ngsw-worker.js
@@ -2472,6 +2472,14 @@
                 // Future new clients will use this hash as the latest version.
                 this.latestHash = hash;
                 yield this.sync();
+                // A new version have been correctly fecthed and registered.
+                // It is ready to be assigned to previously disconnect clients
+                // and to new clients, thus let's go out of
+                // `EXISTING_CLIENTS_ONLY` mode now.
+                if (this.state == DriverReadyState.EXISTING_CLIENTS_ONLY) {
+                    this.state = DriverReadyState.NORMAL;
+                    this.stateMessage = '(nominal)';
+                }
                 yield this.notifyClientsAboutUpdate();
             });
         }

@gkalpak Sorry to ping you, but maybe you have an opinion if it's a good proposal or not?

@gkalpak

This comment has been minimized.

Copy link
Member

commented Jun 26, 2019

Reading through your comments, everything sounds reasonable, @H--o-l.
But I have to take a closer look at the code and the current behavior (it does definitely sound like a bug). I'll try to get to it in the next few days.

@gkalpak

This comment has been minimized.

Copy link
Member

commented Jul 8, 2019

@H--o-l, I finally got around to looking into this. I think the current behavior is (sort of) intended.

What you said above is correct, except that this only last for as long as the ServiceWorker instance is in EXISTING_CLIENT_ONLY. Note, that, once the ServiceWorker is not used for a while, the browser will stop it (to save resources). It will create a new ServiceWorker instance the next time it needs to handle an event (e.g. a fetch event).

The Driver state (e.g. EXISTING_CLIENTS_ONLY) is not persisted, so each SW instance starts in the nominal state (it may enter a different state again if certain conditions are met, but it starts in normal mode). So, the behavior you observed is temporary (until the browser stops the current SW instance).

During debugging, you can verify that, by forcing the SW instance to be stopped:

  • Go to chrome://serviceworker-internals.
  • Search for the domain name your are debugging.
  • Click on the Stop button.

So, once a ServiceWorker instance enters a degraded state (either EXISTING_CLIENTS_ONLY or SAFE_MODE), it will not leave that state. Once the instance is stopped after a while, the next instance that will be created will try to recover. This is by design: Because we can't be sure why the ServiceWorker entered the degraded state, it is risky to try and recover on the same instance (unless we kept more info on why a state was entered and what would be required to recover).

@H--o-l

This comment has been minimized.

Copy link
Contributor Author

commented Jul 23, 2019

@H--o-l, I finally got around to looking into this. I think the current behavior is (sort of) intended.

Thanks for looking into this!

Sorry for the long answer too, I wasn't here.

It's an interesting fact that the Driver state is not persisted between service worker instances.
I did know that service workers would be stop by browsers, but not how to simulate it, thanks!
(Your instructions were almost perfect: I think it's the chrome://inspect/#service-workers page and the terminate button.)

That been said, I have some remarks around the design:

  1. Service worker API is awesome to finely control caching behaviour, in the same way on each browsers.
    Building design around the fact that the ServiceWorker may be stopped when the browser decide, is, I think, a regression to olders API, where it was the browser do it's thing, deal with it.
    The behavior cannot be predicted on all browsers and at all time, and it will surely differ.

  2. I think comment here suggests that the service worker would recover from the EXISTING_CLIENT_ONLY state with a new nsgw.json file (i.e. an update of the application).
    Sadly it's not enough as the service worker as to be stop by the browser as you say.

  3. The documentation explains what are the degraded state, but not how to recover.
    I'd be happy to open a PR to add your explanation in it, but I rather the design being change.

  4. I think many users are confuse about it and post unnecessary comments or issues:

    • they create a hash mismatch in their ngsw.json for a reason or another
    • they have a 504 error
    • they fix the hash mismatch on the fly on their server
    • the Driver state is still broken, they do not realise it was the correct solution because nothing change
    • they open an issue about having a 504 error.

    I think it would spare you a lot of time (and users too) if it was easier to recover from the degrade states.

What do you think about it @gkalpak?
I'm happy to continue the conversion or to propose a small documentation improvement if you think it's the best.
Thanks again for your time!

@gkalpak

This comment has been minimized.

Copy link
Member

commented Jul 24, 2019

Ooops, I meant chrome://serviceworker-internals (instead of chrome://service-worker-internals). But chrome://inspect/#service-workers works too 馃憤

Regarding your points:

  1. You are right that this leaves some things to the browser, but it was a design decision trying to balance different things. For example, balancing the benefits of caching with users' expectation that opening a new tab gives them a recent (if not the latest) version of the content. Also, keep in mind that the SW APIs should be seen as possible progressive enhancements only - not as integral parts of the app. So, we have chosen to err on the side of caution in some cases.
    That said, I will take a look to see if it might be safe to recover from the EXISTING_CLIENTS_ONLY state. (Recoverign from a SAFE_MODE state would be too risky imo, since we can't know exactly what brought us there.

  2. That's just me not understanding all aspects of SW behavior at the time 馃榿
    (Not that I do now, but I hopefully understand some more than before 馃槢)

  3. Yes, please 鉂わ笍 Adding some info in the docs would be great.

  4. I agree that we should try to make it easier for people to recover or debug such states:

    • Adding more info to the docs is a good first step.
    • If you also have ideas about providing more helpful/actionable info in /ngsw/state, I'm open to suggestions.
    • There are some suggestions for possible improvements in #22087 (comment) (e.g. allowing the driver state to be retrieved by the app). If you fancy working on that, I would be happy to help/discuss.
    • Finally, as mentioned above, I will look into whether we could at least safely recover from EXISTING_CLIENTS_ONLY.

Thx, again, for raising these points 馃憤

gkalpak added a commit to gkalpak/angular that referenced this issue Jul 26, 2019
鈥here is a valid update

Previously, when the ServiceWorker entered a degraded mode
(`EXISTING_CLIENTS_ONLY` or `SAFE_MODE`) it would remain in that mode
for the rest of the lifetime of ServiceWorker instance. Note that
ServiceWorkers are stopped by the browser after a certain period of
inactivity and a new instance is created as soon as the ServiceWorker
needs to handle an event (such as a request from the page). Those new
instances would start from the `NORMAL` mode.

The reason for this behavior is to err on the side of caution: If we
can't be sure why the ServiceWorker entered the degraded state, it is
risky to try and recover on the same instance and might lead to
unexpected behavior.

However, it turns out that the `EXISTING_CLIENTS_ONLY` mode can only be
a result of some error happening with the latest version (e.g. a hash
mismatch in the manifest). Therefore, it is safe to recover from that
mode once a new, valid update is successfully installed and start
accepting new clients.

This commit ensures that the mode is set nack to `NORMAL`, when (a) an
update is successfully installed and (b) the mode was
`EXISTING_CLIENTS_ONLY`.

Fixes angular#31109
gkalpak added a commit to gkalpak/angular that referenced this issue Jul 26, 2019
鈥here is a valid update

Previously, when the ServiceWorker entered a degraded mode
(`EXISTING_CLIENTS_ONLY` or `SAFE_MODE`) it would remain in that mode
for the rest of the lifetime of ServiceWorker instance. Note that
ServiceWorkers are stopped by the browser after a certain period of
inactivity and a new instance is created as soon as the ServiceWorker
needs to handle an event (such as a request from the page). Those new
instances would start from the `NORMAL` mode.

The reason for this behavior is to err on the side of caution: If we
can't be sure why the ServiceWorker entered the degraded state, it is
risky to try and recover on the same instance and might lead to
unexpected behavior.

However, it turns out that the `EXISTING_CLIENTS_ONLY` mode can only be
a result of some error happening with the latest version (e.g. a hash
mismatch in the manifest). Therefore, it is safe to recover from that
mode once a new, valid update is successfully installed and start
accepting new clients.

This commit ensures that the mode is set nack to `NORMAL`, when (a) an
update is successfully installed and (b) the mode was
`EXISTING_CLIENTS_ONLY`.

Fixes angular#31109
gkalpak added a commit to gkalpak/angular that referenced this issue Jul 26, 2019
鈥here is a valid update

Previously, when the ServiceWorker entered a degraded mode
(`EXISTING_CLIENTS_ONLY` or `SAFE_MODE`) it would remain in that mode
for the rest of the lifetime of ServiceWorker instance. Note that
ServiceWorkers are stopped by the browser after a certain period of
inactivity and a new instance is created as soon as the ServiceWorker
needs to handle an event (such as a request from the page). Those new
instances would start from the `NORMAL` mode.

The reason for this behavior is to err on the side of caution: If we
can't be sure why the ServiceWorker entered the degraded mode, it is
risky to try recovering on the same instance and might lead to
unexpected behavior.

However, it turns out that the `EXISTING_CLIENTS_ONLY` mode can only be
a result of some error happening with the latest version (e.g. a hash
mismatch in the manifest). Therefore, it is safe to recover from that
mode once a new, valid update is successfully installed and to start
accepting new clients.

This commit ensures that the mode is set back to `NORMAL`, when (a) an
update is successfully installed and (b) the current mode is
`EXISTING_CLIENTS_ONLY`.

Besides making the behavior more predictable (instead of relying on the
browser to decide when to terminate the current ServiceWorker instance
and create a new one), this change can also improve the developer
experience:
When people notice the error during debugging and fix it by deploying a
new version (either to production or locally), it is confusing that the
ServiceWorker will fetch and install the update (as seen by the requests
in the Network panel in DevTools) but not serve it to clients. With this
change, the update will be served to new clients as soon as it is
installed.

Fixes angular#31109
@gkalpak

This comment has been minimized.

Copy link
Member

commented Jul 26, 2019

OK, I thought about it some more and I believe it is reasonable to try to recover from EXISTING_CLIENTS_ONLY, so I created #31865 with a couple of improvements related to entering/recovering from the EXISTING_CLIENTS_ONLY mode 馃帀

@H--o-l

This comment has been minimized.

Copy link
Contributor Author

commented Jul 29, 2019

Awesome!
I'm glad if you think it's reasonable and that you were able to make a PR about it.
Could I suggest a cherry-pick of that commit b643d51 to include doc improvement inside your PR?

Of course feel free to adapt it, or made your how entirely!

@mhevery mhevery closed this in 094538c Sep 4, 2019
@gkalpak

This comment has been minimized.

Copy link
Member

commented Sep 5, 2019

Sorry, @H--o-l, I forgot about incorporating b643d51.
Now that #31865 is merged (馃帀) would you be up to submitting a separate PR with the docs improvements?

sabeersulaiman added a commit to sabeersulaiman/angular that referenced this issue Sep 6, 2019
鈥here is a valid update (angular#31865)

Previously, when the ServiceWorker entered a degraded mode
(`EXISTING_CLIENTS_ONLY` or `SAFE_MODE`) it would remain in that mode
for the rest of the lifetime of ServiceWorker instance. Note that
ServiceWorkers are stopped by the browser after a certain period of
inactivity and a new instance is created as soon as the ServiceWorker
needs to handle an event (such as a request from the page). Those new
instances would start from the `NORMAL` mode.

The reason for this behavior is to err on the side of caution: If we
can't be sure why the ServiceWorker entered the degraded mode, it is
risky to try recovering on the same instance and might lead to
unexpected behavior.

However, it turns out that the `EXISTING_CLIENTS_ONLY` mode can only be
a result of some error happening with the latest version (e.g. a hash
mismatch in the manifest). Therefore, it is safe to recover from that
mode once a new, valid update is successfully installed and to start
accepting new clients.

This commit ensures that the mode is set back to `NORMAL`, when (a) an
update is successfully installed and (b) the current mode is
`EXISTING_CLIENTS_ONLY`.

Besides making the behavior more predictable (instead of relying on the
browser to decide when to terminate the current ServiceWorker instance
and create a new one), this change can also improve the developer
experience:
When people notice the error during debugging and fix it by deploying a
new version (either to production or locally), it is confusing that the
ServiceWorker will fetch and install the update (as seen by the requests
in the Network panel in DevTools) but not serve it to clients. With this
change, the update will be served to new clients as soon as it is
installed.

Fixes angular#31109

PR Close angular#31865
@H--o-l

This comment has been minimized.

Copy link
Contributor Author

commented Sep 14, 2019

@gkalpak

Now that #31865 is merged (馃帀)

馃帀 馃帀

No issue, I open the PR #32682 !

arnehoek added a commit to arnehoek/angular that referenced this issue Sep 26, 2019
鈥here is a valid update (angular#31865)

Previously, when the ServiceWorker entered a degraded mode
(`EXISTING_CLIENTS_ONLY` or `SAFE_MODE`) it would remain in that mode
for the rest of the lifetime of ServiceWorker instance. Note that
ServiceWorkers are stopped by the browser after a certain period of
inactivity and a new instance is created as soon as the ServiceWorker
needs to handle an event (such as a request from the page). Those new
instances would start from the `NORMAL` mode.

The reason for this behavior is to err on the side of caution: If we
can't be sure why the ServiceWorker entered the degraded mode, it is
risky to try recovering on the same instance and might lead to
unexpected behavior.

However, it turns out that the `EXISTING_CLIENTS_ONLY` mode can only be
a result of some error happening with the latest version (e.g. a hash
mismatch in the manifest). Therefore, it is safe to recover from that
mode once a new, valid update is successfully installed and to start
accepting new clients.

This commit ensures that the mode is set back to `NORMAL`, when (a) an
update is successfully installed and (b) the current mode is
`EXISTING_CLIENTS_ONLY`.

Besides making the behavior more predictable (instead of relying on the
browser to decide when to terminate the current ServiceWorker instance
and create a new one), this change can also improve the developer
experience:
When people notice the error during debugging and fix it by deploying a
new version (either to production or locally), it is confusing that the
ServiceWorker will fetch and install the update (as seen by the requests
in the Network panel in DevTools) but not serve it to clients. With this
change, the update will be served to new clients as soon as it is
installed.

Fixes angular#31109

PR Close angular#31865
@angular-automatic-lock-bot

This comment has been minimized.

Copy link

commented Oct 15, 2019

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Oct 15, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
4 participants
You can鈥檛 perform that action at this time.